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

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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.ts records 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.ts and src/apps/runtime.ts feed it.
  • App page: src/web/pages/settings.ts and src/web/studio/blocked.ts show "Blocked: host (N tries, last 2 min ago)" with Allow, Deny and Forget. The action lives in src/actions/egress_refused.ts.
  • Allow: it takes effect within seconds with no redeploy, and commits to forge.json egress with the owner as author.
  • Wildcards: egress_refused.ts only 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), and src/crew/brief.ts and src/crew/app_hosts.ts name refused hosts in the crew's brief.
  • Terminal: src/tui/screens/blocked.ts adds a Blocked hosts screen.
  • Tests: test/egress-refused.test.ts covers 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.

Closes #10456

commented

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.ts records 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.ts and src/apps/runtime.ts feed it.
  • App page: src/web/pages/settings.ts and src/web/studio/blocked.ts show "Blocked: host (N tries, last 2 min ago)" with Allow, Deny and Forget. The action lives in src/actions/egress_refused.ts.
  • Allow: it takes effect within seconds with no redeploy, and commits to forge.json egress with the owner as author.
  • Wildcards: egress_refused.ts only 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), and src/crew/brief.ts and src/crew/app_hosts.ts name refused hosts in the crew's brief.
  • Terminal: src/tui/screens/blocked.ts adds a Blocked hosts screen.
  • Tests: test/egress-refused.test.ts covers 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.

Changes requested — before the latest push

One high finding, two small ones.

  1. HIGH (src/actions/egress_refused.ts, allowBlocked): an agent admin may allow a host live (allowNow runs for any non-private row whatever actor.kind is), 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: for actor.kind === "agent" return an error (e.g. needs_human) before allowNow, 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.
  1. Allowed entries live only in <data>/egress/<repo>.allowed.json and 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.
  1. 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.

commented

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.

  1. Agent admins can no longer allow. allowBlocked returns the new needs_human error for any agent actor, before allowNow. 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.md says the same.
  2. Allowed entries that forge.json doesn't declare are now shown. I took your second option. withLiveOnly lists 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.
  3. AGENTS.md: the egress_refused.ts sentence now comes after the HELD_FRESH_MS sentence.
  4. appBlocked casts: the repeated as string / as Allowed casts are gone. A small blockedMessage(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.

Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs