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

Audit the streaming routes (/events, telemetry/*) for traces of a read-only mirror #10696

Merged opened this

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

Merged

I found no leak in `/events` or the telemetry routes, so there is no code fix. The only change is test coverage in `test/origin-read-only-private.test.ts`. That file's 21 tests pass and `tsc --noEmit` is clean. I did not run `scripts/gate.ts` or the other test files.

shipped production is serving it deploy ea75315

opened this

I found no leak in /events or the telemetry routes, so there is no code fix. The only change is test coverage in test/origin-read-only-private.test.ts. That file's 21 tests pass and tsc --noEmit is clean. I did not run scripts/gate.ts or the other test files.

What the test now reads

  • Telemetry: /api/v1/telemetry/{home,system,flow,sources,history} join the listings array, so anonymous, stranger and crew readers are checked on them like any other listing. The existing check covers the mirror's name, commit message, issue text and commit id.
  • Granted agent: a separate test grants the crew agent telemetry:read and reads all five routes. Each answers 200 and carries no trace of the mirror, so the private-repo cut-out in the agent's view holds.
  • /events: each of the three readers replays with ?since=0 and stays connected. While connected, a new issue is created on the mirror. Neither the replay nor the live frames carry anything of the mirror.
  • Owner: the owner's /events replay does contain the mirror, so the "no leak" results are not just an empty stream.

Not done

  • /events is not in the listings array, because those requests read to the end of the response and a stream never ends. It has its own test with a timed read instead.
  • I did not audit the per-repo /:owner/:name/stream. It is canRead-gated, but it is outside this issue's scope.
  • I did not run scripts/gate.ts.

Try it: bun test test/origin-read-only-private.test.ts

Closes #10677

commented

I found no leak in /events or the telemetry routes, so there is no code fix. The only change is test coverage in test/origin-read-only-private.test.ts. That file's 21 tests pass and tsc --noEmit is clean. I did not run scripts/gate.ts or the other test files.

What the test now reads

  • Telemetry: /api/v1/telemetry/{home,system,flow,sources,history} join the listings array, so anonymous, stranger and crew readers are checked on them like any other listing. The existing check covers the mirror's name, commit message, issue text and commit id.
  • Granted agent: a separate test grants the crew agent telemetry:read and reads all five routes. Each answers 200 and carries no trace of the mirror, so the private-repo cut-out in the agent's view holds.
  • /events: each of the three readers replays with ?since=0 and stays connected. While connected, a new issue is created on the mirror. Neither the replay nor the live frames carry anything of the mirror.
  • Owner: the owner's /events replay does contain the mirror, so the "no leak" results are not just an empty stream.

Not done

  • /events is not in the listings array, because those requests read to the end of the response and a stream never ends. It has its own test with a timed read instead.
  • I did not audit the per-repo /:owner/:name/stream. It is canRead-gated, but it is outside this issue's scope.
  • I did not run scripts/gate.ts.

Try it: bun test test/origin-read-only-private.test.ts

Changes requested — before the latest push

Test-only audit, no leak found; the coverage idea is right, but the /events tests are timing-based and will flake or pass vacuously, which the direction forbids ("fast tests that never flake").

Fix first: replace the wall-clock reads in listen. (1) The live-frame check creates the issue at 300ms and aborts at 1200ms. If the bus delivers late or the machine is loaded, the leaked frame arrives after the abort and the test passes while proving nothing. Make it deterministic: after createIssue, also create a visible marker event the viewer is allowed to see (e.g. a public-repo issue or an event the anonymous/stranger/agent reader may read), and read until that marker appears in the stream (with a generous upper bound only as a failure timeout). Then assert no trace in everything received up to and including the marker. Events are ordered, so a mirror leak would precede it. (2) The same applies to the replay: read until a known visible replayed event, not for a fixed time. (3) This spends ~1.2s x 4 sleeps on a suite aiming to be fast; reading until the marker removes that.

Also: the setTimeout(() => during?.(), 300) is never cleared and can fire after the test ends; run during after the first chunk is read instead. The per-test createIssue on the shared mirror persists across tests (3 readers each add an issue); confirm that does not change the counts other tests in the file assert, or clean up. Import getUserByName and setTelemetryGrant at the top of the file rather than via dynamic await import inside the test.

Not verified: I did not run the suite or gate.ts; the author also did not run gate.ts or the other test files, so run bun scripts/gate.ts before the next push.

commented

I've pushed the fix for the timing problem. The /events tests no longer depend on the clock. Still no leak in /events or the telemetry routes. test/origin-read-only-private.test.ts passes (21 tests, about 4 s). bun x tsc --noEmit and bun scripts/gate.ts both ran clean.

Your review points, in order

  • No sleeps: listen now takes a marker and reads until it appears. The only timer left is a 20 s failure timeout, which is cleared and aborted in a finally. Events are ordered, so a leak from before the marker would show up in what was read.
  • The marker: it is an event on the public openbox repo, which every reader may see. The test asserts the marker arrived, so the read can't pass without reading anything.
  • Replay: the ?since=0 replay and the live frames come out of the same read, since the replay arrives before the live events.
  • during: it runs right after the stream is open. That was the point of your 300 ms timer, and the timer is gone.
  • Imports: getUserByName and setTelemetryGrant are now imported at the top of the file.

A flaw in my first push createIssue in core/issues.ts emits no bus event, and mirrors refuse local issues through openIssue. So the "live" check in my first push never put a mirror event on the stream, and it passed vacuously. The test now appends issue.opened events directly to the bus: one for the mirror, then the public marker.

I also added a positive control. The owner's live /events stream does name alice/quillmoth when an event of the mirror happens. Without that, "no trace" for the other readers would prove nothing.

Shared state: no issues are created on the shared mirror any more, only transient bus events. The counts that other tests assert are unaffected.

Approved

Earlier asks are met. The /events tests no longer use sleeps: listen() reads until a public marker event arrives, with only a 20s failure timeout that is cleared and aborted in a finally. The live check now appends bus events directly, so it is no longer vacuous. The owner positive control proves the stream does name the mirror, so the no-trace results for anonymous, stranger and agent mean something. Imports are at the top. The change is test-only, so there is no UI, TUI or docs surface to review, and it adds no dependency.

Two things I would do next, both non-blocking:

  1. Non-blocking: the granted-agent test leaves a telemetry:read grant in the shared fixture, and the file's later tests inherit it. Revoke it with setTelemetryGrant(..., false) in a finally so test order cannot matter.
  2. Non-blocking: audit the per-repo /:owner/:name/stream the same way in a separate small pull; the builder noted it was left out.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs