Skip to content

web: relay token minting endpoint (POST /api/relay/token) - #7879

Merged
lawrencecchen merged 8 commits into
mainfrom
feat-relay-token-api
Jul 11, 2026
Merged

lawrencecchen merged 8 commits into
mainfrom
feat-relay-token-api

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 11, 2026 •

Copy link
Copy Markdown
Contributor

Backend-only. Implements the web-API side of the private iroh relay: the endpoint that mints the access token cmux clients present to the JWT-gated relay fleet. No iOS/macOS app changes.

What

POST /api/relay/token (Node runtime). A signed-in cmux endpoint gets back:

{ "token": "<EdDSA JWT>", "expiresAt": 1783..., "ttlSeconds": 300,
  "relays": ["https://usc1.relay.cmux.dev", ...7 URLs...] }
  • Auth: Stack Bearer + X-Stack-Refresh-Token (same native-client path as /api/devices), via verifyRequest. Unauthenticated → 401.
  • Token: short-TTL (5 min) EdDSA JWT with iss=cmux, aud=cmux-relay, sub=<user id>, iat, exp, and optional endpoint_id (anti-replay binding to the client's iroh key). Signed with node:crypto Ed25519 — no new dependency.
  • RelayMap in the same call: returns the 7-region fleet so the client learns where to connect without a second request. Overridable via CMUX_RELAY_URLS.
  • Safe to deploy first: returns 503 relay_token_not_configured until CMUX_RELAY_JWT_PRIVATE_KEY_PEM is set, so shipping this before provisioning the secret is a no-op.

Why

The relay fleet is live and gated; each relay holds only the Ed25519 public key and verifies tokens offline. This endpoint is the issuer holding the private key. It's the last missing piece before the client (separate PR #7826, which already threads the token through the bind FFI) can actually authenticate — that wiring stays nil until this ships.

Config

  • CMUX_RELAY_JWT_PRIVATE_KEY_PEM — the Ed25519 PKCS8 private PEM (the match to the public key baked into the relays). Set in Vercel env / secret store. In dev it's the contents of ~/.secrets/cmux-relay/jwt-private.pem.
  • CMUX_RELAY_URLS (optional) — comma-separated override of the returned RelayMap.

Tests

web/tests/relay-token-route.test.ts (bun): the minted token verifies against the matching public key (i.e. a relay would accept it), plus 401 (unauthenticated), 400 (malformed endpoint_id), 503 (key unset), endpoint_id binding, and the CMUX_RELAY_URLS override. bun run typecheck clean.

Follow-up (not here)

Populate relayAuthToken at the client composition roots (PR #7826) to call this endpoint and attach the token; set the client RelayMap from the relays field.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

High Risk
Introduces a new credential-issuing API backed by a private signing key; misconfiguration or weak rate-limit setup could enable abuse, though endpoint binding and fail-closed Vercel limits mitigate replay and unbounded minting.

Overview
Adds POST /api/relay/token so native cmux clients can mint short-lived Ed25519 JWTs for the private iroh relay fleet and receive the relay URL map in the same response.

Auth matches /api/devices: Stack Bearer + refresh header only (allowCookie: false). On Vercel, per-account @vercel/firewall rate limiting is mandatory and fail-closed (missing rule, errors, or throws → 503); local dev skips it. Requests must include a valid endpointId (64-char hex or canonical 52-char RFC base32); tokens bind endpoint_id in the JWT to limit replay. Minting uses CMUX_RELAY_JWT_PRIVATE_KEY_PEM; if unset or non-Ed25519 → 503 so deploy-before-secret is safe. Logic lives in services/relay/token.ts with an injectable handleRelayTokenRequest for tests; web/tests/relay-token*.test.ts cover auth, limits, validation, and signature verification.

Reviewed by Cursor Bugbot for commit 399e124. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Adds POST /api/relay/token to mint short‑lived Ed25519 JWTs for the private relay and return the relay map in one call, with mandatory endpoint binding and a fail‑closed per‑account rate limit on Vercel. Safe to deploy; returns 503 until the signing key is set and when the rate limiter is unavailable.

  • New Features

    • Native‑only auth: Stack Bearer + X‑Stack‑Refresh‑Token; cookies refused (401 if missing).
    • Body: JSON object with endpointId; exact 64‑hex or 52‑char RFC 4648 base32 (A‑Z2‑7), requiring the canonical final symbol (a or q); non‑object → 400; >4 KiB → 413. Encoding validated; relay enforces curve validity.
    • Token: 5‑minute EdDSA JWT (iss=cmux, aud=cmux‑relay, sub, iat, exp, endpoint_id=lowercased); signed with node:crypto Ed25519. Only an Ed25519 key is accepted.
    • Response: { token, expiresAt, ttlSeconds, relays }; relays default to the 7‑region fleet, overridable via CMUX_RELAY_URLS.
    • Rate limiting: per‑account issuance via @vercel/firewall, keyed by user id; 429 when exceeded. On Vercel, missing/not‑found/errors/throws → 503; local dev bypasses.
  • Migration

    • Set CMUX_RELAY_JWT_PRIVATE_KEY_PEM to the Ed25519 PKCS8 private PEM.
    • On Vercel, set CMUX_RELAY_TOKEN_RATE_LIMIT_ID (mandatory; otherwise 503).
    • Optionally set CMUX_RELAY_URLS to customize relay URLs.

Written for commit 399e124. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Issued short-lived, EdDSA-signed relay access tokens for authenticated native clients.
    • Tokens can be bound to a specific endpoint ID and return token, expiresAt, ttlSeconds (300s), and the applicable relay URLs.
    • Added endpoint ID validation and enforced a maximum request payload size.
    • Added per-account rate limiting with Vercel fail-closed behavior.
  • Bug Fixes
    • Clear error responses for unauthorized access, missing relay token configuration, invalid/missing endpoint IDs, malformed JSON, non-object bodies, and oversized requests.
  • Tests
    • Added unit and route-level tests covering token signatures/claims, relay URL selection, validation, configuration handling, and rate-limiting scenarios.

POST /api/relay/token: a signed-in cmux endpoint gets a short-TTL (5 min) EdDSA
JWT (iss=cmux, aud=cmux-relay, sub=<user>, optional endpoint_id binding) plus the
fleet RelayMap in one round-trip. The web API is the token issuer holding the
Ed25519 PRIVATE key (CMUX_RELAY_JWT_PRIVATE_KEY_PEM); each relay holds only the
public key and verifies offline. Returns 503 if the key is not provisioned, so
it is safe to deploy before the secret is set. Signed with node:crypto (no new
dependency). Backend-only; no iOS/macOS app changes.

Test proves the minted token verifies against the matching public key (i.e. the
relay would accept it) and covers 401/400/503/endpoint_id/RelayMap-override.
@vercel

vercel Bot commented Jul 11, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 11, 2026 12:25pm
cmux-staging Building Building Preview, Comment Jul 11, 2026 12:25pm

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an authenticated Next.js route that mints short-lived Ed25519 relay JWTs, with endpoint validation, rate limiting, signing-key configuration, relay URL resolution, and comprehensive route and token tests.

Changes

Relay token minting

Layer / File(s) Summary
Token contracts and signing configuration
web/services/relay/token.ts
Defines relay URL resolution, endpoint ID validation, cached Ed25519 key loading, JWT claims, and EdDSA token signing.
Authenticated route handling
web/app/api/relay/token/route.ts
Authenticates requests, applies optional per-user rate limiting, enforces 4KB body limits, validates endpointId, handles missing configuration, and returns token metadata.
Route and token validation
web/tests/relay-token.test.ts, web/tests/relay-token-route.test.ts
Verifies signatures, claims, endpoint binding, configuration behavior, authentication, rate limiting, input validation, and response status codes.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant POST as POST route
  participant Auth as verifyRequest
  participant Limit as checkRateLimit
  participant Key as relaySigningKey
  participant JWT as mintRelayToken
  participant Relay as relayUrls
  Client->>POST: Send token request
  POST->>Auth: Verify native client request
  Auth-->>POST: Return authenticated user
  POST->>Limit: Check per-user rate limit
  POST->>Key: Load signing key
  Key-->>POST: Return Ed25519 key
  POST->>JWT: Mint token with user ID and endpoint ID
  JWT-->>POST: Return token and expiration
  POST->>Relay: Resolve relay URLs
  POST-->>Client: Return token metadata and relay URLs
Loading
🚥 Pre-merge checks | ✅ 25
✅ Passed checks (25 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed The diff touches only TypeScript relay token files; no Swift sources or isolation-related code were changed.
Cmux Swift Blocking Runtime ✅ Passed Diff touches only TS files; no Swift files or Swift blocking primitives were introduced, so the Swift blocking-runtime check is not applicable.
Cmux Browser Automation Off-Main ✅ Passed PASS: The diff only adds relay-token backend/service/tests; no browser.* routing, WebKit/AppKit wait handling, or socket-worker automation code changed.
Cmux Expensive Synchronous Load ✅ Passed PASS — diff only touches TypeScript relay token files; no Swift files or main-actor agent-history loads were added or moved.
Cmux Cache Substitution Correctness ✅ Passed No fresh-authoritative-read replacement in a persistence/history/undo/snapshot path; the only cache is memoized signing-key parsing in token minting, outside the rule’s scope.
Cmux No Hacky Sleeps ✅ Passed Touched runtime code has no setTimeout/setInterval/sleep/polling/wait helpers; token minting and route handling are deterministic.
Cmux Algorithmic Complexity ✅ Passed New code only handles fixed relay lists and single-request token minting; no nested scans, repeated sorting/filtering, or scalable in-memory joins were introduced.
Cmux Swift Concurrency ✅ Passed The diff touches only web TypeScript files; no Swift files or legacy Swift concurrency patterns were introduced.
Cmux Swift @Concurrent ✅ Passed No Swift files were changed in this PR, so the Swift @concurrent review rule is not applicable.
Cmux Swift File And Package Boundaries ✅ Passed PR touches only TypeScript and tests; no Swift files or package-boundary changes are present.
Cmux Swiftpm Lockfiles ✅ Passed PR diff only changes web TypeScript files; no Package.swift, Package.resolved, Xcode project, workflow, or cmux-owned .gitignore files were touched.
Cmux Swift Logging ✅ Passed The diff only changes TypeScript files; no Swift production code or logging changes are present.
Cmux User-Facing Error Privacy ✅ Passed User-facing responses use only generic codes (e.g. unauthorized, invalid_json, relay_token_unavailable); env vars/vendor details stay in logs/comments.
Cmux Full Internationalization ✅ Passed PASS: The PR only adds backend relay-token minting/tests; no i18n/message files changed, and new response literals are protocol/error codes, not localized UI copy.
Cmux Swiftui State Layout ✅ Passed Diff only changes backend web/token files and tests; no SwiftUI state/layout code is present.
Cmux Architecture Rethink ✅ Passed PR changes only web TypeScript files; no Swift code or architectural rethink patterns are introduced, so the Swift-specific rule is not applicable.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff is backend-only TS; no Swift/window code changed, so the auxiliary-window shortcut rule is not applicable.
Cmux Source Artifacts ✅ Passed Only intentional TS source and test files changed; no logs, caches, scratch dirs, or other source artifacts appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No Swift production Sources/ files were changed; the PR only touches web TS/test files, so the seam rule is not applicable.
Cmux No Ambient Global State ✅ Passed No Swift files changed; the PR only adds TS route/service/test code, so the Swift-specific ambient-global-state rule is not applicable.
Title check ✅ Passed The title clearly names the new relay token minting endpoint and matches the main change.
Description check ✅ Passed The description is mostly complete and covers summary, testing, config, why, and follow-up, though it omits the template's demo and checklist sections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-relay-token-api

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.

@greptile-apps

greptile-apps Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an authenticated API endpoint for issuing short-lived relay access tokens. The main changes are:

  • Native-client authentication and per-account rate limiting.
  • Endpoint-bound Ed25519 JWT creation with a five-minute lifetime.
  • Bounded JSON request parsing and endpoint ID validation.
  • Relay fleet discovery through default or configured URLs.
  • Tests for authentication, signing, validation, and failure paths.

Confidence Score: 5/5

The updated code looks safe to merge.

  • Request bodies are read through a bounded streaming helper.
  • Non-Ed25519 signing keys fail with a controlled response.
  • Endpoint IDs are restricted to canonical encodings accepted by the relay.
  • No remaining blocking issues qualify for this follow-up review.

Important Files Changed

Filename Overview
web/app/api/relay/token/route.ts Adds the authenticated relay-token route with fail-closed rate limiting, bounded body parsing, and controlled error responses.
web/services/relay/token.ts Adds Ed25519 JWT signing, signing-key checks, endpoint ID validation, and relay URL configuration.
web/tests/relay-token-route.test.ts Covers route authentication, input validation, token issuance, and rate-limit outcomes.
web/tests/relay-token.test.ts Covers JWT claims and signatures, key validation, endpoint encodings, and relay URL selection.

Reviews (8): Last reviewed commit: "web: canonical base32 endpoint-id + matc..." | Re-trigger Greptile

Comment thread web/app/api/relay/token/route.ts
Comment thread web/app/api/relay/token/route.ts Outdated
Comment thread web/app/api/relay/token/route.ts Outdated
return key;
} catch {
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PEM escaped newlines not normalized

Medium Severity

CMUX_RELAY_JWT_PRIVATE_KEY_PEM is passed straight to createPrivateKey without rewriting literal \n sequences. Other private keys loaded from env in this repo (ASC_PRIVATE_KEY, APNs .p8) normalize that form because it is common in Vercel/env stores. A key that still contains BEGIN but uses escaped newlines fails parse and surfaces as relay_token_not_configured 503.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c8fb208. Configure here.

Comment thread web/app/api/relay/token/route.ts Outdated
// Optional: bind the token to a specific iroh endpoint id (anti-replay).
let endpointId: string | undefined;
const raw = await request.text();
if (raw.length > MAX_BODY_BYTES) {

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.

P1 security Body Limit Runs After Buffering

When an authenticated caller omits or understates Content-Length, request.text() buffers the complete body before the 4 KiB check runs. A chunked request can therefore consume unbounded process memory even though the route eventually returns 413.

Comment thread web/app/api/relay/token/route.ts Outdated
Comment on lines +68 to +69
cached = { pem, key };
return key;

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.

P1 Wrong Key Type Reaches Signing

createPrivateKey accepts valid private keys that are not Ed25519. If the secret contains another key type, this branch caches it and edSign(null, ...) can throw or create a signature inconsistent with the JWT's EdDSA header, turning a configuration error into a 500 or an unusable token.

Comment thread web/app/api/relay/token/route.ts Outdated
// iroh EndpointId is an Ed25519 public key rendered as z-base-32 (~52 chars) or
// hex (64 chars). Validate permissively on charset/length; the relay is
// authoritative on parsing and on matching the handshake key.
const ENDPOINT_ID_RE = /^[0-9a-z]{40,120}$/i;

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.

P1 Endpoint Pattern Accepts Invalid Keys

This pattern accepts values that are neither a 52-character iroh z-base-32 key nor a 64-character hexadecimal key, such as 40 arbitrary alphanumeric characters. The route returns 200 with that value in endpoint_id, but the relay cannot parse or match it, so the client receives a token that fails only when connecting.

Comment thread web/app/api/relay/token/route.ts Outdated
Comment on lines +49 to +53
const urls = raw
.split(",")
.map((s) => s.trim())
.filter(Boolean);
if (urls.length > 0) return urls;

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.

P1 Invalid Relay Overrides Return 200

The override accepts every nonempty string without parsing or enforcing HTTPS. A value such as usc1.relay.cmux.dev or an accidental http:// URL is returned in a successful response, leaving clients with an unusable or insecure RelayMap instead of exposing the configuration error.

@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: c8fb208a22

ℹ️ 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 web/app/api/relay/token/route.ts Outdated
}

export async function POST(request: Request): Promise<Response> {
const user = await verifyRequest(request);

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 cookie auth for relay token minting

Here verifyRequest is called with its default options, which also allow the Stack cookie token store; the comparable native-only /api/devices POST opts out with allowCookie: false. With the signing key configured, a signed-in cmux browser session can therefore POST to this route and read a relay JWT without the Authorization/X-Stack-Refresh-Token pair that this endpoint documents as its trust boundary, expanding relay credential minting to web-cookie contexts. Pass allowCookie: false here, or add the same browser mutation/origin protection if cookie auth is intended.

Useful? React with 👍 / 👎.

Comment thread web/app/api/relay/token/route.ts Outdated
Comment on lines +67 to +68
const key = createPrivateKey(pem);
cached = { pem, key };

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 Validate the relay signing key type

If CMUX_RELAY_JWT_PRIVATE_KEY_PEM is set to a valid non-Ed25519 private PEM, createPrivateKey succeeds and this caches it, while mintRelayToken still advertises alg: "EdDSA"; for example an RSA key will produce a 200 response with a signature that every Ed25519 relay verifier rejects. Treat parsed keys whose asymmetricKeyType is not ed25519 as not configured before caching/signing so deployment mistakes fail as the documented 503 instead of issuing unusable tokens.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/app/api/relay/token/route.ts`:
- Around line 107-159: Add abuse protection at the start of POST, after
verifyRequest confirms the user, by applying the project’s existing per-user or
per-IP rate-limiting mechanism before signingKey, request parsing, and
mintRelayToken. Return the established rate-limit response when the limit is
exceeded, while preserving normal token issuance for allowed requests; use
existing limiter/coalescing symbols rather than introducing an unrelated
implementation.
- Around line 107-109: Update the verifyRequest call in POST to pass the option
that disables cookie authentication, using allowCookie: false. Preserve the
existing unauthorized response and ensure relay tokens are minted only through
Stack Bearer and refresh-token authentication.

In `@web/tests/relay-token-route.test.ts`:
- Around line 113-136: Add tests alongside the existing relay token route tests
for both validation branches: send malformed JSON through POST and assert status
400, then send an oversized endpointId payload and assert status 413. Reuse the
existing currentUser setup and request helpers where appropriate, targeting the
POST handler.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7a2a5096-b403-4b10-bcfd-20aaf6aebab6

📥 Commits

Reviewing files that changed from the base of the PR and between 99f9d0b and c8fb208.

📒 Files selected for processing (2)
  • web/app/api/relay/token/route.ts
  • web/tests/relay-token-route.test.ts

Comment thread web/app/api/relay/token/route.ts Outdated
Comment thread web/app/api/relay/token/route.ts Outdated
Comment thread web/tests/relay-token-route.test.ts Outdated
Comment on lines +113 to +136
test("400 on malformed endpoint_id", async () => {
currentUser = { id: "user_123" };
const res = await POST(postReq({ endpointId: "not a valid id!!" }));
expect(res.status).toBe(400);
});

test("honors CMUX_RELAY_URLS override", async () => {
currentUser = { id: "user_123" };
process.env.CMUX_RELAY_URLS =
"https://a.example.com, https://b.example.com";
const res = await POST(postReq());
const json = (await res.json()) as { relays: string[] };
expect(json.relays).toEqual([
"https://a.example.com",
"https://b.example.com",
]);
});

test("503 when the signing key is not configured", async () => {
currentUser = { id: "user_123" };
delete process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM;
const res = await POST(postReq());
expect(res.status).toBe(503);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the size-limit and malformed-JSON branches.

The route has explicit payload_too_large (413) and invalid_json (400) paths that no test exercises. For a signing endpoint these validation branches are worth pinning so regressions in the body-size/parse guards surface in CI.

💚 Suggested additional tests
test("400 on malformed JSON body", async () => {
  currentUser = { id: "user_123" };
  const res = await POST(
    new Request("https://cmux.dev/api/relay/token", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: "{not json",
    }),
  );
  expect(res.status).toBe(400);
});

test("413 when body exceeds the size limit", async () => {
  currentUser = { id: "user_123" };
  const res = await POST(postReq({ endpointId: "y".repeat(5000) }));
  expect(res.status).toBe(413);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/tests/relay-token-route.test.ts` around lines 113 - 136, Add tests
alongside the existing relay token route tests for both validation branches:
send malformed JSON through POST and assert status 400, then send an oversized
endpointId payload and assert status 413. Reuse the existing currentUser setup
and request helpers where appropriate, targeting the POST handler.

The route-level test globally mock.module'd auth+routeHelpers, which leaked into
the shared bun-test process and broke 44 unrelated route tests. Move the pure
minting/validation logic into services/relay/token.ts and unit-test that (no
mocking, no leak): mint-verifies-against-pubkey, wrong-key-rejects, endpoint_id
binding, key/URL config. Route is now thin glue over the service.

Full web suite: 586 pass / 0 fail; typecheck clean.
return key;
} catch {
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PEM newline escapes ignored

Medium Severity

relaySigningKey passes CMUX_RELAY_JWT_PRIVATE_KEY_PEM straight to createPrivateKey without normalizing literal \n escapes. Elsewhere (ASC_PRIVATE_KEY, APNs .p8) the codebase rewrites those escapes before parsing. A normally provisioned single-line PEM then fails parse, returns null, and the route keeps answering 503 relay_token_not_configured.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5b95538. Configure here.

@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: 5b95538927

ℹ️ 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 web/app/api/relay/token/route.ts Outdated
} catch {
return jsonResponse({ error: "invalid_json" }, 400);
}
if (body.endpointId !== undefined && body.endpointId !== null) {

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 Guard parsed JSON before reading endpointId

When an authenticated caller sends a valid JSON null body, JSON.parse assigns body = null and this dereference throws before the route can return a controlled 400/empty-body response, so /api/relay/token returns a 500 for malformed input. Validate that the parsed value is a non-null object before reading endpointId (or treat null as an empty body).

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/services/relay/token.ts`:
- Around line 53-64: Update the catch block in relaySigningKey to log the
createPrivateKey parse error before returning null, while preserving the
existing return behavior and leaving the missing-key path unchanged.

In `@web/tests/relay-token.test.ts`:
- Around line 37-40: Add teardown for the environment variables modified by the
relay-token test suite, using afterEach or afterAll alongside beforeEach.
Capture their original values before mutation and restore
CMUX_RELAY_JWT_PRIVATE_KEY_PEM and CMUX_RELAY_URLS afterward, deleting them when
they were initially unset so subsequent tests see the original process
environment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 94ec3b07-212d-470a-b930-ca2c65fe9af3

📥 Commits

Reviewing files that changed from the base of the PR and between c8fb208 and 5b95538.

📒 Files selected for processing (3)
  • web/app/api/relay/token/route.ts
  • web/services/relay/token.ts
  • web/tests/relay-token.test.ts

Comment on lines +53 to +64
export function relaySigningKey(): KeyObject | null {
const pem = process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM;
if (!pem || !pem.includes("BEGIN")) return null;
if (cached && cached.pem === pem) return cached.key;
try {
const key = createPrivateKey(pem);
cached = { pem, key };
return key;
} catch {
return null;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Log parse errors in relaySigningKey before swallowing them.

The catch block silently returns null on malformed PEM input, making production 503s undiagnosable. Operators cannot distinguish "key not configured" from "key configured but invalid" without server-side logging.

🛠️ Proposed fix
   try {
     const key = createPrivateKey(pem);
     cached = { pem, key };
     return key;
-  } catch {
+  } catch (error) {
+    console.error("[relay] Failed to parse signing key:", error);
     return null;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function relaySigningKey(): KeyObject | null {
const pem = process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM;
if (!pem || !pem.includes("BEGIN")) return null;
if (cached && cached.pem === pem) return cached.key;
try {
const key = createPrivateKey(pem);
cached = { pem, key };
return key;
} catch {
return null;
}
}
export function relaySigningKey(): KeyObject | null {
const pem = process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM;
if (!pem || !pem.includes("BEGIN")) return null;
if (cached && cached.pem === pem) return cached.key;
try {
const key = createPrivateKey(pem);
cached = { pem, key };
return key;
} catch (error) {
console.error("[relay] Failed to parse signing key:", error);
return null;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/services/relay/token.ts` around lines 53 - 64, Update the catch block in
relaySigningKey to log the createPrivateKey parse error before returning null,
while preserving the existing return behavior and leaving the missing-key path
unchanged.

Comment on lines +37 to +40
beforeEach(() => {
process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM = privatePem;
delete process.env.CMUX_RELAY_URLS;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add afterEach/afterAll to restore env vars after the suite.

beforeEach sets CMUX_RELAY_JWT_PRIVATE_KEY_PEM and deletes CMUX_RELAY_URLS, but there's no corresponding teardown. After the suite completes, these env vars remain modified, leaking global state to subsequent test files if Bun shares a process. The PR objectives explicitly call out preventing test leakage, so adding cleanup aligns with that goal.

As per coding guidelines: "Isolate shared static, global, UserDefaults, file, and related state per test, resetting it in setUp and tearDown as appropriate."

🛡️ Proposed fix
 import { afterEach, beforeEach, describe, expect, test } from "bun:test";
 import { generateKeyPairSync, verify as edVerify } from "node:crypto";

@@ -37,6 +37,11 @@
 beforeEach(() => {
   process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM = privatePem;
   delete process.env.CMUX_RELAY_URLS;
 });
+
+afterEach(() => {
+  delete process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM;
+  delete process.env.CMUX_RELAY_URLS;
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeEach(() => {
process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM = privatePem;
delete process.env.CMUX_RELAY_URLS;
});
beforeEach(() => {
process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM = privatePem;
delete process.env.CMUX_RELAY_URLS;
});
afterEach(() => {
delete process.env.CMUX_RELAY_JWT_PRIVATE_KEY_PEM;
delete process.env.CMUX_RELAY_URLS;
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/tests/relay-token.test.ts` around lines 37 - 40, Add teardown for the
environment variables modified by the relay-token test suite, using afterEach or
afterAll alongside beforeEach. Capture their original values before mutation and
restore CMUX_RELAY_JWT_PRIVATE_KEY_PEM and CMUX_RELAY_URLS afterward, deleting
them when they were initially unset so subsequent tests see the original process
environment.

Source: Coding guidelines

Codex review fixes:
- native-only auth: verifyRequest({ allowCookie: false }) so a browser cookie
  cannot mint an exportable relay JWT (matches /api/devices).
- stream + cancel the body at 4 KiB via readBoundedJsonObject (no buffer-first),
  and reject non-object JSON (null/array/primitive) -> 400 instead of 500.
- relaySigningKey requires asymmetricKeyType === ed25519, else treated as
  unconfigured (503) rather than minting an unusable token.
- route is now handleRelayTokenRequest(request, deps) with injected auth/key/clock,
  so 401/503/400/413/endpoint_id behavior is unit-tested without leaking module
  mocks (the prior leak broke 44 unrelated tests).

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/app/api/relay/token/route.ts`:
- Around line 70-81: Ensure endpoint ID casing is consistent between validation
and token signing: update the endpoint ID handling near rawEndpointId and
mintRelayToken so accepted mixed-case values are normalized before validation
and minting, or preserve their original casing by removing the signing-time
lowercase conversion. Apply the same chosen representation throughout the claim.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: de39d863-1dbf-498e-929c-a743bbd42202

📥 Commits

Reviewing files that changed from the base of the PR and between 5b95538 and dc281be.

📒 Files selected for processing (3)
  • web/app/api/relay/token/route.ts
  • web/services/relay/token.ts
  • web/tests/relay-token-route.test.ts

Comment thread web/app/api/relay/token/route.ts Outdated
Second review round:
- endpoint_id is now MANDATORY (mintRelayToken requires it; route 400s on
  missing/null) so no token is a bearer credential replayable from any key.
- per-account issuance rate limit via @vercel/firewall keyed by user id
  (CMUX_RELAY_TOKEN_RATE_LIMIT_ID; disabled when unset, 429 when exhausted).
  The complementary per-relay connection cap belongs in the relay (separate repo).
- isValidEndpointId now matches the relay's exact EndpointId contract: 64 hex or
  52-char z-base-32; a token the relay could never parse is rejected 400 instead
  of returning a signed-but-useless 200.
Comment thread web/app/api/relay/token/route.ts
On Vercel the rate limit is now mandatory: a missing rule id, a not-found rule,
or any limiter error returns 503 instead of silently minting without a bound
(matches /api/client-config). Local dev (no VERCEL env) still bypasses.
… validation scope

- Wrap @vercel/firewall checkRateLimit in try/catch: it rejects (not via error)
  on network failure/unexpected status, which was an uncaught 500. Now 503, with
  a test exercising the rejection path.
- Document why isValidEndpointId validates encoding shape (64 hex / 52 z-base-32)
  but defers on-curve validation to the relay: the endpoint_id is the caller's
  own key, so an invalid point is self-harm (a token bound to a key nobody holds,
  unredeemable), not a replay/availability risk — not worth a curve dependency.
- isValidEndpointId now accepts what iroh EndpointId::from_str actually parses:
  64-char lowercase hex or 52-char RFC 4648 base32 (A-Z2-7). The previous
  z-base-32 alphabet rejected valid base32 ids and accepted z-base-32 the relay
  cannot parse (verified against iroh-base 1.0.0-rc.1 decode_base32_hex).
- Race the @vercel/firewall check against a 3s deadline and fail closed (503) on
  a stall, so a hung limiter cannot block token issuance / attach. Test covers a
  never-settling limiter.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 907488f. Configure here.

Comment thread web/app/api/relay/token/route.ts Outdated
- isValidEndpointId now requires the canonical RFC 4648 final symbol: a 52-char
  base32 of 32 bytes has 4 trailing zero bits, so the last char must be 'a' or
  'q'. Non-canonical strings (which iroh's BASE32_NOPAD decoder rejects) are now
  400 instead of a signed-but-unusable 200.
- Drop the Promise.race timeout: @vercel/firewall has no abort signal, so a
  wrapper can only abandon (not cancel) the fetch. Match the repo convention
  (client-config/waitlist/feedback: plain awaited call; platform bounds
  duration) and keep the fail-closed try/catch for rejections.

Remaining review items are conscious cross-boundary exceptions: the per-relay
connection/traffic cap + expiry-disconnect is relay-side (cmux-relay/iroh-relay,
separate repo), and on-curve endpoint validation is deferred to the relay (the
id is the caller's own key; an invalid one is self-harm).
@lawrencecchen
lawrencecchen merged commit 25d9e6f into main Jul 11, 2026
27 checks passed
@lawrencecchen
lawrencecchen deleted the feat-relay-token-api branch July 11, 2026 12:26
azooz2003-bit added a commit that referenced this pull request Jul 15, 2026
* feat(iroh): bridge the production Swift endpoint

* feat(iroh): run the mac host transport

* test(iroh): reject local binding substitution

* test(ios): cover pooled fallback route

* fix(iroh): bind discovery to the local app instance

* fix(ios): retain successful pooled route

* test(iroh): require online-first host policy fallback

* feat(iroh): start hosts from verified offline policy

* feat(iroh): persist mac offline host policy

* fix(iroh): reject partial online binding rotation

* test(iroh): reject unvalidated private fallback

* feat(iroh): own iOS endpoint and multistream sessions

* fix(iroh): revalidate private fallback paths

* fix(iroh): accept JSON media type parameters

* ci(iroh): test full app on Intel Sonoma

* ci(iroh): run transport tests on Intel Sonoma

* test(iroh): cover abandoned relay reservations

* fix(iroh): expire abandoned relay reservations

* feat(iroh): expose admitted host multistream sessions

* test(iroh): require bounded incoming streams

* fix(iroh): bound peer-created QUIC streams

* feat(iroh): defer iOS transport activation

* feat(ios): prefer verified Iroh routes

* test(iroh): preserve endpoint on preferred port collision

* test(iroh): cover LAN rendezvous consistency

* fix(iroh): fall back from occupied preferred port

* feat(iroh): derive private rotating LAN aliases

* fix(iroh): serialize LAN discovery with revocation

* feat(iroh): make secure pairing the default

* docs(iroh): record offline and LAN trust boundaries

* feat(iroh): cache verified client policy offline

* docs(iroh): add Apple and proxy launch caveats

* docs(iroh): clarify Apple local network prompting

* test(iroh): cover offline cache teardown races

* fix(iroh): fence offline cache teardown

* test(iroh): cover online admission leases

* feat(iroh): gate online admission leases

* feat(iroh): add authenticated Bonjour LAN fallback

* feat(iroh): enforce online revocation leases

* test(iroh): cover offline admission leases

* test(iroh): cover canonical trust errors

* fix(iroh): harden trust broker boundaries

* fix(iroh): bound offline admission leases

* test(iroh): cover policy refresh revision races

* fix(iroh): fence admission policy refreshes

* docs(iroh): narrow private network release scope

* test(tailscale): reject unbound bearer routes

* fix(mobile): state private network boundaries

* test(tailscale): reject unbound bearer routes

Cover numeric-only Tailscale bearer routes and reject authorization, DNS, and route substitution before transport writes.

* docs(iroh): specify NAT authorization barrier

* fix(tailscale): bind bearer writes to live tunnel

* test(iroh): require acknowledged NAT admission barrier

* test(tailscale): reject route-only transport bypass

* fix(tailscale): close route-only transport bypass

* fix(iroh): acknowledge NAT admission before app streams

* test(iroh): hide database failure details

* fix(iroh): defer reservation constraint validation

* test(iroh): retain revocation monitor after handoff

* fix(iroh): retain revocation monitor for connection

* test(iroh): reject broker credential redirects

* fix(iroh): block broker credential redirects

* test(iroh): fail closed on terminal foreground policy

* fix(iroh): fail closed on terminal policy refresh

* test(iroh): prevent raw fallback after admission failure

* fix(iroh): pin authenticated pairings to Iroh

* test(auth): reject device registry redirects

* test(iroh): lock registration identity and relay bootstrap

* fix(iroh): preserve registration trust identity

* fix(auth): reject credentialed API redirects

* test(iroh): keep direct paths out of cloud storage

* fix(iroh): keep direct paths device local

* test(iroh): evict remotely closed client sessions

* test(iroh): recover dead session on foreground

* test(iroh): prevent server path-hint disclosure

* fix(iroh): recover suspended client sessions

* fix(iroh): keep private paths off server surfaces

* test(iroh): reject overlapping LAN bootstrap routes

* fix(iroh): reject ambiguous LAN interfaces

* test(iroh): bound pending admissions per identity

* fix(iroh): limit pending admissions per peer

* test(iroh): require owned server event stream

* test(auth): bound credentialed HTTP responses

* test(iroh): reject concurrent control owners

* fix(auth): cap credentialed HTTP responses

* test(iroh): cover firewall dependency failures

* fix(iroh): bound firewall availability checks

* feat(iroh): deliver server events on owned stream

* test(iroh): cap stalled firewall work

* test(iroh): bound active sessions per binding

* fix(iroh): cap stalled firewall work

* fix(iroh): cap active sessions per binding

* test(iroh): require firewall timeout recovery

* fix(iroh): abort stalled firewall checks

* fix(ci): isolate Iroh transport test suites

* test(iroh): route revocation to broker delete

* fix(iroh): send revocation to broker route

* test(mobile): bound concurrent RPC work

* fix(mobile): cap concurrent RPC work

* test(mobile): bound decoded frame batches

* fix(mobile): cap decoded frame batches

* test(iroh): bound pending Bonjour resolves

* test(iroh): gate reserved application lanes

* test(iroh): retain failed binding revocations

* fix(iroh): bound pending Bonjour resolves

* fix(iroh): gate reserved application lanes

* docs(iroh): narrow production multistream claims

* build(iroh): pin attested Swift fork release

* test(iroh): require retry-safe binding revocation

* fix(iroh): make binding revocation retry-safe

* fix(iroh): durably retry binding revocations

* build(iroh): lock iOS Swift fork release

* test(iroh): retain Bonjour observation lifetime

* test(auth): prepare before raced sign-out clear

* test(iroh): quarantine failed sign-out persistence

* test(ios): quarantine failed Iroh sign-out

* fix(iroh): quarantine incomplete sign-out teardown

* fix(iroh): clear host network state in quarantine

* fix(auth): quarantine Iroh before sign-out clear

* fix(ios): quarantine incomplete Iroh sign-out

* fix(mobile): type Iroh binding snapshot

* fix(ios): wait for auth clear before Iroh recovery

* build(iroh): lock app Swift fork release

* fix(iroh): persist secrets in ad-hoc debug builds

* test(iroh): require local-only HTTP minter opt-in

* feat(iroh): add loopback relay minter runner

* test(tailscale): require numeric registry targets

* feat(iroh): gate local relay minter HTTP

* fix(iroh): normalize local minter opt-in

* test(ios): require tagged API origin bake

* fix(ios): bake tagged API origin

* fix(tailscale): pin MagicDNS remotes to peer IPs

* fix(tailscale): reject inactive peer snapshots

* build(iroh): pin hardened FFI release

* test(auth): preserve auto-login during token reads

* test(auth): preserve manual sign-in during token reads

* test(iroh): require startup network event delivery

* fix(iroh): establish endpoint observation before activation

* fix(auth): preserve active session writers

* test(iroh): accept existing binding registration responses

* fix(iroh): accept existing binding relay status

* test(iroh): keep host active after refresh throttling

* fix(iroh): preserve host during broker throttling

* test(iroh): preserve client during broker throttling

* fix(iroh): retain verified policy during broker outages

* fix(iroh): decode broker dates on older macOS

* test(iroh): reject synthetic network change floods

* fix(iroh): observe address changes without feedback loop

* test(iroh): accept canonical UUID identity case

* fix(iroh): canonicalize pinned device UUIDs

* fix(iroh): harden compatibility and private routes

* refactor(iroh): split runtime ownership boundaries

* test(iroh): repair authorization suite split boundaries

* test(iroh): link mobile RPC authorization tests

* test(iroh): support compatibility compilers

* test(iroh): cover uppercase UUID fallback paths

* fix(iroh): canonicalize device UUID authority

* test(iroh): support Intel Sonoma compiler

* test(iroh): avoid non-Sendable fixture captures

* fix(updater): handle Intel-only Sparkle reason

* test(iroh): cover bearer and discovery overload

* fix(iroh): close route and discovery gaps

* fix(ci): close Iroh compatibility regressions

* feat(iroh): integrate endpoint-bound relay fleet

* fix(ci): wrap command timers for Intel Swift

* fix(ios): expose relay deployment to Sendable factory

* test(ci): cover private networking on Intel Sonoma

* test(ci): support Intel Swift Testing macros

* test(iroh): expose relay refresh expiry gap

* fix(iroh): retry relay refresh before expiry

* fix(ios): serialize Iroh quarantine recovery

* refactor(auth): isolate lifecycle revision API

* fix(ci): eliminate Iroh Swift 6 warnings

* fix(ci): support Intel Xcode 16.2

* fix(ci): mark canvas clock sleep sendable

* fix(ci): bridge canvas preferences to main actor

* fix(ci): support sidebar git on Xcode 16.2

* fix(ci): mark RPC termination handler sendable

* fix(ci): support CLI on Xcode 16.2

* fix(ci): support app target on Xcode 16.2

* fix(ci): finish Xcode 16.2 source compatibility

* iroh: point the broker relay fleet at the 7 self-hosted relay.cmux.dev URLs

Replaces the 4 hosted iroh.link relays with our self-hosted fleet in both
allowlists (web MANAGED_RELAY_URLS + presence worker APPROVED_IROH_RELAY_URLS,
kept in lockstep) and the tests that referenced hosted URLs. The self-hosted
relays run iroh-relay 1.0.2 behind per-region MIG+L4-LB (zero-downtime
upgrades), gated by the cmux EdDSA JWT that /api/relay/token (merged, #7879)
mints.

* fix(ci): support trailing closure on Xcode 16.2

* ci: allow Intel compatibility suite to finish

* test(ci): avoid Xcode 16.2 require recursion

* ci: focus Intel compatibility coverage

* fix(ci): stabilize replay ownership and Intel budget

* test(ios): isolate authoritative resync coverage

* feat(iroh): add secure flexible relay policy

* test(iroh): split relay runtime coverage

* test(iroh): allow self-hosted broker without legacy minter

* fix(iroh): make hosted relay minter optional

* test(iroh): cover public firewall host fallback

* fix(iroh): use public host for firewall checks

* fix(iroh): keep accepts and sign-out responsive

* test(iroh): reproduce lost online reachability

* fix(iroh): republish endpoint online routes

* test(iroh): reproduce coalesced route refresh

* fix(iroh): replay coalesced route refreshes

* refactor(iroh): split oversized runtime files

* test(iroh): cover lifecycle refresh races

* fix(iroh): fence lifecycle refresh work

* test(ios): reproduce loopback dev auto-pair race

* test(ios): cover redacted dev Iroh attach URLs

* fix(ios): wait for redacted Iroh dev attach ticket

* test(ios): reproduce Iroh cold-start attach race

* fix(ios): await Iroh before dev auto-pair

* feat(iroh): add server-driven relay preferences

* feat(iroh): complete relay controls and multistream runtime

* ci: rehearse staging migrations from dispatched branch

* Make managed Iroh credentials server-driven

* feat(iroh): expose redacted live path diagnostics

* security(iroh): stage relay policy key rotation

* fix(iroh): use instance-scoped host display name

* test(iroh): require dev attach targets to prefer identity routes

* fix(iroh): prefer identity routes for dev attach

* fix(web): include shared relay catalog in Next root

* test(web): keep relay catalog inside Next boundary

* fix(web): generate relay catalog inside runtime boundaries

* test(mobile): cover transport lifetime ownership

* fix(mobile): retain Iroh transport lifetime

* test(iroh): cover relay policy clock skew

* fix(iroh): tolerate bounded relay policy clock skew

* test(iroh): cover admitted session lifetime

* fix(iroh): separate admission and session lifetimes

* test(iroh): cover relay and route renewal stalls

* fix(iroh): keep relay routes renewed through storage stalls

* test(iroh): cover nonblocking binding persistence

* fix(iroh): publish bindings before secure persistence

* test(iroh): cover strict transport verification modes

* feat(iroh): add strict transport verification modes

* test(iroh): await nonblocking relay persistence

* test(iroh): cover live peer connection quotas

* fix(iroh): bound live sessions per endpoint

* test(iroh): cover broker-aware route renewal backoff

* fix(iroh): back off broker route renewal retries

* test(mobile): cover superseded Iroh transport cleanup

* fix(iroh): close unowned mobile sessions

* test(iroh): reproduce stale reconnect sessions

* fix(iroh): replace stale peer sessions on admission

* feat(iroh): add debug transport mode menu

* Add regression coverage for Iroh merge blockers

* Fix Iroh relay and reconnect merge blockers

---------

Co-authored-by: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: cmux reload-cloud <cmux-reload-cloud@users.noreply.github.com>
Co-authored-by: Aziz Albahar <aziz@manaflow.ai>
@lawrencecchen
lawrencecchen restored the feat-relay-token-api branch July 18, 2026 10:18

This branch was successfully deployed

1 active deployment
Preview – cmux — 399e124a Deployed Jul 11, 2026 by vercel[bot]
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