{"record":{"id":"cfe907a7f03ace3b","repo":"AlistGo/alist","slug":"errarchiveillegalpath-cfe907","errorCode":"ErrArchiveIllegalPath","errorMessage":"archive entry has illegal path: %s","messagePattern":"archive entry has illegal path: (.+?)","errorType":"exception","errorClass":"ErrArchiveIllegalPath","httpStatus":null,"severity":"error","filePath":"internal/archive/tool/securepath.go","lineNumber":19,"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, \"/\") {\n\t\treturn \"\", fmt.Errorf(\"%w: %s\", ErrArchiveIllegalPath, entryName)\n\t}\n\n\trel := filepath.FromSlash(cleaned)\n\tif filepath.IsAbs(rel) || filepath.VolumeName(rel) != \"\" {\n\t\treturn \"\", fmt.Errorf(\"%w: %s\", ErrArchiveIllegalPath, entryName)","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/internal/archive/tool/securepath.go#L1-L37","documentation":"First rejection inside SecureJoin (internal/archive/tool/securepath.go): any archive entry name containing a NUL byte (\\x00) is refused with ErrArchiveIllegalPath. NUL bytes can truncate path strings in downstream OS calls, a classic path-validation bypass, so they are rejected before any normalization.","triggerScenarios":"Extracting an archive whose member name embeds a NUL byte — typically a crafted/malicious archive (Zip-Slip variant) or a corrupted header producing embedded NULs in the decoded name.","commonSituations":"Deliberately malicious downloads; bit-corrupted archives; fuzz-generated files; archives produced by buggy packers that do not sanitize names.","solutions":["Treat the archive as untrusted; verify its source and hash before retrying","List the archive with an external tool to identify the offending member and remove/repair it","Re-create the archive from trusted content rather than bypassing the check"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Go: reject entry names containing NUL before calling any extraction API\nfunc hasNULName(names []string) bool {\n    for _, n := range names {\n        if strings.ContainsRune(n, 0) { return true }\n    }\n    return false\n}","typeGuard":"func isValidEntryName(name string) bool {\n    return !strings.Contains(name, \"\\x00\")\n}","tryCatchPattern":"if err := tool.SecureJoin(outDir, name); err != nil {\n    if errors.Is(err, tool.ErrArchiveIllegalPath) {\n        // untrusted/corrupt archive: quarantine it; never strip NULs and retry\n    }\n    return err\n}","preventionTips":["Validate member names (no NUL, no leading separators, no '..') when generating archives","Scan untrusted archives with listing tools before extraction","Treat NUL-in-name findings as tamper indicators"],"tags":["archive","security","zip-slip","path-traversal","validation"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}