{"record":{"id":"0a73b1528d9cb8a9","repo":"henrygd/beszel","slug":"invalid-file-path-q","errorCode":null,"errorMessage":"invalid file path: %q","messagePattern":"invalid file path: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/ghupdate/extract.go","lineNumber":83,"sourceCode":"\t\toutFile, err := os.Create(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, err := io.Copy(outFile, tr); err != nil {\n\t\t\toutFile.Close()\n\t\t\treturn err\n\t\t}\n\t\toutFile.Close()\n\t}\n\n\treturn nil\n}\n\n// archivePath returns a path within destDir, rejecting path traversal entries.\nfunc archivePath(destDir, name string) (string, error) {\n\tif !filepath.IsLocal(name) {\n\t\treturn \"\", fmt.Errorf(\"invalid file path: %q\", name)\n\t}\n\treturn filepath.Join(destDir, name), nil\n}\n\n// extractZip extracts the zip archive at \"src\" to \"dest\".\n//\n// Note that only dirs and regular files will be extracted.\n// Symbolic links, named pipes, sockets, or any other irregular files\n// are skipped because they come with too many edge cases and ambiguities.\nfunc extractZip(src, dest string) error {\n\tzr, err := zip.OpenReader(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer zr.Close()\n\n\tfor _, f := range zr.File {\n\t\terr := extractFile(f, dest)","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/henrygd/beszel/blob/b38fb7dafa60812cc22e6a84ce313e94f1ce0a32/internal/ghupdate/extract.go#L65-L101","documentation":"archivePath validates the entry name (here used for the downloaded asset filename) with filepath.IsLocal before joining it into destDir, rejecting absolute paths and anything containing traversal elements like \"..\". This is a path-traversal defense; if the release metadata contains a non-local name, the library refuses to build a destination path.","triggerScenarios":"ghupdate.Update -> update -> archivePath when the asset Name from the release JSON is absolute (\"/tmp/x\"), contains \"../\", is reserved (\".\", volume names on Windows), or is otherwise not a local relative path — typically from a hostile or malformed release response.","commonSituations":"A malicious or spoofed GitHub API response (or compromised mirror) publishing assets with traversal names; test fixtures with unusual names; a fork whose asset names contain directory components.","solutions":["Ensure release assets are named with plain filenames (no slashes, no leading separators).","Only fetch releases from the trusted owner/repo (henrygd/beszel) or a mirror you control; treat this error as a red flag for a compromised source.","If this fires unexpectedly, inspect the release JSON's asset names at the API URL shown in the updater output."],"exampleFix":"// before (malicious asset name in release JSON)\n\"name\": \"../../etc/beszel\"\n// after (valid asset name)\n\"name\": \"beszel-agent_0.12.0_linux_amd64.tar.gz\"","handlingStrategy":"validation","validationCode":"if !filepath.IsLocal(asset.Name) {\n    return fmt.Errorf(\"refusing release with non-local asset name %q — source may be compromised\", asset.Name)\n}","typeGuard":null,"tryCatchPattern":"updated, err := ghupdate.Update(cfg)\nif err != nil && strings.Contains(err.Error(), \"invalid file path\") {\n    log.Printf(\"release contains a path-traversal-looking name (%v); inspect release JSON and consider the source compromised\", err)\n}","preventionTips":["Only fetch releases from the trusted owner/repo or a mirror you control.","Name release assets with plain flat filenames.","Investigate immediately if this fires — it usually indicates a malicious or malformed release source."],"tags":["path-traversal","security","validation","archive"],"backgroundTag":"path-traversal-detected","analyzedSha":"b38fb7dafa60812cc22e6a84ce313e94f1ce0a32","analyzedAt":"2026-08-31T15:10:10.149Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}