Skip to content

feat(auth): auth requests carry the dialed host - #8341

Merged
kixelated merged 4 commits into
mainfrom
auth-host
Nov 9, 2026
Merged

kixelated merged 4 commits into
mainfrom
auth-host

Conversation

@kixelated

@kixelated kixelated commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

An auth server that scopes tenants by hostname reads server_name, which is the TLS SNI only. A plaintext ws:// or http:// session has no SNI, so the server can't scope it. When a client dials http:// and its QUIC attempt loses the race to the WebSocket fallback, the session arrives unscoped and a tenant token is refused. The relay already knows the dialed host (moq_tokio::server::Request::authority), but it never reaches the auth request.

Approach

moq_auth::Request gains host: the host the client dialed, without its port. HTTP reads take it from :authority, else the Host header. WebSocket, WebTransport and raw QUIC take it from the transport's authority (the URL host, or the SNI on QUIC and tls://). io_uring WebTransport takes it from the URL, else the SNI. The HTTP host is lowercased to match the URL-derived transports, whose Url::host_str lowercases a domain, so one dialed host yields one host on every transport. server_name is unchanged and stays SNI-only; server_name_is_the_sni_never_the_host now also asserts host on HTTP and WebSocket, with and without TLS, from a mixed-case Host with a port.

The 0.14 migration table's --auth-domain row now points at host: 0.14 matched the lowercased URL host, which server_name cannot reproduce over plaintext.

Also fixes the interop harness's compressed-catalog lanes, broken on main: moq-dev/moq#8266 renamed the JS reader's line to compression=<algo> and left the harness expecting compress=<bool>.

Impact

  • moq_auth::Request::host (Rust) and host in the JS @moq/auth contract: a new optional field, additive on the wire. Older servers ignore it.
  • doc/bin/relay/auth.md documents it next to server_name.

Alternatives

  • Fill server_name from Host over plaintext. Rejected: server_name means "the name that selected the certificate", and servers rely on that.
  • Require TLS for hostname-scoped deployments. That leaves plaintext WebSocket fallback unscopable.

Validation

  • just check: passed.
  • just test interop --all: 191 PASS, 0 FAIL on the final code.

Follow-ups

None.

Models

Size: S
Implementer: Claude Code, claude-opus-5-5, unknown effort
Implementer: Claude Code, claude-opus-5-5, medium effort (iterate, S)

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1bf46-7a3d-7800-94c9-df6776633453, round 1
  • Head: 13f525f
  • Base: origin/main at 6724807
  • Score: 9/10, Source review found no actionable defects and supports landing, with builds and tests intentionally not run.
  • Findings: none

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

coderabbitai Bot commented Nov 9, 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 2 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

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c9b19af3-6498-4a31-b439-5f66751d59ab
Commits

Reviewing files that changed from the base of the PR and between 4e76fe9 and 13f525f.

Files selected for processing: 10

  • doc/bin/relay/auth.md
  • js/auth/api/index.api.md
  • js/auth/src/contract.test.ts
  • js/auth/src/contract.ts
  • rs/moq-auth/CHANGELOG.md
  • rs/moq-auth/src/request.rs
  • rs/moq-relay/src/auth.rs
  • rs/moq-relay/src/uring.rs
  • rs/moq-relay/src/web.rs
  • test/interop/interop.sh
  • 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

@kixelated kixelated left a comment

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.

Automated review by review (OpenAI)

Reviewed head fae583d. No actionable findings.

Checked optional host serialization/schema compatibility, HTTP authority/Host extraction without ports, transport authority forwarding and io_uring fallback. server_name remains SNI-only, and the documentation correctly treats host as client-chosen rather than authenticated identity. HTTP and WebSocket regression assertions cover both plaintext and TLS admission.

Verification limits: GitHub-only static source/diff review. No builds, tests or live transport/auth integration runs executed.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — two minor suggestions.

