diff --git a/dist/urbm-2026.06.21.r008-x86_64-1.txz.sha256 b/dist/urbm-2026.06.21.r008-x86_64-1.txz.sha256 deleted file mode 100644 index 20b220c..0000000 --- a/dist/urbm-2026.06.21.r008-x86_64-1.txz.sha256 +++ /dev/null @@ -1 +0,0 @@ -b571e6ee3cca59d6f63e15c721d53d04f52d27e4e121fa1d314dde7fe7d454cc urbm-2026.06.21.r008-x86_64-1.txz diff --git a/dist/urbm-2026.06.21.r008-x86_64-1.txz b/dist/urbm-2026.06.21.r009-x86_64-1.txz similarity index 56% rename from dist/urbm-2026.06.21.r008-x86_64-1.txz rename to dist/urbm-2026.06.21.r009-x86_64-1.txz index 683d44b..35073ee 100644 Binary files a/dist/urbm-2026.06.21.r008-x86_64-1.txz and b/dist/urbm-2026.06.21.r009-x86_64-1.txz differ diff --git a/dist/urbm-2026.06.21.r009-x86_64-1.txz.sha256 b/dist/urbm-2026.06.21.r009-x86_64-1.txz.sha256 new file mode 100644 index 0000000..a9c9d07 --- /dev/null +++ b/dist/urbm-2026.06.21.r009-x86_64-1.txz.sha256 @@ -0,0 +1 @@ +7331dd7ade82aa68351b48f2555cd6dd9141620e6ba4ebce14e722fb0e6e5a46 urbm-2026.06.21.r009-x86_64-1.txz diff --git a/dist/urbm.plg b/dist/urbm.plg index b523ee2..510afff 100644 --- a/dist/urbm.plg +++ b/dist/urbm.plg @@ -2,13 +2,17 @@ - + - + ]> +### 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 - 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. diff --git a/internal/platform/paths.go b/internal/platform/paths.go index 90279d8..5ca1fcc 100644 --- a/internal/platform/paths.go +++ b/internal/platform/paths.go @@ -16,11 +16,7 @@ func ValidateRestoreTarget(target, restoreRoot string, inPlace, confirmed bool) return "", errors.New("validation: protected system restore target") } if !inPlace { - root := filepath.Clean(restoreRoot) - 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 { + if err := validateStagingRestoreTarget(clean, restoreRoot); err != nil { return "", err } } else if !confirmed { @@ -35,6 +31,19 @@ func ValidateRestoreTarget(target, restoreRoot string, inPlace, confirmed bool) 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 { relative, err := filepath.Rel(root, target) if err != nil { diff --git a/internal/platform/paths_test.go b/internal/platform/paths_test.go index f223cde..152fd4a 100644 --- a/internal/platform/paths_test.go +++ b/internal/platform/paths_test.go @@ -12,6 +12,12 @@ func TestValidateRestoreTarget(t *testing.T) { if got, err := ValidateRestoreTarget(target, root, false, false); err != nil || got != target { 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 { t.Fatal("protected target accepted") } diff --git a/plugin/urbm.plg b/plugin/urbm.plg index 28924e5..af7f5b6 100644 --- a/plugin/urbm.plg +++ b/plugin/urbm.plg @@ -2,13 +2,17 @@ - + ]> +### 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 - 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. diff --git a/webgui/URBM.page b/webgui/URBM.page index eb94dd4..6a66a8b 100644 --- a/webgui/URBM.page +++ b/webgui/URBM.page @@ -9,12 +9,12 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore" - +
- -

URBM 2026.06.21.r008

Restic Backup Manager für Unraid

+ +

URBM 2026.06.21.r009

Restic Backup Manager für Unraid

Verbindung wird hergestellt...
@@ -32,4 +32,4 @@ $pluginRoot = '/plugins/urbm';
- + diff --git a/webgui/assets/urbm.js b/webgui/assets/urbm.js index 19dd952..39a016c 100644 --- a/webgui/assets/urbm.js +++ b/webgui/assets/urbm.js @@ -39,7 +39,7 @@ 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.', 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.', 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.',