Skip to content

Harden Iroh relay server activation - #8454

Merged
4 commits merged into
mainfrom
feat-iroh-server-hardening
Jul 19, 2026
Merged

4 commits merged into
mainfrom
feat-iroh-server-hardening

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • validate the deferred Iroh relay issuance status constraint
  • persist the accepted managed relay catalog and enforce serialized add-before-remove rotation before signing policies
  • validate self-hosted relay signing and rate-limit env in deployed non-preview runtimes while keeping previews credential-free

Testing

  • bun test tests/client-config-env.test.ts tests/relay-policy.test.ts tests/relay-workflows.test.ts tests/relay-token.test.ts tests/relay-token-route.test.ts tests/relay-preferences-route.test.ts (45 pass)
  • bun run typecheck
  • bun run lint -- app/env.ts db/schema.ts services/relay/errors.ts services/relay/http.ts services/relay/repository.ts services/relay/workflows.ts tests/client-config-env.test.ts tests/iroh-db-behavior.test.ts tests/relay-workflows.test.ts
  • CMUX_PORT=4681 bun run db:test (all 11 database behavior suites pass; Iroh suite 26 pass)
  • bun run db:check
  • bun tools/generate-managed-iroh-relay-catalog.ts --check

A later full preflight retry reached the host Docker network allocator limit before starting Postgres. It did not invalidate the earlier green isolated database run.


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


Summary by cubic

Hardens activation of the self-hosted Iroh relay fleet by persisting the full managed catalog, using a canonical digest, and enforcing safe add-before-remove rotation. Production builds now require relay signing keys and rate‑limit IDs; previews stay credential‑free with sanitized error output for missing private config.

  • New Features

    • Enforce safe managed relay catalog rotation: persist the catalog body (iroh_relay_catalog_state.catalog), verify previous digest, compute overlap, and reject unsafe transitions with RelayCatalogRollbackError reasons previous_catalog_unavailable and unsafe_transition (reason logged).
    • Canonicalize the catalog digest so it’s stable across JSON key order; fail-closed on corruption with RelayCatalogIntegrityError (HTTP 503, safe reason logged).
    • Repository API: acceptCatalog({ catalog, nowSeconds }) computes the digest, timestamps updates, backfills the previous body on same-sequence, and validates rotation under lock.
    • Validate the expanded relay issuance status constraint.
    • Redact private relay env issues in logs; surface a single “Self-hosted relay runtime configuration is incomplete” error in production.
  • Migration

    • Apply DB migrations to add the catalog JSONB column and validate iroh_relay_token_issuances_status_check.
    • In non-preview deployments, set: CMUX_RELAY_JWT_PRIVATE_KEY_PEM, CMUX_RELAY_POLICY_KEY_ID, CMUX_RELAY_POLICY_PRIVATE_KEY_PEM, CMUX_RELAY_TOKEN_RATE_LIMIT_ID (optional CMUX_RELAY_PREFERENCES_RATE_LIMIT_ID). Previews/local remain credential-free.

Written for commit 93c88c8. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added relay catalog state storage (including catalog JSON) and safer relay catalog acceptance/rotation.
    • Updated self-hosted relay production configuration to require relay signing/policy and rate-limit settings, while keeping preview deployments more permissive.
  • Bug Fixes
    • Improved validation and fail-closed handling for relay catalog integrity issues, returning a consistent relay policy unavailable response.
    • Improved relay token issuance constraint validation behavior.
  • Documentation
    • Clarified migration and constraint validation timing/locking behavior.
  • Tests
    • Expanded coverage for relay configuration completeness, catalog integrity, and safer activation transitions.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds self-hosted relay production environment requirements, stores relay catalog bodies in database state, and updates catalog acceptance to validate canonical digests, enforce safe rotations, and use workflow-provided timestamps. Migrations, error handling, workflow integration, and related tests are updated.

Changes

Relay catalog and deployment configuration

