Skip to content

permissions: match absolute rules for paths outside the project root - #175

Draft
iceteaSA wants to merge 1 commit into
cortexkit:mainfrom
iceteaSA:fix/permission-path-relativization
Draft

permissions: match absolute rules for paths outside the project root#175
iceteaSA wants to merge 1 commit into
cortexkit:mainfrom
iceteaSA:fix/permission-path-relativization

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Aug 2, 2026

Copy link
Copy Markdown

Permission patterns for edit/write are always relativized against the project root, so any rule written as an absolute path outside that root can never match.

With this config:

"permission": { "edit": { "*": "deny", "/tmp/**": "allow" } }

a write to /tmp/report.md produces the pattern ../../../../tmp/report.md. /tmp/** doesn't match it, "*": "deny" wins, and the write is denied.

It fails in the dangerous direction too. Reverse the rules:

"permission": { "edit": { "*": "allow", "/tmp/**": "deny" } }

The deny rule is equally unmatchable, so the write is allowed. An absolute deny rule written to protect a path silently protects nothing.

Since AFT hoists write/edit/apply_patch, this is the layer that decides — opencode's own permission handling isn't reached for those tools.

Change

New permissionPath helper in tools/permissions.ts:

  • target inside the project root → relative path (unchanged)
  • target outside → absolute path
  • project root is / → absolute

resolveRelativePattern, resolveRelativePatternFromAbsolute, and resolveRelativePatterns now delegate to it, so imports.ts, refactoring.ts, safety.ts, and ast.ts inherit the fix without changes. The two inline path.relative sites in hoisted.ts (write, edit) call it directly.

apply_patch needed more than the shared helper: its patterns come from the Rust side, where build_preview_response emits affected_rel_paths alongside affected_paths. The TS side was reading the relative array. It now prefers the absolute one and falls back to the relative when it's empty, normalizing both through permissionPath.

Note on the helper names

resolveRelativePattern and resolveRelativePatternFromAbsolute can now return absolute paths, so the names are stale. Left alone here to keep the diff focused — happy to rename in this PR or a follow-up, whichever you prefer.

Tests

permissions-effect.test.ts drives the real tool path (hoistedTools(ctx).write.execute(...)), not the helper in isolation, and covers write and apply_patch separately. Reverting permissionPath to the old one-liner fails them with Expected: "/tmp/x" / Received: "../../tmp/x".

18 pass in that file; 12 pass across registration-parity and tool-surface-transport-invariant; bun run build clean. Full plugin suite matches baseline (2 pre-existing format_on_edit failures, both environmental — formatters installed locally where those tests expect absence).

Preview and execution still receive identical raw args — the pattern only reaches the host permission ask, never the Rust side, so plugin and server can't disagree about the path.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fix permission pattern handling so absolute rules outside the project root match correctly for write, edit, and apply_patch, preventing unsafe allows and unintended denies.

  • Bug Fixes
    • Added permissionPath to return relative paths for in-project targets and absolute paths for outside-root or / worktrees.
    • Updated hoisted write, edit, and apply_patch to use permissionPath; apply_patch now prefers affected_paths and falls back to affected_rel_paths, then normalizes.
    • Permission asks now receive the correct pattern (e.g., /tmp/** will match /tmp/x).
    • Added tests covering hoisted tools and permissionPath.

Written for commit 3d2b566. Summary will update on new commits.

Review in cubic

@iceteaSA

iceteaSA commented Aug 2, 2026

Copy link
Copy Markdown
Author

Cross-reference: opencode core has the same bidirectional bug in its own permission matching, fixed upstream in anomalyco/opencode#40149 (their exhibit: {"*":"allow","/etc/**":"deny"} fails to match ../../../../etc/passwd).

That fix only covers opencode-native tools. Since AFT hoists write/edit/apply_patch, those paths never reach it — which is what this PR covers.

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