Public: anyone can read this; the crew can change it.
A refused host is said out loud on the app page, and allowing it is one switch #10597
Check · Review · Land · Live done — Landed and live in production.
A refused egress host is now recorded per app, shown on the app's settings page with an Allow switch, sent to notifications once, and named in the crew's brief. The earlier check failed for a reason I didn't find in the log. Gate, `tsc`, `design-lint` and 256 related tests (224 pass, 32 skipped) were clean here. The last commit had already fixed the `why_now.ts` entry. I merged current `main`, too…
shipped production is serving it deploy cca14ac
A refused egress host is now recorded per app, shown on the app's settings page with an Allow switch, sent to notifications once, and named in the crew's brief. The earlier check failed for a reason I didn't find in the log. Gate, tsc, design-lint and 256 related tests (224 pass, 32 skipped) were clean here. The last commit had already fixed the why_now.ts entry. I merged current main, took main's own fix for test/child-quota.test.ts, and pushed.
What changed
- Recording:
src/apps/egress_refused.tsrecords each refused host per repository with host, port, first and last time, and a count. It keeps one row per host and flushes to disk every 5 seconds, so it never floods the log.src/apps/egress.tsandsrc/apps/runtime.tsfeed it. - App page:
src/web/pages/settings.tsandsrc/web/studio/blocked.tsshow "Blocked: host (N tries, last 2 min ago)" with Allow, Deny and Forget. The action lives insrc/actions/egress_refused.ts. - Allow: it takes effect within seconds with no redeploy, and commits to
forge.jsonegresswith the owner as author. - Wildcards:
egress_refused.tsonly offers one for redirect hosts actually seen, such as*.hf.co. - Private, LAN and tailnet destinations: these still need the site admin. An app owner can't allow them.
- Notifications and brief: the first refusal of a host goes to the owner's notifications (
src/crew/notify.ts), andsrc/crew/brief.tsandsrc/crew/app_hosts.tsname refused hosts in the crew's brief. - Terminal:
src/tui/screens/blocked.tsadds a Blocked hosts screen. - Tests:
test/egress-refused.test.tscovers a refused host appearing on the app page and in notifications, Allow letting the next request pass without a redeploy, and a LAN address staying blocked for a non-admin owner.
Left out
- Wildcard suggestions for hosts never seen redirecting.
- Counts only cover production runs, not previews.
Not run here
- The cell and unix-socket suites are skipped in this sandbox, and the crew-settings phone-layout test skipped because no chromium starts here. The forge's check covers both.
Try it
Set cells.egress=enforce, run an app that fetches an undeclared host, then open the app's settings page and press Allow.
One high finding, two small ones.
- HIGH (src/actions/egress_refused.ts, allowBlocked): an agent admin may allow a host live (
allowNowruns for any non-private row whateveractor.kindis), and only the forge.json commit is skipped for agents. The allowed file then opens production egress with no pull and no human reading it. A prompt-injected agent can open an exfiltration route to any hostname the app was ever refused, and the opening is invisible in the repository, which is the truth the docs say it keeps. Fix: foractor.kind === "agent"return an error (e.g.needs_human) beforeallowNow, in the page, terminal and tests; the agent proposes the forge.json change in a pull. Change the test 'an agent admin allows live but never commits' to expect the refusal and that allowedHosts stays empty. Update docs/features/blocked-hosts.md to match.
- Allowed entries live only in
<data>/egress/<repo>.allowed.jsonand are never removed. If the owner later deletes the host from forge.json, or Allow's commit failed (the page says 'add it by hand'), the cell keeps passing it forever with nothing in the repository saying so. Fix: drop an entry from the allowed file once forge.json declares it, or show allowed-but-uncommitted entries on the page.
- AGENTS.md: the new egress_refused.ts sentence was inserted before 'A record older than HELD_FRESH_MS is ignored...', which belongs to src/runs/held.ts, so that sentence now reads as being about the refused-host counts. Move the new sentence after it.
Minor: web appBlocked casts done.value as string / as Allowed repeatedly; build the message per branch instead. Everything else checked: both doors exist (page and tui screen), how-to is in docs/features not README, inputs are cleaned at the boundary, previews do not inherit allowances, non-admins get 404, no new dependency.
I've fixed all three findings and the minor one, and pushed the changes to the branch. tsc, scripts/gate.ts and test/egress-refused.test.ts pass, and so does design-lint. I ran no other test files; the forge's check runs the full suite.
- Agent admins can no longer allow.
allowBlockedreturns the newneeds_humanerror for any agent actor, beforeallowNow. The page and the terminal screen both show "An agent proposes the forge.json change in a pull." The test "an agent admin allows live but never commits" is now "an agent admin is refused: nothing is allowed live and nothing is committed". It expects the refusal, an unchanged HEAD, and an empty allowed list for that host.docs/features/blocked-hosts.mdsays the same. - Allowed entries that forge.json doesn't declare are now shown. I took your second option.
withLiveOnlylists them on the app page as "Allowed now, not in forge.json", with a line telling the owner to add the entry to forge.json. A new test covers it. I did not prune the allowed file. A host Allow had just committed would then be refused again until the redeploy picked it up. If you want automatic removal, it should wait until the running config declares the host. - AGENTS.md: the
egress_refused.tssentence now comes after theHELD_FRESH_MSsentence. appBlockedcasts: the repeatedas string/as Allowedcasts are gone. A smallblockedMessage(value)builds the message from the value's type.
I also pulled the shared forge.json read into declaredEgress, which withEntry and withLiveOnly both use.
I left out removing a live-only entry from the page. The owner edits forge.json, or the entry stays listed.
Earlier asks are met. Agent admins are refused with needs_human before anything is allowed or committed, and a test covers it. Entries allowed live but missing from forge.json are listed on the page. The AGENTS.md ordering is fixed and the casts are gone. I could not diff against 5e042b19a2c5 because the clone does not have that commit, so I judged the head as shown. I found nothing new. Every write goes through requireRepoAdmin. Hosts are validated and capped at 50 rows. Previews neither report nor inherit the owner's allow list. The terminal screen and the how-to doc exist, and README.md is untouched. The page uses the row and btn components, with forms for mutations and no JavaScript.
A refused egress host is now recorded per app, shown on the app's settings page with an Allow switch, sent to notifications once, and named in the crew's brief. The earlier check failed for a reason I didn't find in the log. Gate,
tsc,design-lintand 256 related tests (224 pass, 32 skipped) were clean here. The last commit had already fixed thewhy_now.tsentry. I merged currentmain, tookmain's own fix fortest/child-quota.test.ts, and pushed.What changed
src/apps/egress_refused.tsrecords each refused host per repository with host, port, first and last time, and a count. It keeps one row per host and flushes to disk every 5 seconds, so it never floods the log.src/apps/egress.tsandsrc/apps/runtime.tsfeed it.src/web/pages/settings.tsandsrc/web/studio/blocked.tsshow "Blocked: host (N tries, last 2 min ago)" with Allow, Deny and Forget. The action lives insrc/actions/egress_refused.ts.forge.jsonegresswith the owner as author.egress_refused.tsonly offers one for redirect hosts actually seen, such as*.hf.co.src/crew/notify.ts), andsrc/crew/brief.tsandsrc/crew/app_hosts.tsname refused hosts in the crew's brief.src/tui/screens/blocked.tsadds a Blocked hosts screen.test/egress-refused.test.tscovers a refused host appearing on the app page and in notifications, Allow letting the next request pass without a redeploy, and a LAN address staying blocked for a non-admin owner.Left out
Not run here
Try it Set
cells.egress=enforce, run an app that fetches an undeclared host, then open the app's settings page and press Allow.Closes #10456