fix: harden path sandboxing with symlink protection, safe defaults, and sensitive file guards#209
Merged
Merged
Conversation
…nd sensitive file guards - Add `Filesystem.containsReal()` with `realpathSync` to prevent symlink escape attacks (same class of bug as Codex GHSA-w5fx-fh39-j5rw and Claude Code CVE-2025-54794) - Add `isAbsolute(rel)` check to `Filesystem.contains()` for Windows cross-drive bypass - Update `Instance.containsPath()` to use symlink-aware `containsReal()` - Add safe permission defaults: deny `rm -rf`, `git push --force`, `git reset --hard`, `DROP DATABASE`, `TRUNCATE` out of the box - Add `Protected.isSensitiveWrite()` to detect writes to `.git/`, `.ssh/`, `.aws/`, `.env*`, credential files even inside the project boundary - Add `assertSensitiveWrite()` guard to write, edit, and apply_patch tools - Remove resolved TODO comments from `file/index.ts` - Update SECURITY.md, permissions docs, and security FAQ with practical guidance - Add 94 tests including 62 e2e tests covering symlink attacks, path traversal, sensitive file detection, and combined attack scenarios Closes #202 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… paths, TOCTOU docs - Fix critical bug: bash deny defaults had `"*": "ask"` LAST which overrode deny rules due to last-match-wins semantics. Moved `"*": "ask"` to first position so deny rules take precedence. - Fix all doc examples with same ordering bug (security-faq.md, permissions.md) - Fix `isSensitiveWrite` to use regex split `/[/\\]/` for cross-platform path handling - Allow per-path "Always" approval for sensitive file writes (reduces approval fatigue) - Document TOCTOU limitation in `containsReal` JSDoc - Add doc clarification about last-match-wins rule ordering with examples - Add tests: bash deny defaults evaluation, user override merge, Windows backslash paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nsitive matching, expanded patterns Fixes from consensus across GPT 5.2, Kimi K2.5, MiniMax M2.5, and GLM-5 reviews: - Add `assertSensitiveWrite(ctx, movePath)` for move destinations in `apply_patch` (CRITICAL: 3 models flagged that moves to `.ssh/`, `.env` bypassed sensitive check) - Add case-insensitive matching on macOS/Windows for sensitive dirs and files (`.GIT/config`, `.SSH/id_rsa` now correctly detected on case-insensitive FS) - Expand `SENSITIVE_FILES` with `.htpasswd`, `.pgpass` - Add `SENSITIVE_EXTENSIONS` for private keys: `.pem`, `.key`, `.p12`, `.pfx` - Add tests: case-insensitive matching, certificate extensions, credential files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on divergence
Gemini 3.1 Pro found that `realpathSync` and the OS kernel disagree on
`symlink/../file.txt`:
- `realpathSync("project/link/..")` → `project/` (lexical normalization)
- `writeFile("project/link/../f")` → writes to parent of symlink TARGET (kernel)
This means `containsReal` would approve a write that the OS places OUTSIDE
the project boundary. The fix rejects any unresolved path containing `..`
segments, since their behavior through symlinks is fundamentally unpredictable
at the application level.
Also adds `.github` to `SENSITIVE_DIRS` (workflow injection vector).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… from sensitive dirs UX impact evaluation of each change: 1. **Bash defaults softened**: Changed destructive shell/git commands from `deny` (blocked silently) to `ask` (prompted). `rm -rf ./build` and `git push --force` after rebase are legitimate workflows — blocking them without a prompt is poor UX. Database DDL (`DROP DATABASE`, `TRUNCATE`) stays `deny` since it's almost never intentional in agent context. 2. **Removed `.github` from sensitive dirs**: Editing CI/CD workflows is a core use case. Prompting on every workflow edit would cause severe approval fatigue. 3. **Expanded FAQ**: Added "Why am I being prompted to edit .env files?" with table of protected patterns and guidance on "Allow always". Added "What commands are blocked or prompted by default?" with clear table showing which commands prompt vs block. Reordered best practices to lead with "work on a branch" (most effective, least friction). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…icky DDL deny rules Fixes two issues flagged by Sentry automated review: 1. `assertSensitiveWrite` now uses `permission: "sensitive_write"` instead of `"edit"`, preventing agents with `edit: "allow"` from silently bypassing sensitive file prompts for `.env`, `.ssh/`, `.aws/`, etc. 2. Database DDL deny rules (`DROP DATABASE`, `DROP SCHEMA`, `TRUNCATE`) are now merged as a `safetyDenials` layer AFTER user/agent configs via `userWithSafety`. This ensures wildcard `bash: "allow"` in agent configs cannot override these denials (last-match-wins). Users who need to override must use specific patterns like `"DROP DATABASE test_db": "allow"`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Code ReviewThis repository is configured for manual code reviews. Comment |
… mismatches - Update `build agent has correct default properties` test: bash now defaults to "ask" (was "allow") per safety defaults - Fix `config/config.ts` marker mismatch: add missing `altimate_change end` after auto-enhance prompt config (9 starts vs 8 ends → 9/9) - Fix `tool/skill.ts` marker mismatch: remove orphaned `altimate_change end` at EOF (6 starts vs 7 ends → 6/6) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
…patterns - Resolve merge conflicts in skill.ts and commit.txt - Add lowercase DDL deny patterns (drop database, truncate) to both defaults and safetyDenials — Wildcard.match is case-sensitive on Linux/macOS - Add tests verifying lowercase DDL commands are denied Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
Author
|
@claude review |
|
Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit in Settings → Usage. Once credits are available, comment |
Contributor
Author
|
@claude review |
Contributor
✅ Tests — All PassedTypeScript — passedcc @anandgupta42 |
anandgupta42
added a commit
that referenced
this pull request
Jun 26, 2026
… (shipped security regression) Found by the confidence audit (scoping the SHIPPED surface). Commit #209 (0449a8a) hardened the project-boundary check to be symlink-aware via Filesystem.containsReal (realpathSync) to stop symlink-escape attacks (CVE-class GHSA-w5fx-fh39-j5rw / CVE-2025-54794). The v1.17.9 merge introduced a NEW lexical copy of containsPath in project/instance-context.ts (FSUtil.contains, no symlink resolution) and rewired tool/external-directory.ts — which gates grep/glob/ls and the external_directory permission — to that copy. Instance.containsPath (project/instance.ts) kept the protection, so the regression was invisible: the #209 security-e2e tests only exercise Instance.containsPath, not this copy, so CI stayed green. Impact (shipped): an in-project symlink to an external dir (e.g. `proj/x -> ~/.ssh`) passes the lexical check, so grep/glob search OUTSIDE the project WITHOUT the external_directory prompt. Confirmed: `rg` with a symlink-dir cwd reads the external target. Fix: instance-context.containsPath now uses Filesystem.containsReal (resolves real paths, walks to nearest existing ancestor for write targets, rejects ".."), matching Instance.containsPath. New regression test exercises THIS copy with a real on-disk symlink (mutation-checked: fails against lexical contains). typecheck 13/13; security-e2e + new test 76 pass; marker 160; branding clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
anandgupta42
added a commit
that referenced
this pull request
Jun 26, 2026
… security regression #2) Found by the differential audit (main vs merge). #209 added assertSensitiveWrite — a SEPARATE "sensitive_write" permission prompt before writing/editing/moving into credential/VCS/security locations (.git/, .ssh/, .aws/, .env*, private keys, ...), even inside the project boundary, so an agent with `edit: "allow"` (the permissive builder default) cannot silently bypass it. The v1.17.9 merge dropped the wrapper AND all 4 call sites in write/edit/apply_patch; Protected.isSensitiveWrite survived as dead code. Impact (HIGH): the agent could overwrite a project-local .env, .git/hooks/*, or a checked-in private key with NO prompt — an exfiltration/persistence vector. Why it slipped past CI: the merge inlined a PRIVATE copy of assertSensitiveWrite into test/file/security-e2e.test.ts, so the suite compiled + passed green while testing the copy, never the (absent) production path. Fix: - Restore assertSensitiveWriteEffect (Effect) + a Promise wrapper in tool/external-directory.ts (Effect port of main's; underlying match is the surviving Protected.isSensitiveWrite). - Re-wire `yield* assertSensitiveWriteEffect(ctx, <path>)` into write.ts, edit.ts, and apply_patch.ts (add + move destination) — 4 call sites. - Un-neuter security-e2e.test.ts: import the production guard instead of a local copy. - Add a PRESENCE guard (fork-feature-guards.test.ts) asserting the wrapper is defined, "sensitive_write" is the permission key, and all three tools call the guard — so a future merge that drops the WIRING (not just the function) goes red. The differential audit's bounded accounting: this is the ONLY remaining shipped #209 drop; all other fork security behaviors (containReal boundary [fixed], bash deny-defaults, auth, redaction, OAuth escaping) survive. typecheck 13/13; security-e2e 73 + tool suites 112 pass; marker 160; branding clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
anandgupta42
added a commit
that referenced
this pull request
Jun 26, 2026
…nfidence audit - Restore packages/opencode/test/file/path-traversal.test.ts (15 Filesystem.contains security cases the v1.17.9 merge dropped); all 15 pass against the merged code. It was the ONLY dropped fork security test file (verified via comm of main vs HEAD test trees). - Add .github/meta/night-run/RELEASE-CONFIDENCE-2026-06-26.md: the consolidated confidence map — every issue found this session → fix → gate, the scope (shipped vs v2-core/unshipped), the 2 shipped #209 security regressions the differential found + why they slipped past Kilo/codex/CI, and the recommended permanent drop-detection gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
anandgupta42
added a commit
that referenced
this pull request
Jun 26, 2026
…m) — shipped regression #3 Found by the differential audit (session/agent/prompt slice). #209-era code loaded the ~11 builtin DE skills (dbt-analyze, dbt-pr-review, cost-report, data-parity, data-viz, dbt-develop/docs/test, dbt-schema-verify, altimate-setup, ...) into the live skill registry via `~/.altimate/builtin/` (FS, postinstall) with a binary-embedded OPENCODE_BUILTIN_SKILLS fallback. The v1.17.9 merge REWROTE skill/index.ts into the live @/skill module but only registers `customize-opencode` + disk discovery — it never reads the embedded blob or ~/.altimate/builtin. The loader survived only in the now-orphaned skill.ts. Impact (HIGH): on Homebrew/Docker/npm-without-postinstall installs, all builtin DE skills silently vanished from <available_skills>, the Skill tool, and auto-load — core fork value, no error. The build still embeds them. Why it slipped past CI: build-integrity.test.ts asserted skill.ts (the ORPHAN) loads the blob, staying green while the live module didn't. Fix: restore both loaders in skill/index.ts (FS-first for @references, embedded fallback via gray-matter), registered before disk discovery so user-disk skills still override. Retarget the build-integrity test at the LIVE module (skill/index.ts) so the drop can't recur unseen. typecheck 13/13; skill + build-integrity suites 95 pass; marker 160. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 tasks
anandgupta42
added a commit
that referenced
this pull request
Jul 2, 2026
Release-evaluation board (safety perspective) found the #209 sensitive-write guard was neutralized at runtime. The guard calls `ctx.ask({ permission: "sensitive_write" })` before writing a sensitive target (.env / .ssh / .git / ...), but `sensitive_write` was never configured, so it fell through to the builder's `"*": "allow"` default and silently auto-approved — defeating the guard the fork deliberately restored. Pre-existing (present on main too), not a merge regression, but a real bypass. Default `sensitive_write: "ask"` in the agent defaults so sensitive writes prompt (users can still override to "allow"). Write-restricted agents (analyst/reviewer/plan) keep their later `"*": "deny"`. Regression test asserts builder=ask, analyst/reviewer=deny.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Hardens the security posture of Altimate Code's path sandboxing and permission system, addressing the same class of vulnerabilities that led to CVEs in Codex (GHSA-w5fx-fh39-j5rw, CVSS 8.6) and Claude Code (CVE-2025-54794, CVSS 7.7). Reviewed by 6 AI models (Claude, GPT 5.2 Codex, Gemini 3.1 Pro, Kimi K2.5, MiniMax M2.5, GLM-5).
Five areas of improvement:
Symlink escape protection —
Filesystem.containsReal()resolves symlinks viarealpathSyncbefore checking containment. Rejects paths with unresolved..segments to prevent divergence betweenrealpathSync(lexical..normalization) and kernel behavior (follows symlink then applies..). Also addsisAbsolute(rel)check for Windows cross-drive bypass.Safe permission defaults — Destructive shell/git commands (
rm -rf,git push --force,git reset --hard) now prompt before execution instead of running silently. Database DDL (DROP DATABASE,DROP SCHEMA,TRUNCATE) is blocked entirely. Users can override in config.Sensitive file guards — New
assertSensitiveWrite()check on write, edit, apply_patch, and move operations that prompts before modifying.env*,.ssh/,.aws/,.git/, credential files, and private keys (.pem,.key) — even inside the project boundary. Case-insensitive on macOS/Windows.Critical fix:
realpathSyncvs kernel divergence — Gemini 3.1 Pro discovered thatrealpathSync("project/symlink/..")returnsproject/(lexical) while the kernel writes to the parent of the symlink target (outside project). Fixed by rejecting any unresolved path containing..segments.Documentation — Updated SECURITY.md, permissions docs with 3 recommended configs and rule ordering guidance, and security FAQ with new sections on sensitive file prompts, default command protections, and best practices.
UX Impact
..segment rejection..— only catches crafted pathsaskrm -rf,git push --force, etc. now prompt instead of running silently. "Allow always" available.denyDROP DATABASE,TRUNCATEblocked entirely. Override in config if needed..env,.ssh/,.aws/, credential files prompts. "Allow always" per-file for the session.Key UX decisions:
askinstead ofdenyfor shell/git commands — Blockingrm -rf ./buildorgit push --forceafter rebase would break common workflows. Prompting lets users approve safely..envfile per session, not on every edit..githubNOT in sensitive dirs — CI/CD workflow editing is a core use case; prompting every time would cause approval fatigue.DROP DATABASE,DROP SCHEMA,TRUNCATEare almost never intentional in agent context.Type of change
Issue for this PR
Closes #202
How did you verify your code works?
symlink/../kernel divergence attackFile.read/File.list.git,.ssh,.aws,.env*, credentials, private keys.pem,.key,.p12,.pfx)assertSensitiveWriteprompting for sensitive files, no-op for normal filesChecklist
🤖 Generated with Claude Code