{"record":{"id":"f12692c33bcdb297","repo":"AlistGo/alist","slug":"archive-entry-has-illegal-path","errorCode":null,"errorMessage":"archive entry has illegal path","messagePattern":"archive entry has illegal path","errorType":"exception","errorClass":"ErrArchiveIllegalPath","httpStatus":null,"severity":"critical","filePath":"internal/archive/tool/securepath.go","lineNumber":13,"sourceCode":"package tool\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// ErrArchiveIllegalPath indicates an archive entry path is unsafe for extraction.\nvar ErrArchiveIllegalPath = errors.New(\"archive entry has illegal path\")\n\n// SecureJoin returns a safe extraction path for an archive entry.\n// It rejects absolute paths, traversal, Windows drive/UNC paths, and NUL bytes.\nfunc SecureJoin(baseDir, entryName string) (string, error) {\n\tif strings.Contains(entryName, \"\\x00\") {\n\t\treturn \"\", fmt.Errorf(\"%w: %s\", ErrArchiveIllegalPath, entryName)\n\t}\n\n\tnormalized := strings.ReplaceAll(entryName, \"\\\\\", \"/\")\n\tif strings.HasPrefix(normalized, \"//\") {\n\t\treturn \"\", fmt.Errorf(\"%w: %s\", ErrArchiveIllegalPath, entryName)\n\t}\n\tcleaned := path.Clean(normalized)\n\n\tif cleaned == \".\" || cleaned == \"..\" || strings.HasPrefix(cleaned, \"../\") {\n\t\treturn \"\", fmt.Errorf(\"%w: %s\", ErrArchiveIllegalPath, entryName)\n\t}\n\tif strings.HasPrefix(cleaned, \"/\") {","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/internal/archive/tool/securepath.go#L1-L31","documentation":"ErrArchiveIllegalPath is returned by SecureJoin when an archive entry name is unsafe to extract: it contains NUL bytes, starts with '//' (UNC path), is absolute, escapes the base directory via '..' traversal, or uses Windows drive letters. The offending entry name is attached via fmt.Errorf('%w: %s'). This is the zip-slip / path-traversal defense for extraction.","triggerScenarios":"Extracting an archive containing entries like '../../etc/passwd', 'C:\\Windows\\system32\\x', '\\\\server\\share\\x', '/absolute/path', or names with embedded \\x00. Any code path that calls SecureJoin per entry during extraction will refuse these immediately.","commonSituations":"Malicious uploads crafted for zip-slip; archives created by tools that store absolute paths (some Windows archivers); entries with backslash-separated names on Linux extraction; symlink-heavy archives from unknown sources.","solutions":["Reject the archive if it comes from an untrusted source — illegal paths are a strong maliciousness signal","If the archive is trusted and paths are merely absolute, re-pack it with relative paths before extraction (e.g. on the machine that created it)","Never bypass SecureJoin or strip the check to 'make it work'","Scan archives (e.g. unzip -l, zipinfo) before extracting user-supplied content"],"exampleFix":"// before: unsafe join allows escape\nfull := filepath.Join(destDir, entry.Name)\n\n// after: guarded join\nfull, err := tool.SecureJoin(destDir, entry.Name)\nif err != nil { return err }","handlingStrategy":"type-guard","validationCode":"for _, name := range entryNames {\n    if strings.Contains(name, \"\\x00\") || strings.HasPrefix(name, \"/\") || strings.HasPrefix(name, \"\\\\\\\\\") { return tool.ErrArchiveIllegalPath }\n}","typeGuard":"func isIllegalPath(err error) bool { return errors.Is(err, tool.ErrArchiveIllegalPath) }","tryCatchPattern":"dest, err := tool.SecureJoin(baseDir, entry.Name)\nif errors.Is(err, tool.ErrArchiveIllegalPath) {\n    log.Warnf(\"skipping hostile entry %q\", entry.Name)\n    continue // skip entry; extraction proceeds\n}","preventionTips":["Never join archive entry names with plain filepath.Join","Quarantine archives that contain traversal entries","Skip-and-log hostile entries for availability, but alert on frequency"],"tags":["security","zip-slip","path-traversal","archive","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}