anomalyco / anomalyco/opencode

Any read or edit permission rule written as an absolute path (~/.ssh, ~/.aws, /etc/hosts, etc.) silently never matches, including MDM managed rules

Open
#48,049 1 comment 0 reactions 1 assignee View on GitHub

@jlongster is already working on this.

Since Sep 9, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Description
Environment
  • opencode 1.18.29
  • macOS (Apple Silicon), zsh
  • Config delivered via MDM managed preferences (Jamf Pro), ai.opencode.managed domain
  • Resolved plist on device: /Library/Managed Preferences/<user>/ai.opencode.managed.plist (root:wheel, 644)
  • ~/.config/opencode/opencode.json contains only a provider block, no permission key
  • Session started from $HOME (worktree = /Users/<user>), also reproduced from a project directory
Summary

We are piloting the documented managed-settings path (Config > Managed settings > macOS managed preferences) to enforce an org-wide deny list for secrets locations. The profile installs and merges correctly, opencode debug config shows every rule, and the rules are correctly marked non-overridable. But every permission.read and permission.edit rule written with a ~/ or absolute path silently never matches, so the agent can read ~/.ssh, ~/.aws/credentials, etc. without a prompt. Only the **/-prefixed rules in the same block are enforced.

This is fail-open for exactly the rules an enterprise would deploy.

Managed config (relevant portion)
<key>permission</key>
<dict>
  <key>*</key>
  <string>ask</string>
  <key>read</key>
  <dict>
    <key>**/.env</key>              <string>deny</string>
    <key>**/.env.*</key>            <string>deny</string>
    <key>**/secrets/**</key>        <string>deny</string>
    <key>**/credentials.json</key>  <string>deny</string>
    <key>~/.ssh/**</key>            <string>deny</string>
    <key>~/.aws/credentials</key>   <string>deny</string>
    <key>~/.aws/config</key>        <string>deny</string>
    <key>~/.kube/config</key>       <string>deny</string>
    <key>~/.netrc</key>             <string>deny</string>
    <key>~/.docker/config.json</key><string>deny</string>
    <key>~/.npmrc</key>             <string>deny</string>
  </dict>
  <key>edit</key>
  <dict>
    <key>~/.ssh/**</key>            <string>deny</string>
    <key>~/.zshrc</key>             <string>deny</string>
    <key>/etc/hosts</key>           <string>deny</string>
    <key>/etc/sudoers</key>         <string>deny</string>
    <!-- more ~/ and /Library/... entries omitted -->
  </dict>
</dict>
Expected

Steps 4 and 5 are both denied. Per the Permissions docs ("Home Directory Expansion": ~/projects/* -> /Users/username/projects/*), ~/ patterns are supported for permission rules, and the managed-settings docs say these rules "are enforced automatically."

Actual
  • Step 4: read of ~/.ssh/known_hosts and directory listing of ~/.ssh both succeed with no prompt.
  • Step 5: .env and credentials.json reads are correctly blocked. The deny message returned to the model shows the ruleset it evaluated, which includes the expanded absolute rule that should have matched in step 4:
{"permission":"read","pattern":"/Users/<user>/.ssh/**","action":"deny"}
{"permission":"read","pattern":"/Users/<user>/.aws/credentials","action":"deny"}
{"permission":"read","pattern":"**/.env","action":"deny"}
{"permission":"read","pattern":"**/credentials.json","action":"deny"}

So the rule is loaded and expanded, it just never matches the subject.

Root cause (from dev)
  • packages/opencode/src/permission/index.ts expand() rewrites ~/.ssh/** to /Users/<user>/.ssh/** (absolute).
  • packages/opencode/src/tool/read.ts and tool/edit.ts call
    ctx.ask({ permission: "read", patterns: [path.relative(instance.worktree, filepath)] }),
    so the subject is worktree-relative: .ssh/known_hosts when started from $HOME, ../../.ssh/known_hosts from a project.
  • packages/core/src/util/wildcard.ts match() anchors the regex with ^...$.

An anchored absolute pattern can never match a relative subject. **/-prefixed patterns work only because ** happens to absorb the leading ../ segments.

Note this is not limited to out-of-worktree files: with the worktree set to $HOME the subject is .ssh/known_hosts with no ../, and it still does not match.

Impact
  • Every ~/ and absolute-path deny rule under read and edit is silently ignored: ~/.ssh, ~/.aws, ~/.kube, ~/.netrc, ~/.npmrc, ~/.docker, ~/.zshrc, /etc/hosts, /etc/sudoers, /Library/LaunchDaemons, etc.
  • Admins get no signal. opencode debug config shows the rule present and enforced, and there is no warning that the pattern shape can never match.
  • Makes the MDM managed-settings feature unsafe to rely on for secrets protection until fixed.
Plugins

n/a

OpenCode version

1.18.29

Steps to reproduce
Steps to reproduce
  1. Deploy the plist above via MDM (or drop the equivalent permission block into ~/.config/opencode/opencode.json).
  2. Confirm it loaded: opencode debug config shows permission.read["~/.ssh/**"] = "deny".
  3. Start opencode in $HOME (or any directory).
  4. Ask the agent to read ~/.ssh/known_hosts and to list ~/.ssh.
  5. In the same session, ask it to read a .env file and a credentials.json in a temp directory.
Image Image Image
Screenshot and/or share link

No response

Operating System

macOS 26.6.2 (25G83)

Terminal

Apple Terminal

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.