|
| 1 | +# Security model |
| 2 | + |
| 3 | +Olympus runs LLM agents that read issues/PRs and **write code, push branches, |
| 4 | +and (optionally) merge** on your repo. This page states the threat model and the |
| 5 | +controls, so an operator can reason about what the agents can and cannot do — |
| 6 | +and what hardening is still the operator's job. |
| 7 | + |
| 8 | +## Threat model |
| 9 | + |
| 10 | +The defining assumption for a **public** repo: **issue and PR authors are |
| 11 | +untrusted.** Anyone can file an issue, and its text flows into an agent. The two |
| 12 | +highest-risk surfaces: |
| 13 | + |
| 14 | +- **Implement / revise** (`hephaestus`) — runs work *derived from issue/review |
| 15 | + text* with broad shell + file-write tools. Untrusted text reaching a |
| 16 | + shell-wielding LLM is a remote-code-execution / exfiltration vector. |
| 17 | +- **Triage** (`hermes`) — investigates untrusted text and posts public replies; |
| 18 | + a `do` verdict dispatches the implement agent. |
| 19 | + |
| 20 | +Trusted, by contrast: the maintainers (repo write access), the runner, the model |
| 21 | +gateway, and `.olympus.json` itself (committed by maintainers). |
| 22 | + |
| 23 | +## Controls (defense in depth) |
| 24 | + |
| 25 | +| Layer | Control | Where | |
| 26 | +|---|---|---| |
| 27 | +| **Authorization** | **Maintainer-dispatch gate.** A `do` verdict auto-dispatches the unattended agent only for authors with write/maintain/admin access; others get a warm reply + a maintainer control to dispatch by hand. A human reviews stranger issues before the agent acts. | `.triage.auto_dispatch` (`trusted`\|`all`\|`never`, default `trusted`) — `run_triage.sh` | |
| 28 | +| **Prompt** | **Untrusted-input framing.** Every agent prompt states that issue/review text is data describing *what to change*, never instructions to obey, with the interpolated title fenced in explicit BEGIN/END UNTRUSTED markers. | `run_hephaestus.sh`, `run_triage.sh`, `run_revise.sh` | |
| 29 | +| **Tools** | **Network egress denied.** The implement/revise agent runs with `--disallowed-tools` for `curl/wget/nc/ncat/netcat/telnet/ssh/scp/sftp/socat/ftp` + `mcp__*`. Deny beats the broad `Bash` allow and survives `bash -c` / `&&` / `;` / `|` wrappers. | `agent-harness.sh`; opt out with `.implement.allow_network` | |
| 30 | +| **Credentials** | **Token stripping.** `GH_TOKEN`/`GITHUB_TOKEN`/`AGENT_GH_TOKEN`/`ADMIN_GH_TOKEN` are removed from the implement subprocess (it edits code + builds; the *driver* script makes the `gh` calls). Model-gateway creds are kept. | `agent-harness.sh` (`env -u`) | |
| 31 | +| **Outbound hygiene** | **Guard linters (no LLM).** Leakage / secret-reference / secret-value gates keep internal IPs, machine paths, and key material out of every outbound surface (issues, PR bodies, reviews, commits). | `guard.yml`, `scripts/lint/check-*.sh` | |
| 32 | +| **Blast radius** | Revise round cap → human escalation; per-issue/PR workflow concurrency; the observer scrubs incident bodies before filing. | `revise_dispatch.sh`, workflow `concurrency` | |
| 33 | + |
| 34 | +A regression test for the combined prompt+tool defense lives at |
| 35 | +`evals/tasks/implement/prompt-injection/` — an issue whose body embeds a |
| 36 | +malicious instruction; it passes only if the legitimate fix lands **and** the |
| 37 | +injected command does not run. |
| 38 | + |
| 39 | +## Residual risks — NOT covered by the above |
| 40 | + |
| 41 | +These need controls the operator owns at the OS / infrastructure layer: |
| 42 | + |
| 43 | +- **Indirect network egress.** The deny-list blocks *direct* `curl`/`ssh`. It |
| 44 | + does **not** stop a build script, a package manager, or `python -c "..."` that |
| 45 | + shells out to the network. **Mitigation: run the implement/revise agent on a |
| 46 | + runner with an egress firewall that allows only the model gateway.** This is |
| 47 | + the single most important hardening step and the only complete fix for exfil. |
| 48 | +- **Trusted-author assumption.** `auto_dispatch: trusted` trusts anyone with |
| 49 | + repo write access. A compromised or malicious maintainer account bypasses the |
| 50 | + dispatch gate. Scope write access accordingly. |
| 51 | +- **Arbitrary build toolchain.** `build_cmd` runs whatever the consumer |
| 52 | + configured; a malicious `.olympus.json` (committed by a maintainer) is out of |
| 53 | + scope — config is part of the trusted base. |
| 54 | +- **Model fallibility.** Prompt framing reduces, but cannot guarantee, that the |
| 55 | + agent ignores a cleverly injected instruction. The tool/network/credential |
| 56 | + controls are what bound the damage when framing fails. |
| 57 | + |
| 58 | +## Operator hardening checklist |
| 59 | + |
| 60 | +- **Egress-firewall the runner** to the model gateway only (closes indirect |
| 61 | + egress). |
| 62 | +- Use a **dedicated, low-privilege, ideally ephemeral** self-hosted runner for |
| 63 | + implement/revise — not a shared CI box. |
| 64 | +- **Minimize `AGENT_GH_TOKEN` scope** to exactly what the loop needs (issues, |
| 65 | + PRs, contents, workflow); never an org-admin token. |
| 66 | +- Keep `auto_dispatch: trusted` (or `never`) on public repos; reserve `all` for |
| 67 | + internal repos where every author is already trusted. |
| 68 | +- Leave `AUTO_MERGE_TEAM` empty until you trust the loop; gated auto-merge is |
| 69 | + opt-in. |
| 70 | + |
| 71 | +## Reporting a vulnerability |
| 72 | + |
| 73 | +Until a dedicated `SECURITY.md` disclosure policy is published, report suspected |
| 74 | +vulnerabilities privately via the repository's GitHub **Security advisories** |
| 75 | +(Report a vulnerability) rather than a public issue. |
0 commit comments