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(sensor): long-running soak harness + steady-state spool compaction (Wave A)
Closes the last open Wave A item (Implementation_Status.md sensor row:
"long-lived production-host soak").
scripts/sensor-soak.sh runs the real aegis-node-sensor against a real
gateway on a real Linux /proc for a sustained period under continuous
collector workload (tagged processes, real TCP connections, real fds,
secret-shaped env vars), periodic signed-kill enforcement round-trips
against real host processes, and a mid-soak gateway outage — then
asserts what only a long run proves: sensor survival with zero panics,
bounded RSS and fd counts (steady-state baseline vs final), bounded
spool disk, full spool drain, all kills verified/executed/ACKed, and
unattended post-outage recovery. Samples land in a CSV + summary JSON
artifact.
.github/workflows/sensor-soak.yml wires it three ways: nightly 15-min
soak, operator-dispatched arbitrary duration, and a short PR smoke soak
when sensor/harness paths change (so this PR proves the harness itself).
Building the harness surfaced a real steady-state defect, fixed here
TDD-first: spool lane files were only ever compacted on the normal
lane's over-budget drop path, so on a healthy long-lived host (enqueue →
ship → ack keeping pending_bytes near zero) both append-only lane files
grew without bound until disk exhaustion. SpoolQueue::compact_if_reclaimable
now reclaims the acked prefix once it exceeds a threshold, called from
the sensor's ship tick; compaction failure degrades to append-only and
retries, never crashes the sensor.
Docs: Implementation_Status.md sensor row + Wave A ledger,
current-vs-roadmap.md, and the PRP task list updated in the same change;
remaining honesty gap is an extended-duration soak on a production-grade
deployment (a CI runner soak is still not production load).
Copy file name to clipboardExpand all lines: docs/Implementation_Status.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -66,7 +66,7 @@ production-authoritative.
66
66
| 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
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 |
| 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 | long-lived production-host soak (this proves correctness on a real Linux VM, i.e. CI, not a production deployment under sustained load) | Phased PR plan §5 | unit (87 tests) + 5 real-host integration tests | beta | production soak |
69
+
| 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 |
3.~~Sensor host enforce kill/pause/resume/quarantine~~**Done** (`process_enforcer` + command_receiver); ~~process collectors that `register_run`~~**Done** (`process_collector.rs` discovers `AEGIS_RUN_ID`-tagged host processes and registers them; `net`/`fs`/`secret` collectors alongside it, all polled from `main.rs`'s loop); ~~live-host proof~~**Done** — `bins/aegis-node-sensor/tests/real_host_integration.rs` runs every collector against a real Linux host's actual `/proc`/sockets/files (verified against a real `rust:1.96-bookworm` container, not just the synthetic `/proc` tempdir fixtures the module unit tests use); **remaining:** long-running production-host soak testing
89
+
3. ~~Sensor host enforce kill/pause/resume/quarantine~~ **Done** (`process_enforcer` + command_receiver); ~~process collectors that `register_run`~~ **Done** (`process_collector.rs` discovers `AEGIS_RUN_ID`-tagged host processes and registers them; `net`/`fs`/`secret` collectors alongside it, all polled from `main.rs`'s loop); ~~live-host proof~~ **Done** — `bins/aegis-node-sensor/tests/real_host_integration.rs` runs every collector against a real Linux host's actual `/proc`/sockets/files (verified against a real `rust:1.96-bookworm` container, not just the synthetic `/proc` tempdir fixtures the module unit tests use); ~~soak harness~~ **Done** — `scripts/sensor-soak.sh` + `sensor-soak.yml` (nightly / dispatch / PR smoke): sustained collector workload, periodic signed-kill round-trips, mid-soak gateway outage, bounded RSS/fd/spool-disk + drain + zero-panic assertions; landing it surfaced and fixed a real steady-state defect (spool lane files were never compacted outside the over-budget drop path, so disk grew without bound on a healthy long-lived host — `compact_if_reclaimable` now runs after ship ticks); **remaining:** extended-duration soak on a production-grade deployment
90
90
91
91
4. ~~E2E: untrusted agent → cage → egress deny → control action → receipt/incident (Docker)~~ **Done** — `scripts/cage-wave-a-e2e.sh` (#1840), CI-wired as job `cage-wave-a-e2e`: `root_trust_level=untrusted_external` cage run, egress routed through `aegis-egress-proxy --gateway-url` (the real fail-closed `POST /v1/egress/check` path) so a durable deny event + `ActionReceiptRecord` is asserted via `GET /v1/egress/events`, then a signed kill control command. Landing this e2e surfaced a real pre-existing bug: a forced-egress sandbox's `--internal` Docker bridge has **no route to the host at all** (not just no internet), so `host.docker.internal` never actually worked for reaching a host-run proxy, in any environment — fixed by having `aegis-cage-runner` join the proxy as a **sidecar container** to each sandbox's dedicated bridge (`egress_proxy_container` config, `docker network connect`, proxy addressed by container name) instead. Verified with a real-Docker regression test (`docker_runtime::forced_egress_sandbox_reaches_the_sidecar_proxy_container`) and the CI job itself.
0 commit comments