← back to AbramsEgo
memos/a2a-egress-rails.md
48 lines
# A2A egress rails + egress-sentinel cross-reference (TK-10381, Phase C)
**Built:** 2026-08-09 · client-only, activation gated (empty allowlist).
## What egress the A2A client can produce
The A2A client (`lib/a2a-client.js`) is the ONLY new outbound-network surface. Its egress
is deliberately shaped to be **benign** per the `egress-sentinel` skill (born of the
2026-07-29 credential-stealer, C2 `144.172.92.199:8080`, `/tmp/.e*.sh` dropper):
- **HTTPS + port 443 ONLY.** `assertSafeUrl()` throws on any `http://` or non-443 port.
So there is never an outbound connection on a non-web port (the exact shape the sentinel
flags as a C2 beacon).
- **No cross-host redirects.** `fetch(..., { redirect: 'error' })` — a 3xx aborts.
- **Card-driven SSRF blocked.** The fetched Agent Card is untrusted; `assertSameHost()`
pins the JSON-RPC endpoint to the SAME host as the allowlisted base URL, so a hostile
card cannot pivot the call to another 443 host.
- **Allowlist is the only place egress can grow**, and it starts EMPTY
(`data/a2a-agents.json` = `[]`). Adding an entry is a Steve-gated act. With an empty
allowlist, `resolveAgent()` throws and NO outbound connection is ever attempted.
- **15s hard timeout** (AbortController) — no hung/long-lived sockets.
- **Payload linter** (`lintPayload()`) refuses any outbound question containing secret-
shaped strings (shopify token, DATABASE_URL, sk_live_/pk_live_, ghp_, ANTHROPIC/OPENAI/
GEMINI/REPLICATE keys, `secret=…`). The outbound body is the question text ONLY — never
the fleet snapshot, cost/PnL, tokens, .env, or file paths.
## Cross-reference for egress-sentinel
When an A2A peer is eventually approved into the allowlist, the egress-sentinel operator
should expect: a short-lived HTTPS:443 connection from the `abramsego` node process to the
allowlisted peer host, during a consult, then closed. That is benign by the sentinel's own
rules (web-port, allowlisted, transient). Anything else from this process — non-443 port,
a dropper file, a node→shell subtree reaching curl/wget/nc — is NOT the A2A client and
should be treated as the sentinel intends: a CRITICAL beacon signal.
## Inbound = none
Phase 1 is client-only: AbramsEgo publishes NO Agent Card, opens NO inbound A2A port,
registers in NO directory. Becoming an A2A **server** (which would make :9773 a public
attack surface) is a separate, default-BLOCK, Steve-gated decision (memo Phase D).
## Activation still gated
Even with the code shipped and routes live, the feature is OFF: the allowlist is empty and
`/api/a2a/consult` returns `agent "…" not in allowlist` for any request. Turning it on =
Steve adds one trustworthy peer `{name, url, purpose, auth_ref}` to `data/a2a-agents.json`
(auth_ref points into secrets-manager; no credential in repo). That edit is the gate.