Reviewed changes

  • host on the auth request: moq_auth::Request gains an optional dialed host beside server_name, documented as client-chosen and present wherever a host exists, including plaintext.
  • Relay population: request_for forwards moq_tokio::Request::authority() (already host-only on every transport), the one-shot HTTP read handlers derive it from :authority/Host, and io_uring falls back to the SNI.
  • JS, docs, and tests: the @moq/auth contract and generated API report gain the optional field, doc/bin/relay/auth.md explains it, and server_name_is_the_sni_never_the_host now asserts host on plaintext and TLS HTTP and WebSocket.
  • Compatibility: the field is an optional, serde-skipped addition, so existing auth servers and the fixed interop vectors are unaffected.

ℹ️ The --auth-domain migration row still names server_name

The new paragraph says host is what a plaintext-scoped server should read, but the 0.14 migration table at doc/bin/relay/auth.md:505 still tells a --auth-domain user that their server reads server_name. A deployment that migrates that way keeps refusing plaintext sessions, which is the case this PR exists to fix.

Technical details
# `--auth-domain` migration guidance names the field that cannot cover plaintext

## Affected sites
- `doc/bin/relay/auth.md:505` — the `--auth-domain` row says "your server reads `server_name` and decides".

## Required outcome
- The migration row names `host` (or both, with the plaintext caveat), so a migrating deployment does not land on SNI-only scoping.

## Open questions for the human
- Whether 0.14's `--auth-domain` matched the SNI, the `Host` header, or both decides whether `host` alone is the right replacement here.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Comment thread rs/moq-relay/src/web.rs Resolved
kixelated and others added 2 commits November 8, 2026 22:55
…gration at host

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#8266 renamed the JS reader's log line from compress=<bool>; the harness kept
the old string, so the compressed-catalog lanes failed on main.

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

Copy link
Copy Markdown
Collaborator Author

Thanks. Both findings are addressed:

  • --auth-domain migration row: it now points at host, which is present over plaintext too (7cb3cc1). 0.14's --auth-domain matched the connection's dialed name, so host is the right replacement.
  • host casing: fixed in the inline thread.

Separately, the compressed-catalog interop lanes were failing on main. #8266 renamed the JS reader's log line, but the harness still expected the old string. This PR updates the expected string, and just test interop --all now passes.

(Written by Claude Opus 5.5 with medium effort)

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review (fae583d) — commits 7cb3cc1 and 13f525f.

  • host casing normalized on HTTP reads: dialed_host now lowercases the Host/:authority host, so an HTTP read and a WebSocket/WebTransport dial of Tenant.Example both report tenant.example. The socket test drives Host: Tenant.Example:8443 and asserts the lowercased, port-stripped value on both HTTP and WebSocket.
  • Migration doc corrected: the 0.14 --auth-domain row now points at host (present over plaintext) instead of server_name, so a migrating deployment does not land on SNI-only scoping.
  • Interop catalog lanes fixed: run_catalog_fanout now expects the reader's compression=<algo> line, matching catalog.ts after #8266 renamed it.

The remaining transports already agree on casing: special-scheme URLs lowercase the domain, and rustls lowercases the SNI it parses, so raw QUIC and tls:// report the same lowercase host.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@kixelated kixelated left a comment

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.

Automated review by review (OpenAI)

Reviewed head 13f525f incrementally from fae583d, separating inherited main changes. No new actionable findings.

Checked lowercasing of HTTP authority/Host values, the mixed-case host-with-port regression assertions, and the auth-domain migration guidance. The interop harness now matches the catalog reader's actual compression=none/deflate output. SNI remains separate from the client-chosen host.

Verification limits: GitHub-only static source/diff review. No builds, tests, browser runs or live transport/interop checks executed.

@kixelated
kixelated added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit 7cce8cf Nov 9, 2026
10 of 12 checks passed
@kixelated
kixelated deleted the auth-host branch November 9, 2026 08:34
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