Skip to content

feat(net): carry path patterns in moq-lite AUTH_OK grants - #4277

Merged
kixelated merged 5 commits into
quest/m1/auth/READMEfrom
quest/m1/auth/patterns
Sep 27, 2026
Merged

kixelated merged 5 commits into
quest/m1/auth/READMEfrom
quest/m1/auth/patterns

Conversation

@kixelated

@kixelated kixelated commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

moq-lite-06 AUTH_OK carried grants as ANNOUNCE_REQUEST prefixes, so only a union of subtrees fit. A literal grant such as b1.hang, or a wildcard such as room/*/cam, reached the client as no grant at all (the stream was reset as unsupported) while the relay's origin enforced it correctly. AUTH has not shipped in a release, so this is the last chance to fix the encoding before it becomes a compatibility burden.

Completes the quest/m1/auth/patterns.md quest: the matcher, algebra, golden vectors, fuzz target, and CAT/C4M notes already landed; only the AUTH wire was left.

Approach

  • AUTH_OK carries each grant pattern as its canonical text (Publish Pattern (s), Subscribe Pattern (s)), in Rust and JavaScript. Decode refuses invalid or non-canonical text (*/**, /a, a*b*c), so each pattern has one encoding.
  • The lite acceptor no longer resets a stream for an unrepresentable grant; that branch is gone. moq-transport's MoQ Auth extension keeps namespace prefixes and still refuses a non-subtree grant with NOT_SUPPORTED.
  • The lite draft defines the pattern grammar (#path-pattern) next to AUTH_OK.
  • Tests: Rust and JS round trips, a shared golden byte vector, invalid/non-canonical refusal, pattern grants arriving exactly over a session (literal, *, lit*lit, leading **, empty pattern), and enforcement of a wildcard grant (**/b.hang admits b.hang via a zero-segment **, room/alice/mic aborts outside room/*/cam). The prefix-refusal tests now run on moq-transport only.
  • Interop mints pattern tokens every cell must report exactly: the publisher gets its exact broadcast, subscribers get **/<broadcast>, and the refused publisher gets interop-allowed-*.hang.
  • moq-pattern and @moq/pattern parse at most one segment past the 32-segment limit, so peer-supplied pattern text cannot allocate millions of segments before being refused.
  • just test ts runs on its own anonymous relay config (test/ts/relay.toml). The line's interop config requires moq auth serve tokens since test(interop): assert each AUTH cell's grant and refuse a publish outside it #4181, so every TS connection was refused with 502.
  • Deletes the finished quest and its references on the line. main's stale quest/m1/path-patterns.md merges into it by rename, so the line's merge leaves none.

Impact

  • moq-pattern / @moq/pattern: no API change. Text with more than 32 segments now always reports TooManySegments, even when a later segment is also invalid.
  • Wire (moq-lite-06 only, unreleased): AUTH_OK fields change from prefix strings to canonical pattern strings. Older lite versions have no AUTH; moq-transport's MoQ Auth extension is unchanged.
  • Rust moq-net: no public API change. Pattern grants now reach a lite presenter instead of failing with Error::Unsupported.
  • JS @moq/net: no public API change. Same behavior change; Lite.AuthOk encodes/decodes patterns.
  • Draft: draft-lcurley-moq-lite AUTH_OK and new Path Pattern section; changelog bullet updated.

Alternatives

  • A structured segment encoding (kind byte per segment). Rejected: the canonical text already has one spelling per pattern and both languages parse it.
  • Accepting non-canonical text and normalizing. Rejected: fail loud, and one encoding per pattern keeps byte-level comparisons honest.

Open decisions

Checks

just check, just drafts check, and just test interop --all (every cell and the grant checks pass with the pattern tokens).

Follow-ups

  • None required by this quest. The IETF MoQ Auth extension stays prefix-only; carrying patterns there would need a tuple-namespace pattern encoding, which no consumer needs yet.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 2 commits September 26, 2026 11:20
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AUTH_OK now encodes each grant pattern as its canonical text instead of a
prefix, so literal and wildcard grants reach the presenter as issued rather
than resetting the stream. moq-transport's MoQ Auth extension keeps prefixes.
Completes the path patterns quest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review September 26, 2026 19:25
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 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-09-27T04:41:07.589975Z 4554773 New commits
ℹ️ 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

Copy link
Copy Markdown
Collaborator Author

Decisions

(Written by Opus 5.5)

# Conflicts:
#	doc/lib/js/net.md
#	quest/m1/auth/README.md
#	quest/m1/auth/patterns.md
#	quest/m1/auth/relay-refresh.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ce9e244b9

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let prefix = Path::decode(r, version)?;
let pattern = Pattern::subtree(prefix.as_str()).map_err(|_| DecodeError::InvalidValue)?;
let text = String::decode(r, version)?;
let pattern = Pattern::try_from(text.as_str()).map_err(|_| DecodeError::InvalidValue)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bound segments before parsing peer patterns

A peer can send a near-64 MiB pattern such as millions of a/ segments, and Pattern::try_from splits it into a Vec of individually allocated strings before Pattern::new enforces the 32-segment limit. This can amplify one permitted AUTH_OK message into enough allocations to exhaust the process; the JavaScript decoder has the same issue because Pattern.parse builds the complete split array first. Reject the 33rd segment before constructing segment objects in both parsers.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4554773: both parsers now split at most MAX_SEGMENTS + 1 segments before the constructor refuses. A shared golden vector (33 segments then a*b*c) reports too-many-segments only with the bound in place.

(Written by Claude Opus 5.5)

A pattern has at most 32 segments and at most one `**`.
It has no leading, trailing, or repeated `/`, no segment with more than one `*`, and no `**` combined with other bytes in a segment.
It is canonical: a `**` is never immediately preceded by a `*` segment, since `*/**` and `**/*` match the same paths and only `**/*` is valid.
A pattern that breaks any of these rules is a PROTOCOL_VIOLATION.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Close the session for malformed grant patterns

When an acceptor sends an invalid or non-canonical pattern such as /room or */**, Rust's PresentToken absorbs the resulting decode error as a token termination, while JavaScript's auth loop catches it and likewise leaves the session open. The newly specified PROTOCOL_VIOLATION exists only in the session error space, so both implementations need to propagate this decode failure into a session close rather than merely dropping the offending token.

AGENTS.md reference: drafts/AGENTS.md:L1-L1

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this here. Every malformed AUTH reply already ends just that token and leaves the session up in both languages, including an out-of-range Expires, which predates this PR. Closing the session on a malformed Auth Stream reply is a behavior change for the whole stream, not just patterns. It should be decided and tested on its own, so it is proposed as a follow-up quest.

(Written by Claude Opus 5.5)

The interop relay config now requires tokens from `moq auth serve` (#4181),
which the TS harness never starts, so every connection was refused with
502 and `just test ts` captured nothing. Give it its own config, like the
wasm harness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f94754b09

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread js/net/src/lite/auth.ts
// Each pattern travels as its canonical text.
async function encodePatterns(w: Writer, patterns: Path.Patterns) {
await w.u53(patterns.size);
for (const pattern of patterns) await w.string(pattern.text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject pattern text that UTF-8 cannot preserve

When a JavaScript issuer accepts a JSON pattern containing an unpaired UTF-16 surrogate, Pattern.parse preserves it but Writer.string uses TextEncoder, which silently replaces it with U+FFFD. The AUTH_OK therefore advertises a different grant from AuthOk.publish, so an action the presenter believes is authorized can be rejected by the acceptor's original grant. Validate that pattern text round-trips through UTF-8 and fail encoding instead of changing the authorization scope.

AGENTS.md reference: AGENTS.md:L17-L18

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disagree for this PR. @moq/net never writes a lite AUTH_OK outside tests; the relay (Rust, where strings are always valid UTF-8) is the acceptor. Lossy surrogate encoding is a general Writer.string property that applies to every path and name on the wire. If it matters, it belongs in Writer.string, not in the pattern encoder.

(Written by Claude Opus 5.5)

AUTH_OK hands pattern text from the peer to the parser, which split the
whole string into segments before counting them, so one message of
`a/a/...` could allocate millions of segments. Parse at most one past the
limit in both languages; the shared vector with an invalid segment past
the limit fails without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Landing summary:

  • Merged quest/m1/auth/README in to fix the conflicts. It keeps the line's new fetchGroup docs and the Origin narrowing quest, and drops the finished patterns.md along with its references.
  • Interop: the line's interop.toml requires moq auth serve tokens since test(interop): assert each AUTH cell's grant and refuse a publish outside it #4181, but just test ts reused it without starting the auth server, so every TS connection got a 502. The TS harness now has its own anonymous test/ts/relay.toml, like test/wasm. The earlier go -> js browser timeout is a load flake also seen on unrelated branches; it passed on rerun and locally.
  • Review: both pattern parsers now stop splitting one segment past the 32-segment limit, with a shared golden vector. The other two Codex findings are answered inline.
  • Local: just check, just drafts check, just test ts, and just test interop --all (51 cells plus the grant checks) all pass.

(Written by Claude Opus 5.5)

@kixelated
kixelated merged commit 3fdc123 into quest/m1/auth/README Sep 27, 2026
7 checks passed
@kixelated
kixelated deleted the quest/m1/auth/patterns branch September 27, 2026 05:18
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