Layer / File(s) Summary
Relay production environment validation
web/app/env.ts, web/tests/client-config-env.test.ts
Adds required relay signing and rate-limit variables for non-preview deployments, optional preferences limiter fallback behavior, and production/preview validation tests.
Catalog storage and constraint validation
web/db/migrations/*, web/db/schema.ts, web/services/iroh/README.md
Adds the nullable JSONB catalog column, validates the relay issuance status constraint, and documents the migration sequence.
Safe catalog acceptance and persistence
web/services/relay/catalog.ts, web/services/relay/repository.ts, web/services/relay/workflows.ts, web/services/relay/errors.ts, web/services/relay/http.ts, web/tests/*relay*
Accepts complete catalogs with timestamps, canonicalizes and validates persisted digests, checks rotation safety, persists catalog state, logs typed failures, and updates workflow, policy, and database tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RelayWorkflowConfig
  participant RelayRepositoryLive
  participant irohRelayCatalogState
  RelayWorkflowConfig->>RelayRepositoryLive: acceptCatalog(catalog, nowSeconds)
  RelayRepositoryLive->>irohRelayCatalogState: read persisted catalog state
  RelayRepositoryLive->>RelayRepositoryLive: validate canonical digest and rotation safety
  RelayRepositoryLive->>irohRelayCatalogState: persist catalog state and updatedAt
  RelayRepositoryLive-->>RelayWorkflowConfig: return acceptance or typed error
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 23 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning Summary and Testing are filled in, but the required Demo Video, Review Trigger, and Checklist sections are missing. Add the missing template sections, including a demo video link if applicable, the review trigger block, and the checklist items.
✅ Passed checks (23 passed)
Check name Status Explanation
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 PR tip changes only web TypeScript files; no .swift files or Swift actor-isolation-sensitive declarations were modified.
Cmux Swift Blocking Runtime ✅ Passed No Swift files or review-rule changes appear in the PR diff, so the Swift blocking-runtime rule is not applicable.
Cmux Browser Automation Off-Main ✅ Passed PR only changes relay/env/db/test files; no browser-automation files or keywords in the rule scope changed, so the off-main routing rule isn’t implicated.
Cmux Expensive Synchronous Load ✅ Passed The PR diff only touches web TS/SQL files; no Swift sources are changed, so it cannot add an expensive synchronous Swift load path.
Cmux Cache Substitution Correctness ✅ Passed acceptCatalog still reads the authoritative DB row and validates persisted catalog/digest; no fresh-read-to-cache swap appears in persistence/history/snapshot paths.
Cmux No Hacky Sleeps ✅ Passed No fixed sleeps, timers, polling, or wall-clock waits were introduced in the changed runtime files; the diff is validation/DB logic only.
Cmux Algorithmic Complexity ✅ Passed PASS: The new relay-catalog and env validation logic operates on explicitly bounded relay lists (MAX_MANAGED_RELAYS=16) with single-pass maps/filters; no nested scans or hot-path rescans were intro...
Cmux Swift Concurrency ✅ Passed No Swift files were changed in this PR, so the Swift concurrency modernization rules are not applicable.
Cmux Swift @Concurrent ✅ Passed PR diff changes only web files; no Swift code was introduced or modified, so the Swift @concurrent rule is not applicable.
Cmux Swift Package Boundaries ✅ Passed No Swift files changed in this PR; the Swift package-boundary rule is not applicable to the web-only diff.
Cmux Swiftpm Lockfiles ✅ Passed No SwiftPM/Xcode/.gitignore/workflow/dependency files changed in this PR, so the swiftpm Package.resolved rule is not implicated.
Cmux Swift Logging ✅ Passed No .swift files changed, so the Swift logging rule is not applicable; the added logging is in TypeScript, not app/runtime Swift.
Cmux User-Facing Error Privacy ✅ Passed PASS: New relay/env errors are generic; private env names coalesce to "Self-hosted relay runtime configuration is incomplete" and API errors stay generic.
Cmux Full Internationalization ✅ Passed PASS: changes are developer-only env validation/logging plus machine-readable relay error codes; no locale-backed UI, markdown, or message files were changed.
Cmux Swiftui State Layout ✅ Passed The PR only changes TypeScript/migration files; no SwiftUI views or state/layout patterns are introduced, so the rule is not applicable.
Cmux Architecture Rethink ✅ Passed PR only changes web/DB files; no Swift files were touched, so the Swift architectural rethink rule is not applicable.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed This PR changes only web/relay files; the commit diff has no Swift files, so the auxiliary-window shortcut rule is not applicable.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source files; no logs, temp dirs, caches, build output, or other stray artifacts were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PR diff changes only web TS files; no Swift production Sources/ files were modified, so the no-test-debug-seam rule is not applicable.
Cmux No Ambient Global State ✅ Passed HEAD only changes web/*.ts files; no production Swift code or new ambient globals were introduced.
Title check ✅ Passed The title is concise and accurately reflects the main change: hardening Iroh relay server activation.
✨ 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-iroh-server-hardening

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.

@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/services/relay/repository.ts`:
- Around line 166-169: The persisted catalog verification in the repository’s
relay-catalog persistence flow should not depend on incidental key ordering from
JSON.stringify. Update relayCatalogDigest to use canonical JSON serialization,
or remove this redundant re-hashing check if the storage boundary is trusted;
preserve the existing digest validation behavior otherwise.
🪄 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: 82c415d2-1277-4547-86e3-cfad1a4f172c

📥 Commits

Reviewing files that changed from the base of the PR and between f0d3c74 and 0275fe5.

📒 Files selected for processing (12)
  • web/app/env.ts
  • web/db/migrations/20260718120000_iroh_relay_status_validation/migration.sql
  • web/db/migrations/20260718121000_iroh_relay_catalog_body/migration.sql
  • web/db/schema.ts
  • web/services/iroh/README.md
  • web/services/relay/errors.ts
  • web/services/relay/http.ts
  • web/services/relay/repository.ts
  • web/services/relay/workflows.ts
  • web/tests/client-config-env.test.ts
  • web/tests/iroh-db-behavior.test.ts
  • web/tests/relay-workflows.test.ts

Comment thread web/services/relay/repository.ts
@greptile-apps

greptile-apps Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens the Iroh relay server activation flow by persisting the complete managed relay catalog body and enforcing serialized add-before-remove rotation. It also validates the deferred relay-issuance status constraint and requires relay signing/rate-limit credentials in non-preview Vercel deployments while keeping previews credential-free.

  • Canonical catalog digest (catalog.ts): switches from JSON.stringify(catalog) to an explicit field-ordered form so the digest is stable regardless of runtime object key ordering, preventing spurious sequence_reused_with_different_catalog errors when the catalog file is re-parsed across environments.
  • Add-before-remove enforcement (repository.ts): stores the full catalog JSONB, verifies the persisted body matches its stored digest before computing relay-set overlap, and rejects unsafe removals until RELAY_POLICY_TTL_SECONDS have elapsed since the prior catalog was accepted.
  • Production env hardening (env.ts): new requireVercelRelayValue validator enforces four relay credentials in deployed non-preview runtimes and uses publicEnvValidationIssues to consolidate and redact individual private-key env-var names from error logs.

Confidence Score: 5/5

Safe to merge. The catalog acceptance logic is protected by an advisory lock, all new error paths are typed and tested, and the nullable backfill column is correctly handled for rolling deploys.

The change is complex but carefully structured: the advisory lock guards all catalog mutations, the typed error hierarchy is complete and wired through the HTTP handler, the backfill path for existing null-catalog rows is correct, and the 45-test suite (including DB-level rotation and integrity tests) covers the key invariants. No paths leave the catalog in an inconsistent state.

repository.ts holds the most logic; the overlap computation and the ordered checks (assertCatalogAdvance → same-sequence backfill → previous_catalog_unavailable guard → integrity check → assertSafeRelayCatalogRotation → UPDATE) warrant a close read to confirm nothing is re-ordered by a future refactor.

Important Files Changed

Filename Overview
web/services/relay/repository.ts Core of the change: replaces the (sequence, digest) API with the full catalog object, stores the catalog body in JSONB, enforces add-before-remove rotation via advisory-locked overlap check, and adds typed IntegrityError for digest corruption.
web/app/env.ts Adds requireVercelRelayValue validators for four relay credentials and publicEnvValidationIssues to redact private-key env-var names from error logs.
web/services/relay/catalog.ts Rewrites relayCatalogDigest to canonicalize field ordering, making the hash stable across JSON key-order variations.
web/services/relay/errors.ts Adds RelayCatalogIntegrityError and two new RelayCatalogRollbackError reasons. Clean typed-error additions.
web/services/relay/http.ts Adds RelayCatalogIntegrityError handler and logs rollback reason; no implementation details in response bodies.
web/db/schema.ts Adds nullable jsonb catalog column for rolling-compatible backfill.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[acceptCatalog called\ncatalog + nowSeconds] --> B[Compute canonical digest]
    B --> C[Acquire pg_advisory_xact_lock]
    C --> D{Existing row?}
    D -- No --> E[INSERT with catalog body\nupdatedAt = nowSeconds]
    D -- Yes --> F[assertCatalogAdvance\nsequence + digest check]
    F -- sequence regressed --> ERR1[RelayCatalogRollbackError\nsequence_regressed]
    F -- same sequence\ndifferent digest --> ERR2[RelayCatalogRollbackError\nsequence_reused_with_different_catalog]
    F -- OK --> G{sequence == current?}
    G -- Yes, catalog null --> H[Backfill catalog body\nupdatedAt unchanged]
    G -- Yes, catalog present --> I[Return no-op]
    G -- No, catalog null --> ERR3[RelayCatalogRollbackError\nprevious_catalog_unavailable]
    G -- No, catalog present --> J[Verify persisted digest\nvs stored catalogDigest]
    J -- Mismatch --> ERR4[RelayCatalogIntegrityError\npersisted_catalog_digest_mismatch]
    J -- OK --> K[Compute overlapSeconds\nnowSeconds - updatedAt]
    K --> L[assertSafeRelayCatalogRotation\nadd-before-remove check]
    L -- Unsafe removal\nor invalid transition --> ERR5[RelayCatalogRollbackError\nunsafe_transition]
    L -- OK --> M[UPDATE catalogSequence\ncatalogDigest, catalog, updatedAt]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[acceptCatalog called\ncatalog + nowSeconds] --> B[Compute canonical digest]
    B --> C[Acquire pg_advisory_xact_lock]
    C --> D{Existing row?}
    D -- No --> E[INSERT with catalog body\nupdatedAt = nowSeconds]
    D -- Yes --> F[assertCatalogAdvance\nsequence + digest check]
    F -- sequence regressed --> ERR1[RelayCatalogRollbackError\nsequence_regressed]
    F -- same sequence\ndifferent digest --> ERR2[RelayCatalogRollbackError\nsequence_reused_with_different_catalog]
    F -- OK --> G{sequence == current?}
    G -- Yes, catalog null --> H[Backfill catalog body\nupdatedAt unchanged]
    G -- Yes, catalog present --> I[Return no-op]
    G -- No, catalog null --> ERR3[RelayCatalogRollbackError\nprevious_catalog_unavailable]
    G -- No, catalog present --> J[Verify persisted digest\nvs stored catalogDigest]
    J -- Mismatch --> ERR4[RelayCatalogIntegrityError\npersisted_catalog_digest_mismatch]
    J -- OK --> K[Compute overlapSeconds\nnowSeconds - updatedAt]
    K --> L[assertSafeRelayCatalogRotation\nadd-before-remove check]
    L -- Unsafe removal\nor invalid transition --> ERR5[RelayCatalogRollbackError\nunsafe_transition]
    L -- OK --> M[UPDATE catalogSequence\ncatalogDigest, catalog, updatedAt]
Loading

Reviews (2): Last reviewed commit: "fix(iroh): close relay hardening review ..." | Re-trigger Greptile

Comment thread web/services/relay/repository.ts Outdated
Comment thread web/services/relay/repository.ts
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Review feedback addressed in 93c88c81af:

  • relay startup validation redacts credential-variable names
  • catalog digests use explicit canonical field order
  • persisted digest corruption returns the same fail-closed 503 and logs only a typed reason
  • the redundant post-proof sequence guard is removed

Verification: 26 focused tests, TypeScript typecheck, changed-file lint, and all 11 database behavior files passed.

Live preview from this head: https://cmux-afexr6dvi-manaflow.vercel.app

@azooz2003-bit azooz2003-bit closed this pull request by merging all changes into main in 288f1e1 Jul 19, 2026
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