live room train D: co-watch rooms, the room's own service under age rules, same-origin room endpoints (#432) #451
Loading…
Reference in a new issue
No description provided.
Delete branch "train/live-room-d"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Live-room train D: co-watch (#432) merged onto #428's live room, the age × co rule, and the room page's unclosed section.
Base: #428's head (
793bd478, then its fix9f63561b, merged in).wai.live.cowatch: watch rooms, merged by meaning<section id="co">closed; a markup test and an e2e checkHow #432 was merged
#432 conflicted in the room service, the page, the e2e suite, both room workflows, and the registry lists.
One config reader.
Config::settings()now returns aResult, because it reads the watch source's file. It carries the following:age,audience_input,latency_budget_ms,leadandai;watch(fromwatch_sourceandwatch_duration_ms),watch_controlandwatch_window_ms.public_prefix. Both sides had added it, so the auto-merge had left two fields inConfigand two keys inroom.example.toml. The example config test caught the duplicate key.e2ee,[age]or[co]:e2ee, the source is served in the clear;[age], the source is served without a grant, so no age check could apply to it;One path for the clock's records.
Item::{Life, Meta}andemit_items, which keeps the clock's rate.Lifecycle::Meta. It is dropped:emit_meta;emit_after, which carries a leastt;watch_record.Service::startspawnsrun_watchfor a watch room.Roles and grants.
metain a room with co-performance or in a watch room.WATCHER_KINDS(meta, chat). An audience member keepsAUDIENCE_KINDS.watch_controlis appended last among the grant attributes, which keeps their key order.Page and e2e.
app.jshas the co-watch mode beside co-performance and the binding.cowatch.run.shruns all the page's node tests.Workflows and lists.
paths:lists are unioned.capabilities.jsonandv2.ttlwere regenerated byregistry export.The age × co rule (age-assurance §5.5)
The problem. In a room with both
[age]and[co], the clock follows and logs every performer's objects over a relay session of its own. That session holds no grant and no token, so the relay served it nothing rated. Its forwarding SUBSCRIBE to the rated video was refused, so it never recorded the lead'sjoinand never opened its events track. Every page then waited for the clock, and the room never started. The falsified e2e below shows exactly this.The rule. A session that proves possession of a room key is the room's own service, and the relay serves it every object of the room.
GRANT_PROOF(live-rooms §10.5.2) whosegrantis 32 bytes of zero, made over the session's challenge.UNAUTHORIZED).Spec.
age-assurance.md§5.5 states the rule; §11 names the reasons it uses and §12 the new vectors.co-performance.md§8 points to it from the paragraph on the clock's own session.Relay (
joule-moq-relay).grant_proof::room_key_proofs).SessionCtx::serviceholds the rooms each proved key covers:[[auth.authorities]]covers its own rooms;[floor] room_keysor[dvr] room_keyscovers every room.threshold()is past every age for the room's own service.ClientOptions::room_keyspresents such proofs.Room service (
joule-moq-room). The clock's session proves both room keys it holds, so a relay that holds either of them recognises it.relay-linesand the README say so.wai-rs, the corpus and the Python verifier.age_assurance::room_serviceimplements the rule.room_servicehas 13 requests:pop_bad_signature), another session's challenge (pop_wrong_challenge), another relay (pop_wrong_context), a named grant (pop_wrong_grant) and a cut proof (pop_malformed).age_assurance_verify.pylearned the rule, with three new checks and four new mutations.joule-moq'stests/rating_vectors.rs, the corpus's third reader, checks those requests as the relay does.Tests.
tests/age_room_service.rsis the relay test with age and co both in play: a room rated 18 and a floor held by the clock's key.below_threshold.UNAUTHORIZED. So does the clock's proof over another session's challenge.agecois a room with both[age]and[co]:The room page's unclosed section
The cause. Train B's merge of #424 with #413 (
173981739) left<section id="co">without its</section>. The page opened five sections and closed four, so the parser nested#binding-debugand the Log inside the hidden#co. In every room without co-performance, neither ever showed.The fix (
984d95b2).index.htmlhas the missing</section>.web/room/markup.test.mjs, run byrun.sh, checks three things:#log,#binding-debugand#cowatchare outside#co;service, a room without[co], the Log shows;cowatch, the co-watch section shows outside#co.The room page takes only its own origin's endpoints (security fix,
cb766e71)The problem. The room client took its endpoints from its query string as given: the relay
(
ws,wt), the room service (service), the test room (fixture), certificate hashes(
hashes) and the issuer directory (issuer_directory). It also followed a relay's GOAWAY toany URI. A crafted link and a press of Join would send a joiner's camera and microphone to
another origin.
The rule (
web/room/endpoint.js).https://<host>, orwss://<host>for arelay.
addresses alone.
hashesare taken on a loopback page alone.The page (
app.js).snapshot().refusedand shown to the visitor.enter()checks the relay before it asks for the camera, a grant or a session.Tests.
web/room/endpoint.test.mjs(run byrun.sh) covers://evil;javascript:anddata:;origincovers three cases:?ws=is refused at Join, and nothing connects: no WebSocket, no request afterthe press, no grant, no camera;
?service=(scheme-relative, behind user information, orjavascript:) isrefused, Join stays off, and no request leaves the origin;
servicescenario now checks that every request and WebSocket of every page stays onthe page's origin. That is the production
connect-src 'self' wss://<host> blob:.Falsified. The check was dropped, so every URL that parses was taken:
endpoint.test.mjsexits 1 (5 of 6 fail);originscenario exits 1 (the page takes the foreign relay).One production note. A room with
[age]fetches its Privacy Pass token from the issuer'sorigin by design.
connect-srcmust name each issuer before[age]runs in production.Main merged, and a stale lock file
origin/main(41c5bafb, #428 landed asaa61ab3b) merged cleanly.Main's wai-quantum 0.6.14 release left
joule-moq-room/Cargo.lockat 0.6.12. With it, theroom service's
--lockedbuild refused to run.ea7efb97refreshes it withcargo metadata;the one version line changes. After that:
cargo metadata --locked --offlineholds in joule-moq-room, joule-code-rs, wai-live-web andwai-rs;
scripts/check-lockfiles.shreports 47 lockfiles, all current;--lockedinrun.sh, and its 17 tests pass.Checks (build host, through rb)
At the final head
ea7efb97:run.sh --runs 1, withorigincargo test --locked --all-targetsscripts/check-lockfiles.shwfsteps.py HEAD origin/main pr/432ruby -ryamlover.github/workflows/*.ymlBROWSER_BINlineAt
5aacacfa, before the fix and the main merge:The full cargo and verifier run was at
298eaa70. The commits after it touch onlyjoule-moq/tests/rating_vectors.rsandwai-conformance.yml(from9f63561b). Three things were run again after them:9a7f953f: joule-moq's tests and the e2e, twice;5aacacfa, which changes only one assertion's form in that test: joule-moq's clippy andrating_vectors;e2e/run.sh --runs 2: compose, webtransport, websocket, goaway, dvr, service, e2ee, age, co, ageco, binding, cowatchnode --testover capsule, media, presentation, privacy-pass, binding, cowatch, markupcargo test -p joule-moq --features quic --all-targets;--features secure-objectscargo test -p joule-moq-relay --all-targets--testdvr, floor, age_rising, age_serving, age_room_service, publish_done_streamscargo test --all-targets--lib;--features e2ee --lib; ingest--all-targets; interop--all-targetscargo build -p joule-moq-web --target wasm32-unknown-unknown--all-targets; wai-rsage_assurance,co_watch --all-targetsmoqt22_check.py,moqt18_check.py,moqt16_check.pyage_assurancelib, test;co_watchlib, testwai_age_assurance_vectors verify;wai_co_watch_vectors verify;wai_browser_sink_vectors verify--lib codecs::(both feature sets); corpus_provenance, cli_registry; int_kernels digestsinclude/wai.hwai registry export --check--all-targets;--features int_video --liblive-conformance/*_verify.py(26 of them),co_watch_verify.pyamong themcargo metadata --locked --offlinein wai-rs, wai-live-web, joule-code-rs, joule-moq-roomwfsteps.py HEAD 9f63561b pr/432ruby -ryaml -e 'ARGV.each{|f| YAML.load_file(f)}' .github/workflows/*.ymlcheck-public-names.shover the 56 changed files; commit messages and this bodyjoule-moq-web.yml'sBROWSER_BINpath, unchanged from #428 and outside the script's own scopeFalsifications
Each was committed first, then broken on the build host's copy, then restored.
threshold()age_room_serviceexits 101: the session proving the clock's key is refused its SUBSCRIBE. Theagecoe2e exits 1: the clock never opens its events track, and the lead's page times out (no publisher within RENDEZVOUS_TIMEOUT)age_room_serviceexits 101: the ordinary session is servedwai-rsroom_servicenever the room'stests/age_assurance.rsexits 101: the corpus no longer regenerateswai-rsroom_serviceby any keytests/age_assurance.rsexits 101service_room_key,service_proof,service_no_grantdroppedroom_service(a byte of the clock's proof; no proof, the performer's proof and the forged proof each stated as the room's service)index.html's</section>removed againmarkup.test.mjsexits 1. The e2eservicescenario (unit test taken out ofrun.sh) exits 1 on the markup check alone,in_co: ["log", "binding-debug"]andlog_shows: false, 41 of 42 checks okDecisions
[floor].grant. In the relay,pop_wrong_grantnever applies to such a proof. The corpus keeps that reason for a session that presents a grant-naming proof as its room-key proof.[age],[co]ore2ee. That is at configuration time, not left to undefined behaviour.Not done
traind-deploy.md: the page files and thesync-room.mjsmapping (co.js,binding.js,privacy-pass.js,cowatch.js), theroom.astrosections for co-performance, binding and co-watch, three wasm packages, the config keys, and the binaries./watch/sourcewould have to be gated by a grant before a watch room can admit by age. The service refuses that configuration until then.room.astroneeds the co-watch markup. The site worktree that adds co-performance and binding does not have it.Main merged again (head
c6c3bce4): main's76504e21(#443, the advisory backlog across every workspace, and thecargo auditgate) merged cleanly.scripts/check-lockfiles.sh: 47 lockfiles, all current.cargo metadata --locked --offlineholds in joule-code-rs, joule-moq-room, wai-rs and wai-live-web.--all-targets, joule-moq-room--all-targetsand wai-live-web--libpass.🤖 Generated with Claude Code
9f63561b(wai-conformance: the age, direction and takedown steps get their own bodies back) 9a7f953f2241c5bafb, #428 landed asaa61ab3b) into train/live-room-d afad601c40