Locks agent behavior to the agents repo so a caller repo can no longer change it — the isolation problem raised for the shared workflow.
Threat: the agent runs inside the caller repo's checkout, and opencode auto-discovers project-level config from the working tree (opencode.json/.opencode/ → provider/model/permission/mcp/plugins, and AGENTS.md/CLAUDE.md → instructions). opencode has no flag to disable this. So any caller repo could swap the model, unlock skills/permissions, register MCP servers or plugins (arbitrary code execution on the runner, which holds ANTHROPIC_API_KEY/AGENT_TOKEN/TOKEN_*/SSH_PRIV_KEY), or rewrite the rules. The trigger gate only restricts who triggers, not what repo content defines behavior.
Changes
run-agent.sh — quarantine: before opencode runs, move caller-controlled opencode.json, opencode.jsonc, .opencode/, AGENTS.md, CLAUDE.md aside; restore on EXIT (trap). Verified: the committed tree / PR diff is unchanged even when the agent commits mid-run (restore + the existing publish commit net out to a clean diff).
run-agent.sh — authoritative rules: dev-agent golden rules now come from scripts/agent-rules.md (platform-controlled), injected into the prompt, instead of "read the caller's AGENTS.md and follow it exactly."
SECURITY.md (new): documents the isolation model + the requirement that callers pin agent.yml@main.
Validation
bash -n clean.
Simulated: malicious opencode.json/.opencode/plugins/AGENTS.md are absent during the run while real source stays editable; tree fully restored after.
Simulated the git path: agent commits mid-run (recording the config as deleted) → after restore + publish commit, git diff base..branch shows only the real change, config intact.
Reviewer notes / trade-off
By design, an agent cannot durably edit these quarantined files during a run (change them via a human PR). This intentionally removes a caller repo's ability to self-configure the agent (e.g. a repo-local opencode.json permission allowlist) — that control now belongs centrally to this repo.
## Summary
Locks agent behavior to the `agents` repo so a **caller repo can no longer change it** — the isolation problem raised for the shared workflow.
**Threat:** the agent runs inside the caller repo's checkout, and opencode auto-discovers project-level config from the working tree (`opencode.json`/`.opencode/` → provider/model/permission/mcp/**plugins**, and `AGENTS.md`/`CLAUDE.md` → instructions). opencode has **no flag to disable** this. So any caller repo could swap the model, unlock skills/permissions, register MCP servers or plugins (**arbitrary code execution on the runner**, which holds `ANTHROPIC_API_KEY`/`AGENT_TOKEN`/`TOKEN_*`/`SSH_PRIV_KEY`), or rewrite the rules. The trigger gate only restricts *who* triggers, not *what repo content defines behavior*.
## Changes
- **`run-agent.sh` — quarantine:** before opencode runs, move caller-controlled `opencode.json`, `opencode.jsonc`, `.opencode/`, `AGENTS.md`, `CLAUDE.md` aside; restore on `EXIT` (trap). Verified: the committed tree / PR diff is unchanged even when the agent commits mid-run (restore + the existing publish commit net out to a clean diff).
- **`run-agent.sh` — authoritative rules:** dev-agent golden rules now come from `scripts/agent-rules.md` (platform-controlled), injected into the prompt, instead of *"read the caller's AGENTS.md and follow it exactly."*
- **`scripts/agent-rules.md`** (new): repo-agnostic authoritative rules.
- **`SECURITY.md`** (new): documents the isolation model + the requirement that callers pin `agent.yml@main`.
## Validation
- `bash -n` clean.
- Simulated: malicious `opencode.json`/`.opencode/plugins`/`AGENTS.md` are absent during the run while real source stays editable; tree fully restored after.
- Simulated the git path: agent commits mid-run (recording the config as deleted) → after restore + publish commit, `git diff base..branch` shows **only the real change**, config intact.
## Reviewer notes / trade-off
By design, an agent cannot durably edit these quarantined files **during a run** (change them via a human PR). This intentionally removes a caller repo's ability to self-configure the agent (e.g. a repo-local `opencode.json` permission allowlist) — that control now belongs centrally to this repo.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The agent runs inside the CALLER repo's checkout and opencode auto-discovers project-level config
from the working tree (opencode.json / .opencode/ for provider/model/permission/mcp/plugins, and
AGENTS.md/CLAUDE.md for instructions). opencode has no flag to disable that discovery, so any caller
repo could swap the model, unlock skills/permissions, register MCP servers or plugins (arbitrary
code execution on the runner, which holds ANTHROPIC_API_KEY/AGENT_TOKEN/TOKEN_*/SSH_PRIV_KEY), or
rewrite the rules the agent follows. The trigger gate only restricts who triggers, not what repo
content defines behavior.
- run-agent.sh: quarantine caller-controlled opencode.json/opencode.jsonc/.opencode/AGENTS.md/CLAUDE.md
for the duration of each run (moved aside, restored on EXIT via trap). Restore keeps the committed
tree/PR diff unchanged even when the agent commits mid-run.
- run-agent.sh: dev-agent golden rules now come from scripts/agent-rules.md (platform-authoritative),
injected into the prompt, instead of "read the caller's AGENTS.md and follow it exactly".
- scripts/agent-rules.md: new repo-agnostic authoritative rules.
- SECURITY.md: documents the isolation model and the requirement that callers pin agent.yml@main.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fully hiding the caller's AGENTS.md dropped repo-specific operational knowledge agents need
(e.g. homelab's migration mechanism: deleting a service requires a matching migrations/*.sh, else
the old service keeps running in prod). Separate the two concerns:
- Platform golden rules stay authoritative (scripts/agent-rules.md), un-overridable.
- The caller's AGENTS.md / CLAUDE.md text is captured before quarantine and injected into the prompt
as explicitly SUBORDINATE context — usable for repo mechanics, but unable to change behavior,
grant permissions, or override the rules.
- opencode.json / .opencode remain fully blocked (config + RCE), never re-injected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update after checking homelab: it has noopencode.json/.opencode/ (so config quarantine costs it nothing), but its AGENTS.md carries repo-specific ops knowledge agents need — the GitOps deploy model and the migrations rule (delete a service → add a migrations/*.shdocker service rm, or it keeps running in prod).
Fully hiding it would have regressed agent quality, so I refined the fix: platform golden rules stay authoritative (agent-rules.md), but the caller AGENTS.md/CLAUDE.md text is now injected as explicitly subordinate repo notes (usable for mechanics, cannot change behavior/permissions/rules). opencode.json/.opencode/ remain fully blocked. Verified homelab's migration guidance still reaches the agent.
Update after checking `homelab`: it has **no** `opencode.json`/`.opencode/` (so config quarantine costs it nothing), but its `AGENTS.md` carries repo-specific ops knowledge agents need — the GitOps deploy model and the migrations rule (delete a service → add a `migrations/*.sh` `docker service rm`, or it keeps running in prod).
Fully hiding it would have regressed agent quality, so I refined the fix: platform golden rules stay authoritative (`agent-rules.md`), but the caller `AGENTS.md`/`CLAUDE.md` text is now injected as explicitly **subordinate** repo notes (usable for mechanics, cannot change behavior/permissions/rules). `opencode.json`/`.opencode/` remain fully blocked. Verified homelab's migration guidance still reaches the agent.
Closing — abandoning the opencode.json quarantine approach. Decision: a caller repo's opencode.jsonpermission block is the intended per-repo control (e.g. homelab's read-only node1 ssh allowlist) and must be respected, not quarantined. Repos are single-owner and trusted. The real bug the homelab test surfaced was routing: the agent roster omits skills, so @pm can't route by capability. Fixing that instead.
Closing — abandoning the opencode.json quarantine approach. Decision: a caller repo's `opencode.json` **permission** block is the intended per-repo control (e.g. homelab's read-only node1 ssh allowlist) and must be respected, not quarantined. Repos are single-owner and trusted. The real bug the homelab test surfaced was routing: the agent roster omits skills, so @pm can't route by capability. Fixing that instead.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Locks agent behavior to the
agentsrepo so a caller repo can no longer change it — the isolation problem raised for the shared workflow.Threat: the agent runs inside the caller repo's checkout, and opencode auto-discovers project-level config from the working tree (
opencode.json/.opencode/→ provider/model/permission/mcp/plugins, andAGENTS.md/CLAUDE.md→ instructions). opencode has no flag to disable this. So any caller repo could swap the model, unlock skills/permissions, register MCP servers or plugins (arbitrary code execution on the runner, which holdsANTHROPIC_API_KEY/AGENT_TOKEN/TOKEN_*/SSH_PRIV_KEY), or rewrite the rules. The trigger gate only restricts who triggers, not what repo content defines behavior.Changes
run-agent.sh— quarantine: before opencode runs, move caller-controlledopencode.json,opencode.jsonc,.opencode/,AGENTS.md,CLAUDE.mdaside; restore onEXIT(trap). Verified: the committed tree / PR diff is unchanged even when the agent commits mid-run (restore + the existing publish commit net out to a clean diff).run-agent.sh— authoritative rules: dev-agent golden rules now come fromscripts/agent-rules.md(platform-controlled), injected into the prompt, instead of "read the caller's AGENTS.md and follow it exactly."scripts/agent-rules.md(new): repo-agnostic authoritative rules.SECURITY.md(new): documents the isolation model + the requirement that callers pinagent.yml@main.Validation
bash -nclean.opencode.json/.opencode/plugins/AGENTS.mdare absent during the run while real source stays editable; tree fully restored after.git diff base..branchshows only the real change, config intact.Reviewer notes / trade-off
By design, an agent cannot durably edit these quarantined files during a run (change them via a human PR). This intentionally removes a caller repo's ability to self-configure the agent (e.g. a repo-local
opencode.jsonpermission allowlist) — that control now belongs centrally to this repo.🤖 Generated with Claude Code
Update after checking
homelab: it has noopencode.json/.opencode/(so config quarantine costs it nothing), but itsAGENTS.mdcarries repo-specific ops knowledge agents need — the GitOps deploy model and the migrations rule (delete a service → add amigrations/*.shdocker service rm, or it keeps running in prod).Fully hiding it would have regressed agent quality, so I refined the fix: platform golden rules stay authoritative (
agent-rules.md), but the callerAGENTS.md/CLAUDE.mdtext is now injected as explicitly subordinate repo notes (usable for mechanics, cannot change behavior/permissions/rules).opencode.json/.opencode/remain fully blocked. Verified homelab's migration guidance still reaches the agent.Closing — abandoning the opencode.json quarantine approach. Decision: a caller repo's
opencode.jsonpermission block is the intended per-repo control (e.g. homelab's read-only node1 ssh allowlist) and must be respected, not quarantined. Repos are single-owner and trusted. The real bug the homelab test surfaced was routing: the agent roster omits skills, so @pm can't route by capability. Fixing that instead.Pull request closed