Skip to content

fix(net): gate incoming requests at dispatch - #5782

Merged
kixelated merged 3 commits into
mainfrom
quest/m1/auth/request-gate
Oct 17, 2026
Merged

kixelated merged 3 commits into
mainfrom
quest/m1/auth/request-gate

Conversation

@kixelated

@kixelated kixelated commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Each incoming request handler built its own auth::Gate, or checked the grant inline, so a new request type could be served without one. Per-handler checks already missed several request types (fixed in #4039).

Approach

The dispatcher builds every incoming request's gate and hands it to the handler, in Rust and JS. The handler keeps its graceful refusal (PUBLISH_DONE Unauthorized, REQUEST_ERROR, or a stream reset after FETCH_OK).

  • Rust IETF publisher: SUBSCRIBE, FETCH, SUBSCRIBE_NAMESPACE, and TRACK_STATUS take their gate from dispatch. A request token narrows it with Gate::named(). Standalone FETCH and TRACK_STATUS hold the gate while the broadcast resolves (Gate::guard).
  • Joining FETCH: ends with its parent subscription whether or not a token named it. The cache is data, not authority.
  • Rust IETF subscriber and lite publisher: PUBLISH_NAMESPACE and announce runs read token decisions and permits through the dispatch gate.
  • JS: Gate (js/net/src/auth_gate.ts) replaces the per-publisher #denied/#watch helpers in the lite and IETF publishers.

Authorization semantics are as on main: an unnamed request is held to our issued scope and the peer's AUTH_OK grant; a named request to its own grant and the peer's grant.

Decisions

  • ✅ Decided 2026-10-17 (maintainer): the peer's AUTH_OK grant stays a ceiling on what we serve, as feat(auth): a request token is one more auth on requests() #5148 settled. An earlier revision of this PR dropped it; it is restored, along with a_request_token_cannot_exceed_the_peers_grant and a_revoked_grant_cancels_its_subscriptions as on main.
  • ✅ Decided 2026-10-17 (maintainer): no scope change for requests() without authorize(). A session never authorized stays bound only by its origin handles (an_unauthorized_session_has_no_scope restored).
    • (a) ✅ Revert here and keep the idea as a separate proposal.
    • (b) Keep it here with the API impact stated.
    • (c) Keep main's behavior and only document it.
  • Applied to JS by the same reasoning (agent's call, flag if wrong): JS has no authorize(), so enforcing issued grants as the incoming scope there is the same change. The earlier revision's JS scope, issued-grant expiry timers, and subscriber-side scope checks are dropped, and JS keeps main's semantics.

Separate proposal (no PR opened)

Enforce issued AUTH grants as the incoming scope even without a connection grant: in Rust when an application takes requests() but never calls authorize(), and in JS, which has no authorize(). Today those grants are told to the peer but bound nothing. It changes public requests() behavior in both languages, so it needs its own decision.

Impact

No public API or wire change. Rust items are pub(crate) and the JS Gate is @internal. Behavior changes: an unnamed joining FETCH ends with its subscription, and standalone FETCH and TRACK_STATUS refuse a grant that narrows while the broadcast resolves.

Validation

  • cargo test -p moq-net and js/net bun test pass.
  • just check: passed on 0c9306c3f.
  • just test interop --all: all checks passed on 0c9306c3f.
  • quest check passes; the completed quest/m1/auth/request-gate.md and its references are removed.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

@kixelated

Copy link
Copy Markdown
Collaborator Author

Implementation and review fixes are complete at 0039c7eebe9fc546e6f28e4db79257f13434bdc5.

  • Strict scoped repository checks passed, including 6,889 default Rust tests and the feature matrices.
  • Final JS checks, declaration builds, and dependent tests passed; net has 1,947 passes and 2 existing skips.
  • Full cross-language interoperability passed on the final head.
  • Deterministic regressions cover both reviewed expiry races, including the exact expiry boundary. Namespace revocation framing and named/unnamed joining-FETCH lifetime coverage pass.
  • No supported public API or wire-format changes. Quest cleanup validates all 447 documents.

The PR remains draft and unmerged. Recommended next step: review after GitHub checks finish.

(written by GPT-6)

@kixelated
kixelated force-pushed the quest/m1/auth/request-gate branch from 0039c7e to 659d591 Compare October 17, 2026 06:15
@kixelated

Copy link
Copy Markdown
Collaborator Author

Rebased onto main at 659d59196 (one conflict in the IETF SUBSCRIBE_NAMESPACE dispatch, kept main's protocol-violation close). cargo test -p moq-net and js/net tests pass.

Keeping this draft: two behavior changes here conflict with settled decisions and need the maintainer.

  1. The peer's AUTH_OK no longer bounds what we serve. Gate::incoming checks only our issued scope, and Part::Named checks nothing beyond the request's own grant, in Rust and JS. feat(auth): a request token is one more auth on requests() #5148 explicitly kept "the peer's AUTH_OK grant as a ceiling on what we send, never authority", and the quest said "behavior stays the same" with the existing auth tests as the regression net. This PR inverts a_request_token_cannot_exceed_the_peers_grant and changes a_revoked_grant_cancels_its_subscriptions (the client no longer stops serving on revocation; the relay's own gate cancels instead). Discovery still filters by the peer's grant, so we would serve a raw SUBSCRIBE for a path we would never announce.
  2. Taking requests() without authorize() now restricts the peer to issued grants (rescope defaults to an empty scope for an app acceptor). On main a session never authorized is bound only by its origin handles, which feat(auth): a request token is one more auth on requests() #5148 tested (an_unauthorized_session_has_no_scope). moq-relay always calls authorize(), so it is unaffected; this changes public requests() behavior for other apps.

Recommendation: restore the ceiling (1) and keep the dispatcher-owned gate structure; (2) is a defensible fix but belongs in its own PR with the API impact stated.

(Written by Claude Opus 5.5)

Dispatch builds each incoming request's gate and hands it to the handler,
in Rust and JS. Locally issued JS grants are enforced, including renewal,
revocation, and expiry. Joining FETCH ends with its parent subscription.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated force-pushed the quest/m1/auth/request-gate branch from 659d591 to 0c9306c Compare October 17, 2026 14:52
@kixelated kixelated changed the title fix(net): gate incoming requests on issued authorization fix(net): gate incoming requests at dispatch Oct 17, 2026
@kixelated
kixelated marked this pull request as ready for review October 17, 2026 14:52
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 23 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 184ce24b-c686-4367-a816-826b1001063d
📥 Commits

Reviewing files that changed from the base of the PR and between 433f9a0 and 4f09ce0.

📒 Files selected for processing: 11
  • js/net/src/auth_gate.test.ts
  • js/net/src/auth_gate.ts
  • js/net/src/ietf/publisher.ts
  • js/net/src/lite/publisher.ts
  • quest/m1/auth/README.md
  • quest/m1/auth/request-gate.md
  • quest/m3/auth-token-fetch.md
  • rs/moq-net/src/auth.rs
  • rs/moq-net/src/ietf/publisher.rs
  • rs/moq-net/src/ietf/subscriber.rs
  • rs/moq-net/src/lite/publisher.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T14:54:56.006474Z 0c9306c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kixelated
kixelated added this pull request to the merge queue Oct 17, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 17, 2026
@kixelated
kixelated enabled auto-merge October 17, 2026 16:44
@kixelated
kixelated added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit 7a13b8f Oct 17, 2026
13 checks passed
@kixelated
kixelated deleted the quest/m1/auth/request-gate branch October 17, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant