fix: reject symlinked hook directories before mutation - #31
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
🟢 Approval recommended
The change is narrowly scoped, adds targeted regression tests for the identified security/escape scenario, and the updated implementation consistently applies workspace-boundary validation before mutating config or permissions.
Pull request overview
This PR hardens hooks install against symlink-based escape paths by validating the .githooks/pre-commit path within the workspace filesystem boundary before any mutation, and by applying executable permissions through a verified file descriptor to avoid unexpected external permission changes.
Changes:
- Add regression coverage ensuring
hooks installrejects symlinked.githooksparents or hook files without mutating Git config or external file permissions. - Update hook installation to use
workspaceLstatfor workspace-boundary validation andopenSync(...O_NOFOLLOW)+fchmodSyncon a verified descriptor for safe permission changes.
File summaries
| File | Description |
|---|---|
| test/hooks.test.ts | Adds regression tests for symlinked parent/hook scenarios and ensures rejection is non-mutating. |
| src/hooks.ts | Enforces workspace-boundary checks for the hook path and applies executable bits via a verified, non-following file descriptor. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Problem
Hook installation rejects a symlinked hook file but follows a symlinked .githooks directory, changing an external file's permissions and configuring Git to execute it.
Solution
Use the existing workspace filesystem boundary before either mutation. Open the validated regular hook without following a final symlink, check its identity, and apply executable permissions through that descriptor. Valid linked worktrees remain supported. Missing hooks keep a distinct diagnostic; missing paths also leave Git configuration unchanged.
Proof
Two new source-CLI parent-symlink regressions failed before the fix: the external hook changed from0600 to0711. The focused suite passes22 tests. The full gate passes165 tests with91.48% aggregate line coverage, unchanged frozen parity, build and installed-tarball smoke. Independent Astra review is clean. Fixtures verify external permissions and both existing/unset Git configuration remain unchanged on rejection. This does not claim isolation from arbitrary concurrent hostile filesystem replacement.