{"record":{"id":"3e95b06abc49bdce","repo":"gastownhall/beads","slug":"unsafe-path-q-in-porcelain-status","errorCode":null,"errorMessage":"unsafe path %q in porcelain status","messagePattern":"unsafe path %q in porcelain status","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/worktree_cmd.go","lineNumber":1156,"sourceCode":"\t\t\t}\n\t\t\tpathSet[records[index]] = struct{}{}\n\t\t}\n\t}\n\n\tpaths := make([]string, 0, len(pathSet))\n\tfor path := range pathSet {\n\t\tpaths = append(paths, path)\n\t}\n\tsort.Strings(paths)\n\n\thasher := sha256.New()\n\tfor _, gitPath := range paths {\n\t\tcleanPath := filepath.Clean(filepath.FromSlash(gitPath))\n\t\tif cleanPath == \".\" ||\n\t\t\tfilepath.IsAbs(cleanPath) ||\n\t\t\tcleanPath == \"..\" ||\n\t\t\tstrings.HasPrefix(cleanPath, \"..\"+string(filepath.Separator)) {\n\t\t\treturn \"\", fmt.Errorf(\"unsafe path %q in porcelain status\", gitPath)\n\t\t}\n\t\tif _, err := fmt.Fprintf(hasher, \"%s\\x00\", filepath.ToSlash(cleanPath)); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tfingerprint, err := fingerprintWorktreeFilesystem(filepath.Join(worktreePath, cleanPath))\n\t\tif err != nil {\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\tfingerprint = \"<missing>\"\n\t\t\t} else {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t}\n\t\tif _, err := fmt.Fprintf(hasher, \"%s\\x00\", fingerprint); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\treturn fmt.Sprintf(\"%x\", hasher.Sum(nil)), nil\n}","sourceCodeStart":1138,"sourceCodeEnd":1174,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/worktree_cmd.go#L1138-L1174","documentation":"After collecting dirty paths from git status, bd validates each path before joining it under the worktree root and hashing: it must not be empty (\".\"), absolute, or escape the worktree via \"..\". This safety check rejects path traversal; hitting it means git reported a path outside the worktree (or the output was tampered with/corrupted), so bd aborts rather than touch files outside the worktree.","triggerScenarios":"fingerprintWorktreeStatusPaths receiving a status record whose path resolves to \".\", an absolute path, or something containing \"..\" — e.g. corrupted status output, an absolute-path-emitting git wrapper, or attacker-influenced repository content in a hostile repo.","commonSituations":"Running bd in a repo with malicious git configuration (status filters/wrappers); a patched git that emits absolute paths in porcelain; processing status text that was not produced by `git status -z` directly.","solutions":["Verify the status output yourself: `git -C <worktree> status --porcelain=v1 -z --untracked-files=all` and look for absolute or ../ paths","Check for git aliases/filters/wrappers altering status output (`git config -l | grep -i filter`, custom git in PATH); remove them","If you did not expect traversal, treat the checkout as untrusted: inspect `.git/config` and hooks before proceeding"],"exampleFix":"// before: git wrapper emitting absolute paths\ngit() { command git \"$@\" | sed 's|/abs/repo/||' ; }  # remove\n// after: use stock git, ensure relative porcelain paths\nunset -f git && bd worktree remove mywt","handlingStrategy":"validation","validationCode":"clean := filepath.Clean(p)\nif clean == \".\" || filepath.IsAbs(clean) || clean == \"..\" || strings.HasPrefix(clean, \"..\"+string(filepath.Separator)) {\n    return fmt.Errorf(\"unsafe path %q\", p)\n}","typeGuard":"func safeRelPath(p string) bool {\n    c := filepath.Clean(p)\n    return c != \".\" && !filepath.IsAbs(c) && c != \"..\" && !strings.HasPrefix(c, \"..\"+string(filepath.Separator))\n}","tryCatchPattern":"if strings.Contains(err.Error(), \"unsafe path\") {\n    // treat repo as untrusted: audit .git/config, hooks, and git wrappers before retrying\n}","preventionTips":["Never install git wrappers/aliases that rewrite porcelain paths","Treat status output from untrusted repos as untrusted input","Keep stock git in PATH for bd worktree operations"],"tags":["git","security","path-traversal"],"backgroundTag":"unsafe-path-in-git-output","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}