Allow safe custom restore targets
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
b571e6ee3cca59d6f63e15c721d53d04f52d27e4e121fa1d314dde7fe7d454cc urbm-2026.06.21.r008-x86_64-1.txz
|
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
7331dd7ade82aa68351b48f2555cd6dd9141620e6ba4ebce14e722fb0e6e5a46 urbm-2026.06.21.r009-x86_64-1.txz
|
||||||
Vendored
+6
-2
@@ -2,13 +2,17 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "urbm">
|
<!ENTITY name "urbm">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.06.21.r008">
|
<!ENTITY version "2026.06.21.r009">
|
||||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
||||||
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
|
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
|
||||||
<!ENTITY packageSHA256 "b571e6ee3cca59d6f63e15c721d53d04f52d27e4e121fa1d314dde7fe7d454cc">
|
<!ENTITY packageSHA256 "7331dd7ade82aa68351b48f2555cd6dd9141620e6ba4ebce14e722fb0e6e5a46">
|
||||||
]>
|
]>
|
||||||
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
|
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
|
||||||
<CHANGES>
|
<CHANGES>
|
||||||
|
### 2026.06.21.r009
|
||||||
|
- Allow normal staging restores to explicitly selected safe targets under /mnt/user, /mnt/disks, and /mnt/remotes.
|
||||||
|
- Keep protected system paths blocked and continue rejecting symlink traversal in restore targets.
|
||||||
|
|
||||||
### 2026.06.21.r008
|
### 2026.06.21.r008
|
||||||
- Improve the visual hierarchy of folder trees with clearer disclosure buttons, row spacing, and normal path casing.
|
- Improve the visual hierarchy of folder trees with clearer disclosure buttons, row spacing, and normal path casing.
|
||||||
- Preserve tree and page scroll positions when expanding folders in directory and snapshot browsers.
|
- Preserve tree and page scroll positions when expanding folders in directory and snapshot browsers.
|
||||||
|
|||||||
@@ -16,11 +16,7 @@ func ValidateRestoreTarget(target, restoreRoot string, inPlace, confirmed bool)
|
|||||||
return "", errors.New("validation: protected system restore target")
|
return "", errors.New("validation: protected system restore target")
|
||||||
}
|
}
|
||||||
if !inPlace {
|
if !inPlace {
|
||||||
root := filepath.Clean(restoreRoot)
|
if err := validateStagingRestoreTarget(clean, restoreRoot); err != nil {
|
||||||
if clean != root && !strings.HasPrefix(clean, root+string(os.PathSeparator)) {
|
|
||||||
return "", errors.New("validation: staging restore must remain below restoreRoot")
|
|
||||||
}
|
|
||||||
if err := rejectSymlinks(root, clean); err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
} else if !confirmed {
|
} else if !confirmed {
|
||||||
@@ -35,6 +31,19 @@ func ValidateRestoreTarget(target, restoreRoot string, inPlace, confirmed bool)
|
|||||||
return clean, nil
|
return clean, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateStagingRestoreTarget(target, restoreRoot string) error {
|
||||||
|
allowedRoots := []string{filepath.Clean(restoreRoot), "/mnt/user", "/mnt/disks", "/mnt/remotes"}
|
||||||
|
for _, root := range allowedRoots {
|
||||||
|
if root == "." || root == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if target == root || strings.HasPrefix(target, root+string(os.PathSeparator)) {
|
||||||
|
return rejectSymlinks(root, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("validation: staging restore target must be below restoreRoot, /mnt/user, /mnt/disks, or /mnt/remotes")
|
||||||
|
}
|
||||||
|
|
||||||
func rejectSymlinks(root, target string) error {
|
func rejectSymlinks(root, target string) error {
|
||||||
relative, err := filepath.Rel(root, target)
|
relative, err := filepath.Rel(root, target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ func TestValidateRestoreTarget(t *testing.T) {
|
|||||||
if got, err := ValidateRestoreTarget(target, root, false, false); err != nil || got != target {
|
if got, err := ValidateRestoreTarget(target, root, false, false); err != nil || got != target {
|
||||||
t.Fatalf("staging target rejected: %q %v", got, err)
|
t.Fatalf("staging target rejected: %q %v", got, err)
|
||||||
}
|
}
|
||||||
|
if got, err := ValidateRestoreTarget("/mnt/user/Transfer", root, false, false); err != nil || got != "/mnt/user/Transfer" {
|
||||||
|
t.Fatalf("user share restore target rejected: %q %v", got, err)
|
||||||
|
}
|
||||||
|
if _, err := ValidateRestoreTarget("/home/root/restore", root, false, false); err == nil {
|
||||||
|
t.Fatal("staging target outside allowed restore roots accepted")
|
||||||
|
}
|
||||||
if _, err := ValidateRestoreTarget("/etc", root, true, true); err == nil {
|
if _, err := ValidateRestoreTarget("/etc", root, true, true); err == nil {
|
||||||
t.Fatal("protected target accepted")
|
t.Fatal("protected target accepted")
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -2,13 +2,17 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "urbm">
|
<!ENTITY name "urbm">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.06.21.r008">
|
<!ENTITY version "2026.06.21.r009">
|
||||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
||||||
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
|
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
|
||||||
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
|
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
|
||||||
]>
|
]>
|
||||||
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
|
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
|
||||||
<CHANGES>
|
<CHANGES>
|
||||||
|
### 2026.06.21.r009
|
||||||
|
- Allow normal staging restores to explicitly selected safe targets under /mnt/user, /mnt/disks, and /mnt/remotes.
|
||||||
|
- Keep protected system paths blocked and continue rejecting symlink traversal in restore targets.
|
||||||
|
|
||||||
### 2026.06.21.r008
|
### 2026.06.21.r008
|
||||||
- Improve the visual hierarchy of folder trees with clearer disclosure buttons, row spacing, and normal path casing.
|
- Improve the visual hierarchy of folder trees with clearer disclosure buttons, row spacing, and normal path casing.
|
||||||
- Preserve tree and page scroll positions when expanding folders in directory and snapshot browsers.
|
- Preserve tree and page scroll positions when expanding folders in directory and snapshot browsers.
|
||||||
|
|||||||
+4
-4
@@ -9,12 +9,12 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore"
|
|||||||
<?php
|
<?php
|
||||||
$pluginRoot = '/plugins/urbm';
|
$pluginRoot = '/plugins/urbm';
|
||||||
?>
|
?>
|
||||||
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260621r008">
|
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260621r009">
|
||||||
<div id="urbm-app" data-api="<?= $pluginRoot ?>/api.php">
|
<div id="urbm-app" data-api="<?= $pluginRoot ?>/api.php">
|
||||||
<header class="bu-header">
|
<header class="bu-header">
|
||||||
<div class="bu-brand">
|
<div class="bu-brand">
|
||||||
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260621r008" alt="URBM-Logo">
|
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260621r009" alt="URBM-Logo">
|
||||||
<div><h1>URBM <span class="bu-version">2026.06.21.r008</span></h1><p>Restic Backup Manager für Unraid</p></div>
|
<div><h1>URBM <span class="bu-version">2026.06.21.r009</span></h1><p>Restic Backup Manager für Unraid</p></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="bu-health" class="bu-health" tabindex="0" data-tooltip="Zeigt, ob die URBM-Hintergrundkomponente erreichbar ist. Beispiel: 'Daemon online' bedeutet, dass Jobs gestartet werden können.">Verbindung wird hergestellt...</div>
|
<div id="bu-health" class="bu-health" tabindex="0" data-tooltip="Zeigt, ob die URBM-Hintergrundkomponente erreichbar ist. Beispiel: 'Daemon online' bedeutet, dass Jobs gestartet werden können.">Verbindung wird hergestellt...</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -32,4 +32,4 @@ $pluginRoot = '/plugins/urbm';
|
|||||||
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
|
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
<script>window.URBM_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
|
<script>window.URBM_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
|
||||||
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.21.r008.js"></script>
|
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.21.r009.js"></script>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
knownHostsPath: 'Datei mit dem geprüften SSH-Hostschlüssel. Beispiel: /root/.ssh/known_hosts. StrictHostKeyChecking ist immer aktiv.',
|
knownHostsPath: 'Datei mit dem geprüften SSH-Hostschlüssel. Beispiel: /root/.ssh/known_hosts. StrictHostKeyChecking ist immer aktiv.',
|
||||||
snapshotId: 'Eindeutige ID des Sicherungsstands. Beispiel: 7f3a91cd. Sie kann im Bereich Snapshots über Durchsuchen ausgewählt werden.',
|
snapshotId: 'Eindeutige ID des Sicherungsstands. Beispiel: 7f3a91cd. Sie kann im Bereich Snapshots über Durchsuchen ausgewählt werden.',
|
||||||
includes: 'Nur diese Pfade werden wiederhergestellt, einer pro Zeile. Beispiel: /mnt/user/Documents/Rechnung.pdf oder /mnt/user/Documents.',
|
includes: 'Nur diese Pfade werden wiederhergestellt, einer pro Zeile. Beispiel: /mnt/user/Documents/Rechnung.pdf oder /mnt/user/Documents.',
|
||||||
target: 'Zielverzeichnis der Wiederherstellung. Sicheres Beispiel: /mnt/user/urbm-restores/restore-123. Standardmäßig wird nicht direkt über Originaldaten geschrieben.',
|
target: 'Zielverzeichnis der Wiederherstellung. Beispiele: /mnt/user/Transfer oder /mnt/user/urbm-restores/restore-123. Ohne Originalort-Option sind sichere Ziele unter /mnt/user, /mnt/disks, /mnt/remotes oder dem Restore-Staging-Verzeichnis erlaubt.',
|
||||||
inPlace: 'Erlaubt Wiederherstellung direkt an einen produktiven Zielpfad. Nur aktivieren, wenn vorhandene Dateien bewusst ersetzt werden sollen.',
|
inPlace: 'Erlaubt Wiederherstellung direkt an einen produktiven Zielpfad. Nur aktivieren, wenn vorhandene Dateien bewusst ersetzt werden sollen.',
|
||||||
confirmed: 'Zusätzliche Bestätigung für einen In-place-Restore. Ohne diese Bestätigung wird ein direkter Restore blockiert.',
|
confirmed: 'Zusätzliche Bestätigung für einen In-place-Restore. Ohne diese Bestätigung wird ein direkter Restore blockiert.',
|
||||||
resticPath: 'Pfad zur vom Plugin bereitgestellten Restic-Datei. Standard: /usr/local/libexec/urbm/restic. Normalerweise nicht ändern.',
|
resticPath: 'Pfad zur vom Plugin bereitgestellten Restic-Datei. Standard: /usr/local/libexec/urbm/restic. Normalerweise nicht ändern.',
|
||||||
|
|||||||
Reference in New Issue
Block a user