Skip to content

fix(sandbox): resolve git metadata carveouts for worktrees#805

Open
jaivial wants to merge 1 commit into
Gitlawb:mainfrom
jaivial:fix/sandbox-worktree-git-carveout
Open

fix(sandbox): resolve git metadata carveouts for worktrees#805
jaivial wants to merge 1 commit into
Gitlawb:mainfrom
jaivial:fix/sandbox-worktree-git-carveout

Conversation

@jaivial

@jaivial jaivial commented Jul 24, 2026

Copy link
Copy Markdown

Problem

Worktree/submodule checkouts store .git as a file (gitdir: <path>), not a directory. The sandbox's git metadata write-deny carveout (gitMetadataWriteCarveouts in internal/sandbox/profile.go) hardcoded <root>/.git/hooks + <root>/.git/config, assuming .git is a directory.

In a worktree that path has no mkdir-able parent (.git is a file), so the Linux bwrap backend's --tmpfs <root>/.git/hooks fails:

bwrap: Can't mkdir parents for <root>/.git/hooks: Not a directory

every sandboxed tool is blocked in a worktree checkout (bash, file tools, etc.).

Fix

  • .git is a file → resolve the real gitdir from the gitdir: pointer, follow commondir, and deny the shared hooks/config (a worktree is protected like a plain clone).
  • .git is a dir or absent → unchanged (<root>/.git/{hooks,config}).
  • New helpers: resolveGitDir, resolveGitCommonDir.

Tests

  • internal/sandbox/profile_worktree_test.go: worktree resolves the common dir + never points under the .git file; plain checkout keeps literal paths.
  • Existing TestSeatbeltProfileAllowsGitWritesExceptHooksAndConfig still passes.

Verification

bin carveout result
before <worktree>/.git/hooks bwrap: Can't mkdir parents ... Not a directory
after <common>/.git/hooks sandboxed git rev-parse runs fine

Summary by CodeRabbit

  • Bug Fixes
    • Improved sandbox protection for Git worktrees and submodules by correctly identifying Git metadata locations when .git is a file.
    • Prevented write restrictions from targeting invalid paths in these checkout configurations.
    • Preserved correct Git metadata protection for standard checkouts.

.git is a file (gitdir: ...) in worktrees/submodules, not a dir. The
hooks/config write-deny carveout assumed a directory and pointed at
<root>/.git/hooks, which has no mkdir-able parent under the .git file.
bwrap then failed 'Can't mkdir parents ... Not a directory', blocking
every sandboxed tool in worktree checkouts.

Resolve the real (common) git dir via the gitdir pointer + commondir so
worktrees deny the shared hooks/config like a plain clone. Plain
checkouts (.git dir) and .git-absent roots keep the literal paths.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

gitMetadataWriteCarveouts now resolves .git files to the actual git and common directories before creating sandbox carveouts. Tests cover worktrees, submodules-style indirection, and plain checkouts.

Changes

Git metadata carveout resolution

Layer / File(s) Summary
Resolve git directories and compute carveouts
internal/sandbox/profile.go
gitMetadataWriteCarveouts resolves .git file pointers and commondir references, placing hooks and config carveouts under the appropriate git directory while retaining fallback behavior.
Validate worktree and plain checkout paths
internal/sandbox/profile_worktree_test.go
Tests verify resolved common-directory carveouts for worktrees and direct .git carveouts for plain checkouts.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • Gitlawb/zero#654: Modifies sandbox .git carveout handling for hooks and config.

Suggested reviewers: gnanam1990, vasanthdev2004

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing sandbox git metadata carveouts for worktree checkouts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/sandbox/profile.go`:
- Around line 72-75: Update the gitDir resolution flow around resolveGitDir so
an unresolved non-directory .git file does not retain the literal root/.git
fallback; return no carveouts for stale, malformed, or unreadable gitdir
pointers. Preserve the literal fallback only when .git is absent, and add a
regression test covering a stale gitdir: pointer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a4a360b2-9b5c-48d9-b5ae-4d6f0af98017

📥 Commits

Reviewing files that changed from the base of the PR and between a4a805a and 0c7ae90.

📒 Files selected for processing (2)
  • internal/sandbox/profile.go
  • internal/sandbox/profile_worktree_test.go

Comment on lines +72 to +75
if info, err := os.Stat(gitDir); err == nil && !info.IsDir() {
if real := resolveGitDir(root); real != "" {
gitDir = resolveGitCommonDir(real)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Return no carveouts when a .git file cannot be resolved.

If resolveGitDir returns "" for a stale, malformed, or unreadable .git file, this leaves gitDir as <root>/.git and returns descendants of that file. That recreates the documented bwrap “Not a directory” failure and blocks sandboxed tools. Keep the literal fallback only when .git is absent; return no carveouts for an unresolved non-directory .git, with a regression test for a stale gitdir: pointer.

Proposed fix
 	if info, err := os.Stat(gitDir); err == nil && !info.IsDir() {
-		if real := resolveGitDir(root); real != "" {
-			gitDir = resolveGitCommonDir(real)
+		real := resolveGitDir(root)
+		if real == "" {
+			return nil
 		}
+		gitDir = resolveGitCommonDir(real)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if info, err := os.Stat(gitDir); err == nil && !info.IsDir() {
if real := resolveGitDir(root); real != "" {
gitDir = resolveGitCommonDir(real)
}
if info, err := os.Stat(gitDir); err == nil && !info.IsDir() {
real := resolveGitDir(root)
if real == "" {
return nil
}
gitDir = resolveGitCommonDir(real)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/sandbox/profile.go` around lines 72 - 75, Update the gitDir
resolution flow around resolveGitDir so an unresolved non-directory .git file
does not retain the literal root/.git fallback; return no carveouts for stale,
malformed, or unreadable gitdir pointers. Preserve the literal fallback only
when .git is absent, and add a regression test covering a stale gitdir: pointer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant