You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(gateway): enforce image_digest bans at cage run create + claim (#1678)
The agent_bans store has accepted image_digest targets since migration
0029 but never consulted them. A banned sandbox image digest now blocks
fail-closed (403) at both moments that matter:
- POST /v1/agent-cage/runs — when cage_spec.image_digest is present,
registration is denied before the run record or start command exist;
- POST /v1/agent-cage/runs/:id/claim — re-checked against the digest
stored on the run record, so a ban created after registration still
blocks the sandbox from ever starting.
Storage errors during the check are 500s (unreadable enforcement state
blocks). Tests written RED-first: banned digest denied at create,
late ban denied at claim, unrelated digest ban does not block.
Honesty ledger: fingerprint has no carrier field at any choke point
today (mTLS yields CN only — needs a protobuf-first contract change)
and prompt_hash appears only at telemetry ingest, where blocking would
destroy evidence (enforcement belongs to SOC auto-response). Both stay
stored-but-unenforced, now documented as such with the reason.
| Control commands (signed kill/pause/quarantine) | Partial | store + protocol + gateway issue routes; sensor poll/verify + **host ProcessEnforcer** (SIGTERM/STOP/CONT for registered PIDs); cage-runner Docker kill path | auto PID discovery/collectors; grace_period from command payload | Phased PR plan §5 | storage + sensor unit (real child kill) | beta | collectors |
67
-
| Ban system (first-class store) | Implemented |`lib/storage/src/db/agent_bans.rs`, migration `0029`; enforced at every choke point: `POST /v1/authorize` (agent + tool bans → durable deterministic deny via `write_decision_and_audit`), `POST /v1/broker/execute` (tool ban → 403 before approval consumption), `POST /v1/agent-cage/runs` create + claim (agent ban → 403; claim re-checks so a ban created after registration still blocks start), `POST /v1/egress/check` (destination ban, pre-existing); sensor prop: `POST /v1/bans` with `target_type=agent` issues signed `kill_run` control commands for the agent's active runs (`list_active_agent_runs_for_agent`) |`fingerprint`/`image_digest`/`prompt_hash` target types stored but not yet consulted at a choke point |#1678| storage + route (authorize/broker/runtime/control/egress) | beta | remaining target types |
67
+
| Ban system (first-class store) | Implemented | `lib/storage/src/db/agent_bans.rs`, migration `0029`; enforced at every choke point: `POST /v1/authorize` (agent + tool bans → durable deterministic deny via `write_decision_and_audit`), `POST /v1/broker/execute` (tool ban → 403 before approval consumption), `POST /v1/agent-cage/runs` create + claim (agent ban → 403; claim re-checks so a ban created after registration still blocks start), `POST /v1/egress/check` (destination ban, pre-existing); sensor prop: `POST /v1/bans` with `target_type=agent` issues signed `kill_run` control commands for the agent's active runs (`list_active_agent_runs_for_agent`) | `image_digest` bans enforced at cage run create + claim (`image_digest_ban_denial`, re-checked at claim so a ban created after registration still blocks start); `fingerprint`/`prompt_hash` target types stored but not consulted — `fingerprint` has no carrier at any choke point today (mTLS yields CN only; needs a wire field, i.e. a protobuf-first contract change), `prompt_hash` only appears at telemetry ingest where blocking would destroy evidence (enforcement belongs to SOC auto-response, a separate design) | #1678 | storage + route (authorize/broker/runtime/control/egress) | beta | remaining target types |
| Node sensor | Implemented | `bins/aegis-node-sensor` (main, spool, shipper, command_receiver); real `process`/`net`/`fs`/`secret` collectors (`AEGIS_RUN_ID`-tagged process discovery -> `ProcessEnforcer` registration + `network_connection`/filesystem/secret-signal runtime events), all polled from the main loop; Dockerfile, Helm, compose; `tests/real_host_integration.rs` proves `scan_host_aegis_processes`/`ProcessCollector`/`NetCollector`/`FsCollector`/`SecretCollector` against a real Linux host's actual `/proc`, a real signal-killed child, a real established TCP socket, a real open file descriptor, and a real secret-shaped env var (name reported, value never leaves the host) -- not the synthetic `/proc` tempdir fixtures the module unit tests use; `scripts/sensor-soak.sh` + CI `sensor-soak.yml` (nightly 15-min + dispatchable multi-hour + PR smoke) soak the real sensor on the runner's real `/proc` under continuous collector workload, signed-kill round-trips, and a mid-soak gateway outage, asserting bounded RSS/fds/spool disk, zero panics, full spool drain, and unattended recovery; steady-state spool compaction (`compact_if_reclaimable`, wired into the ship tick) keeps lane files from growing without bound on long-lived hosts | extended-duration soak on a production-grade deployment (the nightly CI soak runs on a CI runner, not a production host under production load) | Phased PR plan §5 | unit (89 tests) + 5 real-host integration tests + nightly CI soak | beta | production-deployment soak |
0 commit comments