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

  1. Verify the status output yourself: `git -C <worktree> status --porcelain=v1 -z --untracked-files=all` and look for absolute or ../ paths
  2. Check for git aliases/filters/wrappers altering status output (`git config -l | grep -i filter`, custom git in PATH); remove them
  3. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/3e95b06abc49bdce. Report an issue: GitHub.