From 12dca5f0b076acbee67feb713203194ab5c9fd39 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 24 Feb 2026 20:07:18 +0100 Subject: [PATCH] fix(restore): reject zip entries with path traversal sequences Validate all zip entry names during restore to reject entries containing directory traversal sequences (e.g. ../../../pwned.txt). This prevents a Zip Slip attack where a malicious archive could write files outside the intended extraction directory. --- pkg/modules/dump/restore.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/modules/dump/restore.go b/pkg/modules/dump/restore.go index e4fbec4e3..aa246efc0 100644 --- a/pkg/modules/dump/restore.go +++ b/pkg/modules/dump/restore.go @@ -26,6 +26,7 @@ import ( "fmt" "io" "os" + "path/filepath" "sort" "strconv" "strings" @@ -71,6 +72,10 @@ func Restore(filename string, overrideConfig bool) error { dbfiles := make(map[string]*zip.File) filesFiles := make(map[string]*zip.File) for _, file := range r.File { + if containsPathTraversal(file.Name) { + return fmt.Errorf("unsafe path in zip archive: %q", file.Name) + } + if strings.HasPrefix(file.Name, "config") { configFile = file continue @@ -427,6 +432,17 @@ func restoreConfig(configFile, dotEnvFile *zip.File) error { return nil } +// containsPathTraversal checks if a zip entry name contains directory traversal +// sequences that could be used to write files outside the intended directory. +func containsPathTraversal(name string) bool { + // Clean the path and check for traversal + cleanPath := filepath.ToSlash(filepath.Clean(name)) + return strings.HasPrefix(cleanPath, "../") || + strings.Contains(cleanPath, "/../") || + cleanPath == ".." || + strings.HasPrefix(name, "/") +} + func checkVikunjaVersion(versionFile *zip.File) error { if versionFile == nil { return fmt.Errorf("dump does not contain VERSION file, refusing to continue")