Security & trust¶
IronClaw's value is not "AI agents" — it is AI agents you can run without trusting them. This page is the map of that trust story: what IronClaw defends against, the invariants that make the defense hold, and how you verify what you install.
Start here¶
-
The assumption that drives every design decision: the agent inside the sandbox is potentially compromised. What that means for the blast radius, and — in §8 — what counts as a vulnerability.
-
Every release is checksummed, keyless-signed with cosign, and carries build-provenance attestations. Here is how to check all three before you trust a download.
The invariants¶
IronClaw's hardening rests on a small set of invariants that hold regardless of what the agent does:
- Sealed runtime. The sandbox has no interpreter, no in-sandbox package install, and no rootfs mutation. An agent can only invoke capabilities the binary already compiled in. This is why skills and MCP servers grant capabilities through configuration, never code.
- Deterministic approval gateway. Every mutation — persona, enabled tools,
packages, wiring, permissions, mounts,
create_agent— is held at the gateway until a human approves it. There is no bypass path. The Quickstart makes this choke point concrete in two commands. - No public surface. The control-plane API binds only to the private mesh (Tailscale) interface; network reachability is the primary control, and the bearer token is defense-in-depth on top of it. See the API reference.
- Network-isolated sandboxes. Sandboxes run
network=noneby default; outbound access is granted host-by-host through the egress broker, only as part of an approved change. - Append-only audit. Every approve/reject decision and every gated action is recorded. See Architecture.
Credential vault: agents use keys without holding them¶
An agent reaches a vaulted API by logical name — vault://<cred>/<path> — and
never by holding the key. The egress broker forwards the call to a separate host-side
injector that attaches the real credential; the broker (and the sandbox) inject
nothing. Access is deny-by-default and per agent group: a group may use a
credential against a host only if an approved policy grant says so.
Those grants are config, never secrets — every rule names a credential, never
holds one — and they are managed through the gateway like any other capability
change, so a grant is held until a human approves it and is recorded in the audit
log. Manage them with ironctl vault:
# See a group's deny-by-default state and active grants (no secret is ever shown):
ironctl vault list --group <agent-group>
# Propose a grant (held at the gateway for human approval):
ironctl vault grant --group <agent-group> --credential github --host api.github.com --by you
ironctl change approve <change-id> --by you
# Narrow or remove a grant (also gateway-gated):
ironctl vault revoke --group <agent-group> --credential github --host api.github.com --by you
Rotating the secret value behind a credential is an injector operation — the
control plane never holds the key, so there is nothing for it to rotate. Point the
broker at an injector with --vault-endpoint. The threat model's §11 has the full
model.
The supply chain is part of the promise¶
A release a user cannot verify is not a secured release. IronClaw's published artifacts are:
- Reproducible — re-derivable from a known commit, with no nondeterministic inputs leaking into the build.
- Checksummed — every archive is listed in
SHA256SUMS; the installers (install.sh/install.ps1) verify checksums before executing anything. - Signed — a keyless cosign signature
over
SHA256SUMSis the trust anchor. - Attested — build-provenance attestations tie every artifact back to its
source commit and the workflow that built it. Both the release archives and the
individual binaries inside them carry provenance, so
gh attestation verifyworks against either. - Proven contained — every release also ships a signed containment report
(
ironclaw_<version>.containment.jsonand.txt). The red-team escape harness runs against the offline demo at the released commit and freezes the result into a machine-verifiable artifact: every isolation invariant, the assertion that proved it, and pass/fail, bound to the commit and the runtime tested. The two files are checksummed intoCONTAINMENT-SHA256SUMS, which is cosign-signed and attested the same waySHA256SUMSis — so you can confirm the threat model's core §5/§8 invariants held for the exact version you run, without re-running anything. If a core assertion had failed, the release would have been yanked, not shipped.
The Release runbook is the operational reference for cutting,
verifying, and yanking a release. The threat model — the single source of truth for
what IronClaw defends against — is threat-model.md; this page and
SECURITY.md summarize
and point there rather than restating it.
Fuzzing the trust boundary¶
The parsers that sit on the trust boundary — skill/MCP manifest YAML, the minisign signature and public-key blobs, and the name/version/asset-path validators that compose filesystem paths — all consume attacker-influenced bytes. Every one of them must fail closed: malformed input returns an error, never a panic (a crash in the host control-plane is an availability break), and a validator must never accept an identifier or path that could escape its mount root.
These properties are enforced by Go native fuzz
targets in internal/host/skills/fuzz_test.go. Each target ships an inline seed
corpus (valid inputs plus hostile ones — traversal paths, NUL bytes, YAML
pathologies, junk base64) so coverage starts meaningful. Run one locally with:
# 30s is enough for a quick regression check; raise -fuzztime for a deeper run.
CGO_ENABLED=1 go test ./internal/host/skills/ -run='^$' -fuzz='^FuzzParse$' -fuzztime=30s
Available targets: FuzzParse (manifest), FuzzParseSignature,
FuzzParsePublicKey, FuzzValidAssetPath, FuzzValidIdentifiers.
Two CI jobs keep this honest, both isolated from the release path so a fuzz failure can never gate a release:
- The
fuzzjob in ci.yml runs every target for 30s on each push and PR — a fast regression smoke test (and what OpenSSF Scorecard's Fuzzing check detects). - fuzz-nightly.yml runs each target for several minutes on a nightly schedule
(and on demand via Run workflow), giving the engine time to walk past the seed
corpus. If a target crashes, the reproducer it writes under
internal/host/skills/testdata/fuzz/is uploaded as a build artifact for triage.
Reporting a vulnerability¶
Disclosure policy lives in SECURITY.md. The scope — what counts as a vulnerability — is defined in §8 of the threat model, which the disclosure policy points back to.