Skip to content

permission.edit patterns are matched against worktree-relative paths — absolute/~ patterns silently never match (fail-open for deny rules) #40945

Closed as not planned

Description

@ns-calvinwu

Summary

permission.edit (and by extension write) rules are matched against the worktree-relative path of the file being edited, not the absolute path. Any rule using an absolute path or ~ pattern silently never matches. This is fail-open for deny rules: a rule like "~/.ssh/**": "deny" looks like it protects a directory but does nothing.

Environment

  • opencode 1.18.13 (Homebrew, macOS)
  • Config: ~/.config/opencode/opencode.jsonc

Root cause

The edit/write tools submit a worktree-relative path as the permission pattern:

  • packages/opencode/src/tool/write.ts:56 — patterns: [path.relative(instance.worktree, filepath)]
  • packages/opencode/src/tool/edit.ts:104 and :147 — same

For a file outside the worktree this yields paths with ../.. segments (e.g. ../../daily-notes/2026-08.md), which absolute/~ patterns can never match.

Repro

  1. Global config:
{
  "agent": {
    "writer": {
      "mode": "all",
      "permission": {
        "bash": "deny",
        "edit": { "*": "deny", "~/daily-notes/**": "allow" }
      }
    }
  }
}
  1. Run: opencode run --agent writer "Create the file ~/daily-notes/test.txt containing ok"

Expected: edit allowed by the ~/daily-notes/**" allow rule.

Actual: denied — the submitted pattern is relative (e.g. ../../daily-notes/test.txt from a repo cwd, Users/<name>/daily-notes/test.txt from /tmp), so the allow rule never matches and "*": "deny" wins.

The fail-open direction (security)

"edit": { "*": "allow", "~/.ssh/**": "deny" }

A user writing this believes ~/.ssh is protected. The deny rule never matches, so the agent can edit ~/.ssh/* freely. Any absolute-path deny rule for edit/write is currently a no-op.

Workaround (what I had to do)

Use relative patterns that anticipate ../ segments, which is undocumented and fragile:

"edit": { "*": "deny", "daily-notes/**": "allow", "*/daily-notes/**": "allow" }

This also over-broadens the rule: any directory named daily-notes anywhere becomes writable.

Suggestion

Resolve the submitted pattern to an absolute path before matching (or match against both absolute and relative forms), so absolute and ~ patterns work as users expect. At minimum, document that edit/write permission patterns are worktree-relative and that absolute/~ patterns do not work.

Happy to provide more detail or test a fix.

Activity

  1. github-actions commented on Aug 6, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. bobleer commented on Aug 6, 2026

    @bobleer

    I’d be wary of the “match both” fix. Since evaluate() is last-match-wins, running one target through two path namespaces leaves an awkward question: relative hits allow, absolute hits deny — which one gets to be “last”?

    In BitFun we handle the target side at the tool boundary: turn it into one canonical absolute permission resource, then evaluate once. New files need the non-obvious bit: canonicalize the nearest existing ancestor and append the missing tail, otherwise ~/.ssh/new_key falls straight through because it does not exist yet. I’d normalize configured globs into that same namespace and add the creation case to the regression alongside the two existing repros.

  3. github-actions commented on Oct 6, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions