Skip to content

egress shapes and address vetting, chat webhook URLs as credentials, a scratch window - #9

Merged
itswl merged 4 commits into
mainfrom
egress-shapes-redaction-scratch
Oct 19, 2026
Merged

itswl merged 4 commits into
mainfrom
egress-shapes-redaction-scratch

Conversation

@itswl

@itswl itswl commented Oct 19, 2026

Copy link
Copy Markdown
Owner

Three gaps a second deployment running copies of these files found in use over the past week, each one measured here before it was closed. The same-file fixes were kept small; the decision note is .agents/notes/implemented/2026-10-19-the-allowlist-names-shapes-and-a-name-must-resolve-where-its-kind-says.md.

1. The egress allowlist names shapes, and a listed name is vetted at the address (deploy/sidecars/proxy.py)

  • .amazonaws.com admitted every hostname anyone can obtain under that suffix: an EC2 instance's public name, a load balancer, an API Gateway stage, somebody else's EKS cluster — attacker-controlled destinations inside the allowlist. EGRESS_ALLOW now takes @aws-api (<service>[.<region>].amazonaws.com) and @aws-s3 (a bucket in every spelling); the work compose default uses them, and a cluster endpoint the planner is given goes on the operator's list by name (the shapes do not match it, on purpose). An unknown @ rule stops the container at start.
  • The list was on names and the connection on addresses. The proxy now resolves a listed name, refuses any answer that is loopback, link-local, private, reserved or the host machine's mapped address (IPv4-in-IPv6 included), and connects to the address it vetted — no second lookup. Compose peers go in EGRESS_INTERNAL and must resolve inside the compose network; both deploy composes name theirs. The name is checked before any lookup; a name that does not resolve is a 502, not a 403 (the agent is told 403 means do not retry).
  • Tests: the shapes against the forms they must and must not admit, the address vetting with a faked resolver, the peer rule, name-before-lookup, 502 on no answer.

2. A chat bot's incoming-webhook URL is a credential (hookprobe/hookprobe/gate.py, redact.py)
The output guard names the kind chat webhook url for Feishu/Lark, Slack, DingTalk, WeCom and Discord hooks, and the redactor masks the token half while keeping the host. The sibling's action job read a function's environment on 2026-10-12 and such a URL went into its transcript, two logs and the model call; nothing here named the shape, and this family posts its own cards through exactly such URLs.

3. Scratch and the CLI's own logs get a retention window of their own (HOOKPROBE_SCRATCH_DAYS, default 7)
Measured on two probe volumes: the CLI's MCP transport logs under home/.cache/claude-cli-nodejs had grown since 2026-09-04 (113 and 364 files) carrying whatever tools returned, with retention.prune never naming them and HOOKPROBE_RETENTION_DAYS defaulting to keep-forever because case files are recall. The agent subprocess now gets TMPDIR={workdir}/scratch, and that directory plus the CLI cache are swept daily on their own window: a scratch folder is old only when nothing under it was written inside the window. Passed through every compose that passes the retention knob; reference regenerated.

Operator side, done here: the work stack's .env line for EGRESS_ALLOW now carries the two shapes and its three cluster endpoints by name instead of .amazonaws.com (backup beside it). Nothing is running; it applies at the next recreate.

Gate: STACK GATE GREEN locally; CI read back below before merging.

Also on this branch, first commit, unrelated: pip-audit went red on a new advisory against the SDK's transitive mcp pin (CVE-2026-59951); upgraded to 2.3.0 in the venv and relocked. Kept as its own commit so it cherry-picks alone. Dependabot's open #6–#8 still need the same relock treatment for their bumps.

itswl added 4 commits October 19, 2026 09:55
pip-audit turned the probe's gate red on a new advisory against the SDK's transitive mcp pin (fix versions 1.30.0 / 2.2.0). Upgraded in the probe's venv, relocked with scripts/relock.sh (only this pin and mcp-types moved), audit clean. Unrelated to the rest of this branch and kept apart so it cherry-picks alone.
The output guard names the kind (chat webhook url) for Feishu/Lark, Slack, DingTalk, WeCom and Discord incoming hooks, and the redactor masks the token half while keeping the host, so a case file still says which chat was posted to. A second deployment running copies of these files read a function's environment on 2026-10-12 and such a URL went into its transcript, two logs and the model call; nothing here named the shape, and this family posts its own cards through exactly such URLs. Fixtures are visibly fake on purpose: the estate guard refuses anything shaped like a real token.
…heir own

HOOKPROBE_SCRATCH_DAYS (default 7): the agent subprocess gets TMPDIR={workdir}/scratch, and that directory plus $HOME/.cache/claude-cli-nodejs are swept daily on their own window, whatever HOOKPROBE_RETENTION_DAYS says. A scratch folder is old only when nothing under it was written inside the window, so a job still writing into its own folder is left alone.

Measured on two probe volumes: the CLI's MCP transport logs had grown since 2026-09-04 (113 and 364 files) carrying whatever tools returned, with retention.prune never naming them and the case-file window defaulting to keep-forever because case files are recall. A second deployment found a day-old scratch holding other teams' configs. Passed through every compose that passes the retention knob; reference regenerated.
…ere its kind says

EGRESS_ALLOW takes @aws-api (<service>[.<region>].amazonaws.com, any service, any region) and @aws-s3 (a bucket in every spelling), and the work compose's default uses them instead of .amazonaws.com, which also admitted every hostname anyone can get under that suffix: an instance's public name, a load balancer, an API Gateway stage, somebody else's EKS cluster. A cluster endpoint the planner is given goes on the operator's list by name; the shapes do not match it on purpose. An unknown @ rule stops the container at start.

A listed name is vetted at the address: the proxy resolves it, refuses any answer that is loopback, link-local, private, reserved or the host machine's mapped address (IPv4-in-IPv6 included), and connects to the address it vetted, never a second lookup. Compose peers go in EGRESS_INTERNAL and must resolve inside the compose network; both deploy composes name theirs. The name is checked before any lookup. A name that does not resolve is a 502, not a 403. Decision note beside it.
@itswl
itswl merged commit 9059ab2 into main Oct 19, 2026
8 checks passed
@itswl
itswl deleted the egress-shapes-redaction-scratch branch October 19, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant