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
@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.manageddomain - Resolved plist on device:
/Library/Managed Preferences/<user>/ai.opencode.managed.plist(root:wheel, 644) ~/.config/opencode/opencode.jsoncontains only a provider block, nopermissionkey- 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_hostsand directory listing of~/.sshboth succeed with no prompt. - Step 5:
.envandcredentials.jsonreads 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.tsexpand()rewrites~/.ssh/**to/Users/<user>/.ssh/**(absolute).packages/opencode/src/tool/read.tsandtool/edit.tscall
ctx.ask({ permission: "read", patterns: [path.relative(instance.worktree, filepath)] }),
so the subject is worktree-relative:.ssh/known_hostswhen started from$HOME,../../.ssh/known_hostsfrom a project.packages/core/src/util/wildcard.tsmatch()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 underreadandeditis silently ignored:~/.ssh,~/.aws,~/.kube,~/.netrc,~/.npmrc,~/.docker,~/.zshrc,/etc/hosts,/etc/sudoers,/Library/LaunchDaemons, etc. - Admins get no signal.
opencode debug configshows 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
- Deploy the plist above via MDM (or drop the equivalent
permissionblock into~/.config/opencode/opencode.json). - Confirm it loaded:
opencode debug configshowspermission.read["~/.ssh/**"] = "deny". - Start opencode in
$HOME(or any directory). - Ask the agent to read
~/.ssh/known_hostsand to list~/.ssh. - In the same session, ask it to read a
.envfile and acredentials.jsonin a temp directory.
Screenshot and/or share link
No response
Operating System
macOS 26.6.2 (25G83)
Terminal
Apple Terminal
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.