fix(sandbox): add platform-specific write lock around grant state file (#752)#755
fix(sandbox): add platform-specific write lock around grant state file (#752)#755PierrunoYT wants to merge 1 commit into
Conversation
Gitlawb#752) Add grants_lock_unix.go and grants_lock_windows.go with a lockStateFile method that serializes access to the persisted grant state across processes. Wire the lock into Grant, GrantCommandPrefix, Revoke, RevokePath, and Clear. Also add .gitattributes to force LF line endings for Go files so gofmt checks pass on Windows. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses a sandbox security race where concurrent zero processes could overwrite each other’s persisted grants file and inadvertently resurrect a revoked permission. It does so by adding an inter-process, file-level write lock around the full read-modify-write transaction for the grants JSON.
Changes:
- Add platform-specific file locking helpers (Unix
flock, WindowsLockFileEx/UnlockFileEx) and a timed retry loop to acquire the lock. - Serialize all persisted grant mutations by acquiring the lock in
Grant,GrantCommandPrefix,Revoke,RevokePath, andClearbefore reading/writing state. - Add a regression test to ensure concurrent stores block while the lock is held, plus
.gitattributesto enforce LF endings for Go/module files.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| internal/sandbox/grants.go | Adds a lock acquisition step (with timeout/retry) around grant state read-modify-write operations. |
| internal/sandbox/grants_test.go | Adds a concurrency regression test validating that a second store blocks while the lock is held. |
| internal/sandbox/grants_lock_unix.go | Implements non-blocking exclusive lock/unlock using syscall.Flock for non-Windows builds. |
| internal/sandbox/grants_lock_windows.go | Implements non-blocking exclusive lock/unlock using Win32 LockFileEx/UnlockFileEx. |
| .gitattributes | Forces LF line endings for Go/module/sum files to keep formatting checks consistent on Windows. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
WalkthroughChangesSandbox grant locking
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Vasanthdev2004
left a comment
There was a problem hiding this comment.
Approve. This closes the #752 lost-update/resurrection hole cleanly, and the Windows path in particular is done right.
Checked on head a60e60c:
- The full read-modify-write is under the inter-process lock: every mutator (Grant, GrantCommandPrefix, Revoke, RevokePath, Clear) does lockStateFile then readState then mutate then writeState, with the read inside the lock, so two processes cannot both read-then-write and clobber a revoke.
- writeState is atomic (temp file in the same dir plus os.Rename), so the unlocked reader / permission-check path never sees a torn file, only the complete old or new one.
- Fail-closed: on a lock error or the 5s timeout the mutators return the error and abort, and the CLI surfaces a Revoke error instead of printing a success line, so a dropped revoke cannot be mistaken for a completed one.
- Windows LockFileEx (grants_lock_windows.go): success is keyed off the BOOL return value (
if result != 0), not off the err from LazyProc.Call, which is always the last-error. That is the right way to do it and sidesteps the trap where a failed lock reads as success and silently disables the protection. Flags are LOCKFILE_EXCLUSIVE_LOCK|LOCKFILE_FAIL_IMMEDIATELY with a zeroed OVERLAPPED, which is correct. - Built and ran internal/sandbox locally: gofmt, vet and build clean, grant tests pass including the cross-store serialization test that exercises the real file lock. CI green.
One optional note, not blocking: grants_lock_unix.go uses //go:build !windows, which nominally includes plan9 and js where syscall.Flock is undefined. It matches the existing convention in the repo (permissions_lock_unix.go and friends), and Zero only ships linux/darwin/windows, so it builds fine for every real target. Flagging only because I hit the same tag question on another lock PR; take it or leave it.
LGTM.
jatmn
left a comment
There was a problem hiding this comment.
I found issues that need to be addressed before this is ready.
Findings
- [P2] Rebase this branch onto the current
mainbefore merging
internal/sandbox/grants.go
The PR head is based once4a996, whilemainis now96859c9; GitHub reports the PR asCONFLICTING/DIRTY, and the merge has content conflicts in both this file andgrants_test.go. Resolve the conflicts against the current state model and rerun the grant-state review/tests rather than selecting either side wholesale. In particular, retain main's schema-v3 and legacy/policy migration behavior, and extend the new interprocess transaction toConsumeMigrationNoticeplus the migrations that currently write throughreadState; otherwise the rebase would either regress existing grants or leave additional lost-update paths.
Summary
Closes #752
Test plan
Generated with Devin
Summary by CodeRabbit
Bug Fixes
Tests