Block loopback egress#216
Conversation
# Conflicts: # Cargo.lock # crates/firma-core/src/decision.rs # crates/firma-sidecar/benches/pipeline.rs # crates/firma-sidecar/benches/stage1.rs
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
# Conflicts: # Cargo.lock
|
thanks for working on this. question: have you tested manually on both OS as well? I think we could have an OS targeted stacked change here to help @therandomsecurityguy or others to review and test on linux as well instead of one targeting everything. If it's possible it would be great to have something like this to speed up the process and isolated reviews: run-audit
linux maybe could be one pr?
macos:
docs
I think we can add more tests based on the test coverage report as well. NOTE: this is just a suggestion to take it or leave it. |
|
Superseded — split into 4 focused PRs:
Same content, reviewable per-platform. Closing this monolith. |
## What Adds the **firma run audit channel**: an out-of-band control socket (`FIRMA_RUN_AUDIT_SOCK`) carrying newline-delimited `firma_core::RunAuditMessage`. The Sidecar owns the mapping from each `RunAuditEvent` variant to `(action_class, decision, deny_reason, resource)` and signs the result. `RunAuditEvent::LoopbackBlocked` is the first variant, mapping to the audit-only `network.loopback` action class (not in `ActionClassRegistry::v0_1`, never Cedar-evaluated). The channel is reusable by future out-of-band producers rather than adding new sockets. ## Why Foundation for the loopback egress guard (Linux) and future out-of-band enforcement facts to reach signed audit without touching the request pipeline. ## Stack Base of the fir-383 split. Replaces #216. - this -> #(linux) -> stacked - this -> #(docs) -> stacked
## What Linux `bwrap` seccomp `user-notify` filter that traps the agent's `connect(2)` and **blocks** direct connections to any loopback address (`127.0.0.0/8`, `::1`, IPv4-mapped) except the proxy bridge and DNS stub ports, returning `EACCES`. Catches direct sockets that bypass `HTTP_PROXY`. Each block is reported to the Sidecar over the audit channel (PR #231) and recorded as a signed `DENY` / `network.loopback` / `loopback blocked` event, visible in `firma monitor`. Deny path is race-free; allow path uses `SECCOMP_USER_NOTIF_FLAG_CONTINUE`. Fails open with a warning if user-notify is unavailable — the private netns already isolates the agent, so the guard is defense-in-depth plus a direct-socket audit trail. ## Stack Based on #231 (audit channel). Part of the fir-383 split replacing #216.
## What Documents the loopback egress enforcement model across backends: - `docs-site/src/content/docs/concepts/sandbox.md` — new **Loopback blocking** section (Linux seccomp guard, macOS sandbox-exec port-scope, proxy-only backends), updated network-boundary / proxy-bridge prose, and structural/invariant tables. - `crates/firma-run/src/backend/windows_wsl2.rs` — comment documenting the WSL2/Windows gap: the seccomp guard is Linux-host-only and not wired across the Windows->guest boundary; WSL2 stays proxy-only. `llms.txt` audit-channel + `network.loopback` action-class registry entries land in the branches that own them (#231 base; #232 linux; #233 macOS). ## Stack Based on #231 (audit channel, for the `network.loopback` model). Part of the fir-383 split replacing #216.
Problem
A wrapped agent's connection to
127.0.0.1/::1/loopback bypassesHTTP_PROXY, never reaching the Sidecar. Where the agent shares the host netns (proxy-only, macOS sandbox-exec), it can reach local admin ports, daemons, MCP servers with no policy eval and no audit.Approach
Block loopback at the sandbox boundary — agent process only, never Firma's own components (proxy bridge + DNS stub use loopback) — and audit each block.
user-notifytrapsconnect(2); host supervisor readssockaddrvia/proc/<pid>/mem, blocks non-sanctioned loopback withEACCES. In-sandbox installer hands the notify fd overSCM_RIGHTS, thenexecves the agent.Audit
New
DenyReason::LoopbackBlocked+ audit-onlynetwork.loopback. Blocks flow over the genericfirma runaudit channel (FIRMA_RUN_AUDIT_SOCK):RunAuditMessage { session_id, agent_id, event }with taggedRunAuditEvent. Sidecar owns the mapping (impl From<&RunAuditMessage> for AuditPayload) and signs it.LoopbackBlockedis the first kind; future producers reuse the channel.Limitations
Verification
Draft — full build/nextest/clippy/dprint pending (tooling outage during dev).