Capabilities & sandbox
How Comet enforces what an Agent can and cannot do.
Every Agent in SoyaOS runs inside a Comet sandbox with a capability allowlist. Anything not declared in capabilities is denied. The model is fail-closed: the default state is “you cannot touch the outside world”, and the manifest opts back in for specific, narrow capabilities.
Why a capability model
Three pressures shaped this:
- Untrusted authorship. SoyaPacks come from anywhere. A pack you
pull-ed yesterday should not be able to exfiltrate your data today if the author flipped malicious. - Trustworthy audit. When an incident happens, you want to answer “what was this pack allowed to reach?” by reading one block in one YAML file, not crawling Go code.
- Reproducibility. Capability declarations double as a contract for Comet’s cache: a pack with
egress: []anddeterminism_tier: read-onlyis provably cacheable.
What gets gated
| Surface | Mechanism | Bypass possible? |
|---|---|---|
| Network egress | Per-host:port:protocol allowlist; resolved at run time | No — DNS resolution itself goes through the allowlist. |
| Filesystem read | Mount-namespace + bind mounts, prefix-matched | No — paths not on the read list are 404. |
| Filesystem write | Same as read; writes outside fs.write get EACCES | No. |
| Process exec | Only the pack’s own interpreter; subprocess spawn is denied | No. |
| Time access | Real time (clock_gettime) is allowed; setting time is not | n/a — needed for sane logs. |
| Random | /dev/urandom is always available | n/a. |
| Outbound DNS | Resolved through the Moon (which enforces the allowlist) | No. |
Egress
capabilities:
egress:
- host: api.openai.com
port: 443
protocol: https
- host: hooks.slack.com
port: 443
protocol: https
- Each entry is a triple — host + port + protocol — and all three must match for the connection to succeed.
- Wildcards are accepted (
*.openai.com) butpack lint --strictflags them. - IP literals (
192.168.1.1) are accepted, but discouraged — they don’t survive a service migration. - Egress is enforced inside the Comet; even if your tool tries to bypass the manifest with a raw socket, the kernel netfilter chain set up at Comet start blocks the connection.
If your pack needs an unknown number of hosts (e.g. a web-crawling Agent), you have two honest options:
- Use
unrestricteddeterminism +egress: ["*"]— explicit about being a black box. Comet refuses to cache results. - Add a Moon-side proxy — the Moon’s HTTP proxy can be the only
egressentry; the Moon then enforces its own list. Recommended for production.
Filesystem
capabilities:
fs:
read:
- /workdir # always available; explicit-is-better
- /workdir/inputs
write:
- /workdir/out
- All paths are inside the sandbox. The host filesystem is invisible — the Comet creates a fresh mount namespace at start.
- Prefix-matched.
/workdir/out/foo.jsonis allowed byfs.write: [/workdir/out]. /workdiritself is always read-only unless you list a subpath as writable.- Read of an undeclared path returns ENOENT, not EACCES — i.e., the file looks like it doesn’t exist. This is intentional: never leak the host’s directory structure.
Determinism tiers
| Tier | Semantics | Cached? |
|---|---|---|
read-only | Same input always produces the same output (up to LLM nondeterminism). | Yes, by input hash. |
side-effect | The Agent makes observable changes outside the sandbox (creates a Linear issue, posts to Slack, writes to a DB). | No. |
unrestricted | Escape hatch. Treats the Agent as fully opaque. Implies side-effect. | No. |
The tier is enforced by Comet on the Agent’s behavior, not just its declaration. If an Agent declares read-only but its tools contain kind: builtin.http_post, validation fails at push time.
What happens on a violation
When the Comet detects a capability violation (network, fs, exec):
- The current syscall returns EACCES / ENOENT / ECONNREFUSED — the Agent code sees a normal-looking error.
- A
capability_violationScope event is emitted with the offending surface, requested resource, and matching manifest entry (or “no match”). - The run exits with code
3. - The Moon flags the pack version in the registry; subsequent runs of the same version emit a warning on each invocation until the pack is patched.
In CI:
$ soyaos pack lint . --strict
err capabilities.egress declares `api.openai.com:443` but tools.fetch uses `api.anthropic.com:443`
1 error · 0 warnings
Tooling support
soyaos pack validate .— checks the manifest’s shape.soyaos pack lint .— checks coherence betweencapabilities,tools, andexamples.soyaos pack lint . --strict— fails on wildcard hosts, missing examples for declared capabilities, anddeterminism_tiermismatches.
See SoyaPack v0 manifest for the manifest schema, and Scope events for the event envelope.