Enforce `[security]` declarations at runtime (sandbox-exec / bwrap / firejail)
- Dominant language
- Rust
- Stars
- 3
- Forks
- 1
- Avg merge
- 4h 50m
- Merged PRs (30d)
- 1
Description
`agent.toml` supports a `[security]` block declaring the agent's intended blast radius — see [`docs/reference/agent-spec.md`](docs/reference/agent-spec.md#security--schema-only-in-v0):
```toml
[security]
allowed_commands = ["python3"]
network = ["api.github.com"]
filesystem_writable = ["/Users/me/reports"]
```
Today, `[security]` is **schema-only**. The manifest parses, `dotagent doctor` warns when it's missing, but the runner **does not enforce** any of the fields. An agent declaring `network = "deny"` still has unrestricted network. The doc explicitly says enforcement is a follow-up. This is the single largest gap between dotagent's stated security posture and its actual behavior.
## Proposal
Wrap agent execution in a per-platform sandbox driven by the declared `[security]` fields:
- **macOS**: `sandbox-exec` with a generated profile (`.sb` file) constraining filesystem writes, network egress, and subprocess execution.
- **Linux**: prefer `bwrap` (bubblewrap), fall back to `firejail` if available. Same constraints expressed in the chosen wrapper's flags.
The runner builds the sandbox spec at spawn time from the manifest. If no `[security]` block exists, behavior is unchanged (today's permissive default).
This issue should land behind a feature flag (`DOTAGENT_SANDBOX=1` or similar) for one release so existing agents can opt in and surface false positives before it becomes default.
## Acceptance criteria
- [ ] An agent declaring `network = "deny"` cannot make outbound connections (smoke test: `curl example.com` fails inside the agent).
- [ ] An agent declaring `filesystem_writable = ["/tmp/x"]` cannot `touch /tmp/y` (writes outside the allow-list fail).
- [ ] `allowed_commands = ["python3"]` blocks the agent from spawning anything else.
- [ ] `dotagent doctor` reports whether the host has the required sandbox tool installed (`sandbox-exec` on macOS is always present; `bwrap` or `firejail` on Linux is checked).
- [ ] When no sandbox tool is available on Linux, the agent runs unsandboxed with a clear warning log line (not a silent fallback).
- [ ] [`docs/security/threat-model.md`](docs/security/threat-model.md) updated with the new enforcement state.
## Where to start
- `crates/dotagent-runner/src/lib.rs::run` — the spawn site that needs to be wrapped.
- `crates/dotagent-core/src/manifest.rs` — the `Security` struct (already parsed).
- [`docs/security/threat-model.md`](docs/security/threat-model.md) — full threat model.
## Non-goals
- Network policy at the IP level (egress filtering by host/port is enough for v1; deep ACLs are future work).
- Process namespacing beyond what `sandbox-exec`/`bwrap` provide natively.
- Replacing the threat model. This implements what is already declared.
Contributor guide
Research direction
Start with crates/dotagent-runner/src/lib.rs::run, then inspect the parsed Security struct in crates/dotagent-core/src/manifest.rs and read docs/security/threat-model.md. Check the available sandbox tools and existing runner tests before designing the feature-flagged spawn path. Done means the listed smoke tests and doctor checks pass, Linux warns on an unsandboxed fallback, and the threat model documents enforcement.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- linux, macos, rust
- Domain
- backend, operating-systems, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100