Public: anyone can read this; the crew can change it.

A stranger reading an app sees only the app's own Server-Timing; the forge's entries (forge-lag, forge-route and the rest) go to the app's writers #11113

Merged opened this

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

Merged

An app's response now carries the forge's own `forge-*` Server-Timing entries only when the reader may write the app's repository or is a forge admin. Everyone else gets exactly the Server-Timing the app wrote. The forge's own pages now show the `lag` entry only to admins.

shipped production is serving it deploy f9da504

opened this

An app's response now carries the forge's own forge-* Server-Timing entries only when the reader may write the app's repository or is a forge admin. Everyone else gets exactly the Server-Timing the app wrote. The forge's own pages now show the lag entry only to admins.

Proxy (src/apps/proxy.ts):

  • Right after admit, readsForgeTiming decides once per request whether the reader may see the forge's entries: an active writer of the repository, or a non-agent forge admin. A request with no grant is never checked, so a stranger costs no database read.
  • Every relayed answer now goes out through one new function, relayAnswer. For anyone else it drops the noted phases and any forge-stall, and returns the app's response unchanged. That also removes the unprefixed rewrite entry that strangers used to get on HTML pages.
  • The forge's own /__forge/* pages still use withPhases.
  • noteRelayWait and the loop watch still record every relay on the server side, so the app's speed card and terminal line are unchanged.

Forge pages (src/http/server.ts): lag;dur= is appended only when the request's role is admin. Paths that match no route never read a session, so they carry no lag for anyone.

Nothing reads forge- from a browser. Searching src for serverTiming / server-timing, the only readers are:

  • src/release/smoke.ts, which reads the app entry;
  • src/tui/screens/api.ts and src/web/api_reference.ts, which only show the raw header to whoever made the call.

Nothing in src reads .serverTiming (the browser timing API). /system and RUM take their numbers from perfRecord, loopWorst and noteRelayWait, not from the header.

Tests. Four test files that expected strangers to see these entries now expect the new behaviour:

  • test/apps-proxy-timing.test.ts: its existing checks now run as the app's owner. New cases show that a visitor and a signed-in non-writer get exactly db;dur=0.1, app;dur=1.1 (or no header at all), and that the writer's copy keeps the app's entries first, unchanged, followed only by forge-* entries.
  • test/relay-stall-name.test.ts: it asserted that a stranger sees forge-lag; it now asserts no forge- entry at all.
  • test/route-timing.test.ts: anonymous responses carry no lag.
  • test/loop-worst.test.ts: reads lag with an admin session, and checks that a stranger and a signed-in non-admin get none.

Left as is:

  • /healthz JSON still reports loopLagMs to anyone. Probes depend on it, and this issue covers the header only.
  • A writer's response keeps the app's own cache-control, so a shared cache that ignores cookies could store a copy with forge-* entries. I didn't force private there because it would change the owner's view of their own caching headers.
  • I couldn't call the keep_plan tool from this run, so the issue has no plan posted.

Checks run:

  • bun scripts/gate.ts and bun x tsc --noEmit pass.
  • scripts/fast.ts named 2,047 test files, too many to run here, so I picked the 16 that cover this change: 126 pass, 0 fail.
  • I skipped the design lint: nothing under src/web/ changed.

To try it: run curl -sI against the preview's app host while signed out and only the app's Server-Timing comes back. Open it signed in as a writer and the forge-* entries are there. curl -sI / on the forge while signed out shows no lag.

🤖 Generated with Claude Code

Closes #11112

commented

An app's response now carries the forge's own forge-* Server-Timing entries only when the reader may write the app's repository or is a forge admin. Everyone else gets exactly the Server-Timing the app wrote. The forge's own pages now show the lag entry only to admins.

Proxy (src/apps/proxy.ts):

  • Right after admit, readsForgeTiming decides once per request whether the reader may see the forge's entries: an active writer of the repository, or a non-agent forge admin. A request with no grant is never checked, so a stranger costs no database read.
  • Every relayed answer now goes out through one new function, relayAnswer. For anyone else it drops the noted phases and any forge-stall, and returns the app's response unchanged. That also removes the unprefixed rewrite entry that strangers used to get on HTML pages.
  • The forge's own /__forge/* pages still use withPhases.
  • noteRelayWait and the loop watch still record every relay on the server side, so the app's speed card and terminal line are unchanged.

Forge pages (src/http/server.ts): lag;dur= is appended only when the request's role is admin. Paths that match no route never read a session, so they carry no lag for anyone.

Nothing reads forge- from a browser. Searching src for serverTiming / server-timing, the only readers are:

  • src/release/smoke.ts, which reads the app entry;
  • src/tui/screens/api.ts and src/web/api_reference.ts, which only show the raw header to whoever made the call.

Nothing in src reads .serverTiming (the browser timing API). /system and RUM take their numbers from perfRecord, loopWorst and noteRelayWait, not from the header.

Tests. Four test files that expected strangers to see these entries now expect the new behaviour:

  • test/apps-proxy-timing.test.ts: its existing checks now run as the app's owner. New cases show that a visitor and a signed-in non-writer get exactly db;dur=0.1, app;dur=1.1 (or no header at all), and that the writer's copy keeps the app's entries first, unchanged, followed only by forge-* entries.
  • test/relay-stall-name.test.ts: it asserted that a stranger sees forge-lag; it now asserts no forge- entry at all.
  • test/route-timing.test.ts: anonymous responses carry no lag.
  • test/loop-worst.test.ts: reads lag with an admin session, and checks that a stranger and a signed-in non-admin get none.

Left as is:

  • /healthz JSON still reports loopLagMs to anyone. Probes depend on it, and this issue covers the header only.
  • A writer's response keeps the app's own cache-control, so a shared cache that ignores cookies could store a copy with forge-* entries. I didn't force private there because it would change the owner's view of their own caching headers.
  • I couldn't call the keep_plan tool from this run, so the issue has no plan posted.

Checks run:

  • bun scripts/gate.ts and bun x tsc --noEmit pass.
  • scripts/fast.ts named 2,047 test files, too many to run here, so I picked the 16 that cover this change: 126 pass, 0 fail.
  • I skipped the design lint: nothing under src/web/ changed.

To try it: run curl -sI against the preview's app host while signed out and only the app's Server-Timing comes back. Open it signed in as a writer and the forge-* entries are there. curl -sI / on the forge while signed out shows no lag.

🤖 Generated with Claude Code

Approved

Reads clean. Proxied app answers carry forge-* Server-Timing only for an active repo writer or a non-agent admin, decided once from the signed grant, so a stranger costs no read. Every relayed return goes through relayAnswer, which also drops forge-stall and the unprefixed rewrite entry. Forge pages show lag only to admins. The tests cover visitor, non-writer, writer and admin, and the app's own entries stay first and unchanged. The remaining withPhases calls are the /__forge/* pages only. Nits, not blockers: a writer's response keeps the app's cache-control, so a shared cache could store a copy with forge-* entries (consider Vary: Cookie or private when the entries are added); the createUser call in apps-proxy-timing.test.ts is badly formatted; and the withPhases doc comment line is overlong.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs