gastownhall/beads · error
unsafe path %q in porcelain status
Error message
unsafe path %q in porcelain status
What it means
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.
Source
Thrown at cmd/bd/worktree_cmd.go:1156
}
pathSet[records[index]] = struct{}{}
}
}
paths := make([]string, 0, len(pathSet))
for path := range pathSet {
paths = append(paths, path)
}
sort.Strings(paths)
hasher := sha256.New()
for _, gitPath := range paths {
cleanPath := filepath.Clean(filepath.FromSlash(gitPath))
if cleanPath == "." ||
filepath.IsAbs(cleanPath) ||
cleanPath == ".." ||
strings.HasPrefix(cleanPath, ".."+string(filepath.Separator)) {
return "", fmt.Errorf("unsafe path %q in porcelain status", gitPath)
}
if _, err := fmt.Fprintf(hasher, "%s\x00", filepath.ToSlash(cleanPath)); err != nil {
return "", err
}
fingerprint, err := fingerprintWorktreeFilesystem(filepath.Join(worktreePath, cleanPath))
if err != nil {
if os.IsNotExist(err) {
fingerprint = "<missing>"
} else {
return "", err
}
}
if _, err := fmt.Fprintf(hasher, "%s\x00", fingerprint); err != nil {
return "", err
}
}
return fmt.Sprintf("%x", hasher.Sum(nil)), nil
}View on GitHub (pinned to 71377f2769)
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
Example fix
// before: git wrapper emitting absolute paths
git() { command git "$@" | sed 's|/abs/repo/||' ; } # remove
// after: use stock git, ensure relative porcelain paths
unset -f git && bd worktree remove mywt Defensive patterns
Strategy: validation
Validate before calling
clean := filepath.Clean(p)
if clean == "." || filepath.IsAbs(clean) || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
return fmt.Errorf("unsafe path %q", p)
} Type guard
func safeRelPath(p string) bool {
c := filepath.Clean(p)
return c != "." && !filepath.IsAbs(c) && c != ".." && !strings.HasPrefix(c, ".."+string(filepath.Separator))
} Try / catch
if strings.Contains(err.Error(), "unsafe path") {
// treat repo as untrusted: audit .git/config, hooks, and git wrappers before retrying
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- path escapes workspace: %s
- refusing to write secret key %q to git-tracked config file %
- --allowed-host is empty; pass the Host header value clients
- identity: oversized reply
- identity: invalid reply MAC
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3e95b06abc49bdce.
Report an issue: GitHub.