oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

Findings on the Meet spec, revision 5

GPT-6 via codex exec, read-only, round 5, reviewing commit bbfe440 against the four earlier findings files. Verbatim.

Reviewed revision 5 at bbfe4406b5cffaba11ab40e4d1cd6034efc2bfc4. No files modified. “Closed” assesses the design against the reported failure, not implementation or test results.

| R4 finding | Status | Assessment | |---|---|---| | R4-1 reset protocol | Half-closed | Typed reset messages, server-held generations and roster reconciliation supply coordinated recovery. Resetting sequence numbers without changing the resume cursor makes retained signalling disappear: R5-1. | | R4-2 lost replacement notice | Closed | A takeover creates a new peer; the old peer receives 410 on resume. After its tombstone expires, fresh entry still receives 409 while another participation owns the session’s room. Automatic reconnection cannot displace that participation. The new takeover operation has separate defects below. | | R4-3 authorization-table disagreements | Closed | §2.5 now permits participation-only leave, requires admitted members for admission and removal, and supplies the page-access rule that §3.3 explicitly adopts. | | R4-4 revoked-continuation response | Closed | §3.2 distinguishes the inherited 403 at Authorize from the authenticated continuation fallback. §13 matches those branches. Neither creates a grant through the revoked originating link. |

The half-closed residues from R4’s two tables, checked individually:

| Earlier residue | Status | Assessment | |---|---|---| | R3-7 downstream signalling loss | Half-closed | Replay works within one generation; recovery across generations remains defective: R5-1. | | R3-9 replacement loop | Closed | Lost replaced delivery no longer permits automatic displacement of another live participation. | | R2-4 resume/restart | Closed | Admission-branched resume, roster reconciliation, connection disposal on restart and fresh-entry conflict handling answer the recorded failures. | | R2-5 delivery | Half-closed | Ordering and replay remain, but their sequence cursor cannot identify the new generation: R5-1. | | R1-9 reconnect | Closed | Admission survives ordinary renewal; missed departures reconcile; restart closes old connections; stale tabs cannot automatically take over. | | R1-10 negotiation | Half-closed | Perfect negotiation and renewal completion remain specified, but reset recovery can withhold the offer needed to complete negotiation: R5-1. |

R5-1 — P2 — §§4.5–4.6: The resume cursor cannot distinguish generations after sequence numbers restart.

last maps a sender ID to a sequence number. The hub replays only messages above that number. A pair rebuild resets both browsers’ sequence counters, but the resume request carries no generation. Resume cursor and replay filter, counter reset.

A concrete failure:

  1. A has received B’s generation-1 messages through sequence 20, then loses its stream.
  2. B triggers a reset. The hub advances the pair to generation 2. B receives pair, rebuilds and sends its new offer at sequence 1. The hub accepts it and retains it for A.
  3. A resumes with last[B]=20. Its roster says generation 2, so A rebuilds. The hub nevertheless suppresses B’s retained offer because 1 <= 20.

Resetting A’s local receive counter when processing the roster cannot change the cursor already supplied to the server. If B is the impolite peer and still has its local offer outstanding, B ignores A’s colliding offer. A never receives the offer it must answer. The protocol has lost an accepted, retained message without necessarily emitting gap.

The server also cannot infer whether 20 belongs to generation 1 or generation 2. Both are valid browser states under the declared wire format. The added browser gate expressly requires recovery when pair was missed and its generation is learned through the roster; it needs this missing distinction. Recovery gate.

Correction: Carry {gen, seq} per sender in last, and scope replay, gap detection and receive deduplication to that generation. When the client’s generation is older, send the authoritative generation and replay its retained messages from the beginning, or explicitly initiate another coordinated reset if that history is incomplete. Alternatively, keep sequence numbers monotonic across pair resets. Add a slice-2 test with a nonzero old cursor, a missed pair, and a new-generation offer accepted before resume.

R5-2 — P1 — §§4.2–4.3: Taking over a waiting participation broadcasts its departure into the call.

The new takeover procedure unconditionally sends gone for the old peer to admitted peers. It does not branch on whether that peer was admitted, waiting or unjoined. Takeover procedure.

A waiting guest opens a second tab and chooses takeover. The guest’s old waiting peer now produces gone for everyone admitted, including admitted guests who are not entitled to see the waiting queue. This discloses a waiting peer’s existence and lets an unadmitted participant inject a call-presence event. Members also receive the wrong event for removing the old knock.

This recreates R1-1’s waiting-departure failure through a new endpoint. Ordinary leave correctly sends knock-gone only to members; the roster likewise restricts the waiting queue to members. Departure permissions, queue visibility.

Correction: Make takeover use the same state-dependent departure operation as leave and removal: admitted → gone to admitted peers; waiting → knock-gone to admitted members; unjoined → no room announcement. Test all three states, with an admitted guest observing the room.

R5-3 — P1 — §§4.2, 4.5: Takeover is a destructive GET that bypasses the reused CSRF protection.

GET /m/{sqid}/stream?takeover=1 ends the session’s existing participation. Saying the parameter is sent only by the button does not enforce that condition at the server. Mutation and button assumption.

The scaffold mounts rastrillo’s csrf.Protect, which expressly passes GET requests without checking their origin. The inherited session cookie uses SameSite=Lax. Scaffold middleware, GET exemption, cookie configuration.

An attacker who knows the meeting sqid can send a signed-in member through a cross-site, top-level navigation to that URL. The browser supplies the session cookie, standing succeeds, and the GET ends the member’s call in another tab. The attacker needs neither the session token nor a successful join. The unauthorized disconnection happens when the stream opens.

Correction: Move takeover to a CSRF-protected POST, with the repository-required confirmation page using ui/confirm-form. Make the ownership change atomic and return the new peer for subsequent stream attachment. A GET must not end an existing participation. Add a slice-2 browser test proving that cross-site navigation cannot trigger takeover.

The remaining checked source claims introduce no new blocking defect. The relay list and credential shape match relay §2 at dcf0cad; the three-hour credential and two-and-a-half-hour renewal schedule remain within its hard deadline. The browser APIs support replacing ICE configuration followed by restart, and candidate statistics expose the generation field used by §5.3. W3C configuration semantics, candidate statistics.

The scriptless floor remains buildable. No new dependency on a later slice was found; all three corrections belong to slice 2 and its gates.

Verification limits remain for rastrillo/rastrillo discussion 17, oficina/suite discussion 5 and the claimed live relay deployment. The supplied discussion dumps do not establish those claims.

Verdict: request changes.