Skip to content

Conversation

@NathanFlurry
Copy link
Member

No description provided.

@vercel
Copy link

vercel bot commented Nov 4, 2025

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

Project Deployment Preview Comments Updated (UTC)
rivetkit-serverless Ready Ready Preview Comment Nov 4, 2025 8:59pm
3 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
rivet-cloud Ignored Ignored Nov 4, 2025 8:59pm
rivet-inspector Ignored Ignored Preview Nov 4, 2025 8:59pm
rivet-site Ignored Ignored Preview Nov 4, 2025 8:59pm

This was referenced Nov 4, 2025
Copy link
Member Author

NathanFlurry commented Nov 4, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label merge-queue to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@claude
Copy link

claude bot commented Nov 4, 2025

PR Review: Handle CORS on the Gateway

Summary

This PR consolidates CORS handling at the gateway level in Rust, removing the previous TypeScript-based CORS middleware configuration. The changes simplify CORS management by implementing a consistent policy across both the Rust gateway and TypeScript routers.

Code Quality & Best Practices ✅

Rust Implementation (pegboard-gateway/src/lib.rs)

  • Well-structured with clear comments explaining the rationale
  • Follows Rust conventions with proper error handling using Result
  • Good use of structured logging with tracing macros (line 106)
  • Proper header extraction before request consumption (lines 79-86)

TypeScript Implementation (common/cors.ts)

  • Clean, reusable middleware pattern
  • Good documentation explaining behavior
  • Consistent with gateway implementation

Code Style Alignment:

  • ✅ Logging follows structured pattern: tracing::debug!("handling OPTIONS preflight request at gateway") (line 106)
  • ✅ Uses anyhow for error handling
  • ✅ Proper imports at file top

Potential Issues 🔍

1. Overly Permissive CORS Policy (SECURITY CONCERN)

The current implementation accepts any origin and echoes it back:

// engine/packages/pegboard-gateway/src/lib.rs:81-86
let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("*")
    .to_string();

Issue: This allows requests from any origin, which could enable:

  • Cross-site request forgery (CSRF) attacks
  • Unauthorized data exfiltration
  • Token theft from malicious websites

Comment in code states:

"This implementation allows requests from anywhere and lets the actor handle CORS manually in onBeforeConnect."

Recommendation:

  • If actors are expected to handle authorization in onBeforeConnect, this should be clearly documented with security warnings
  • Consider adding a configuration option for origin validation at the gateway level
  • Document that the x-rivet-token header requirement provides some protection for authenticated endpoints
  • Add a warning in the code that this is intentionally permissive and relies on downstream validation

2. Missing Wildcard Safety Check

// line 113
.unwrap_or("*")

When origin is set to "*", you cannot use credentials: true according to CORS spec. The code does include Vary: Origin logic (lines 128-130, 224-226), but mixing * with credentials could cause issues with some browsers.

Recommendation: Either:

  • Never use "*" when credentials are enabled (always require an origin)
  • Or handle the missing origin case differently

3. Inconsistent Header Handling

The gateway allows all headers via wildcard:

// line 123
.header("access-control-allow-headers", requested_headers)

However, rivetkit-typescript/src/common/actor-router-consts.ts defines ALLOWED_PUBLIC_HEADERS (lines 48-61), but this list isn't being enforced at the gateway.

Recommendation:

  • Either enforce ALLOWED_PUBLIC_HEADERS at the gateway level
  • Or remove the constant if it's no longer needed
  • Document why unrestricted header access is safe

4. WebSocket CORS Coverage

The comment states:

"This had the added benefit of also applying to WebSockets." (line 104)

However, WebSocket connections don't use the standard CORS preflight mechanism. WebSockets use the Origin header for security, but the handle_websocket function doesn't appear to validate origins.

Recommendation:

  • Verify that WebSocket connections properly validate origins
  • Add origin validation in handle_websocket if needed (line 236)

Performance Considerations ⚡

Positive:

  • ✅ Early return for OPTIONS requests avoids unnecessary processing (line 132)
  • ✅ Origin extracted once and reused (line 81-86, then 219, 224)
  • ✅ 24-hour preflight cache reduces repeated OPTIONS requests (line 125)

Minor Optimization:

  • The Vary: Origin header is only added when origin != "*" (lines 128-130, 224-226), which is good for caching
  • No significant performance concerns

Security Concerns 🔒

Critical:

  1. Permissive CORS policy - Accepts any origin (see Issue [SVC-2555] Set up issue templates #1 above)
  2. No origin allowlist - Previous implementation had defaultInspectorOrigins in inspector/config.ts (deleted lines), which provided some protection

Medium:
3. Wildcard credentials conflict - Potential browser compatibility issues
4. WebSocket origin validation - Unclear if validated

Positive Security Aspects:

  • ✅ Uses Vary: Origin to prevent cache poisoning (lines 128-130, 224-226)
  • ✅ Proper CORS preflight handling
  • ✅ Comment indicates reliance on x-rivet-token header for auth

Test Coverage 📋

Concerns:

  • ❌ No tests found for the new CORS implementation
  • ❌ No tests for OPTIONS preflight handling
  • ❌ No tests for origin echoing behavior
  • ❌ No tests for WebSocket CORS behavior
  • ❌ No security tests for malicious origins

Recommendations:

  1. Add Rust tests for gateway CORS handling:

    • OPTIONS preflight returns 204 with correct headers
    • Origin header is properly echoed
    • Vary header is added correctly
    • Actual requests include CORS headers
  2. Add TypeScript tests for the new cors() middleware:

    • Test preflight OPTIONS handling
    • Test origin echoing
    • Test header deletion on preflight (lines 39-40)
  3. Add integration tests:

    • Test end-to-end CORS flow through gateway
    • Test WebSocket CORS behavior
    • Test onBeforeConnect origin validation (if that's the security model)

Configuration Removal Impact 🔧

The PR removes InspectorConfig.cors (deleted from inspector/config.ts):

// DELETED: Lines that defined defaultInspectorOrigins and defaultCors
const defaultInspectorOrigins = [
    "http://localhost:43708",
    // ... studio.rivet.gg, inspect.rivet.dev, etc.
];

Impact:

  • Previous implementation had origin validation
  • New implementation accepts all origins
  • This is a security regression if actors don't validate origins in onBeforeConnect

Recommendation:

  • Verify that all actors properly validate origins in onBeforeConnect
  • Add documentation/examples showing proper origin validation in actors
  • Consider adding this validation to the gateway as defense-in-depth

Overall Assessment

Strengths:

  • Clean code architecture with good separation of concerns
  • Proper error handling and logging
  • Performance optimizations in place
  • Consistent implementation across Rust and TypeScript

Concerns:

  • Major security concern: Overly permissive CORS policy
  • Missing test coverage
  • Unclear security model (gateway vs actor-level validation)
  • Potential regression from previous allowlist-based approach

Recommendations Summary

Before Merging:

  1. 🔴 Security: Document the security model - clarify that actors MUST validate origins in onBeforeConnect
  2. 🔴 Security: Add explicit warning comments about the permissive CORS policy
  3. 🟡 Tests: Add comprehensive test coverage for CORS behavior
  4. 🟡 Security: Fix wildcard + credentials conflict
  5. 🟡 Documentation: Add examples of proper origin validation in actors

Post-Merge (Technical Debt):

  1. Consider adding configurable origin allowlist at gateway level
  2. Enforce ALLOWED_PUBLIC_HEADERS or document why unrestricted headers are safe
  3. Add WebSocket-specific origin validation if needed

Great work on consolidating CORS handling! The implementation is clean, but please address the security concerns before merging.

@NathanFlurry NathanFlurry force-pushed the 11-04-chore_guard_handle_cors_on_the_gateway branch from 8bc1d65 to 70ec891 Compare November 4, 2025 19:24
@NathanFlurry NathanFlurry force-pushed the 11-04-chore_guard_add_support_for_routing_runner_ws_to__runners_connect_ branch from 50142b7 to 23c26cd Compare November 4, 2025 19:24
@claude
Copy link

claude bot commented Nov 4, 2025

PR Review: chore(guard): handle cors on the gateway

Summary

This PR consolidates CORS handling at the gateway level, removing scattered CORS configurations from various TypeScript routers and implementing a unified approach in both Rust (pegboard-gateway) and TypeScript (new cors middleware). This is a good architectural improvement that simplifies CORS management.

Code Quality & Best Practices ✅

Strengths:

  • Architectural improvement: Centralizing CORS at the gateway eliminates duplication and provides consistent behavior
  • Rust implementation: Clean and follows Rust idioms with proper error handling
  • TypeScript middleware: Simple, reusable implementation that mirrors gateway behavior
  • Logging: Appropriate use of tracing::debug! for OPTIONS requests (engine/packages/pegboard-gateway/src/lib.rs:106)
  • Comments: Good explanatory comments about why CORS is handled at gateway level (lines 100-104)

Code style notes:

  • Follows project conventions (hard tabs in Rust, structured logging)
  • Consistent implementation across Rust and TypeScript

Security Concerns ⚠️

Critical: Overly Permissive CORS Configuration

The current implementation allows requests from any origin by echoing back the request origin:

// Rust (lib.rs:81-85)
let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("*")
    .to_string();
// TypeScript (cors.ts:16)
const origin = c.req.header("origin") || "*";

Issues:

  1. No origin validation: Any domain can make authenticated requests to your actors
  2. Credentials enabled: access-control-allow-credentials: true combined with wildcard origin acceptance is a security anti-pattern
  3. CSRF vulnerability: Without origin restrictions, malicious sites can make authenticated cross-origin requests
  4. Removed safeguards: The old config had explicit origin whitelisting (inspector/config.ts:12-29, deleted)

Recommendations:

  1. Add origin validation at the gateway level:

    fn is_allowed_origin(origin: &str) -> bool {
        // Whitelist based on environment or config
        let allowed = ["https://studio.rivet.gg", "https://dashboard.rivet.dev"];
        allowed.contains(&origin) || origin.ends_with(".rivet-dev.vercel.app")
    }
  2. Consider environment-based configuration:

    • Development: permissive (localhost, all origins)
    • Production: strict whitelist
  3. If actors need custom CORS: The comment mentions actors can handle CORS in onBeforeConnect, but this should be documented with security guidelines

  4. Alternative approach: If you truly need any-origin support, consider:

    • Token-based auth instead of cookies (less CSRF risk)
    • Short-lived tokens
    • Document the security model clearly

Performance Considerations ✅

  • Early return for OPTIONS: Efficient preflight handling without actor overhead (lib.rs:105-133)
  • Header extraction optimization: Origin extracted before request consumption (lib.rs:79-86)
  • Cache headers: access-control-max-age: 86400 reduces preflight requests
  • Vary header: Correctly added when echoing origin to prevent cache issues (lib.rs:128-130)

Potential Bugs 🐛

1. Header Case Sensitivity
The Rust code uses lowercase header names:

.header("access-control-allow-origin", &origin)

While HTTP/1.1 headers are case-insensitive, ensure the Response::builder() handles this correctly. (This is likely fine, just noting for thoroughness)

2. Wildcard Expose Headers
Both implementations use access-control-expose-headers: *, which is valid but may expose sensitive headers. Consider explicitly listing headers if any contain sensitive data.

Test Coverage ❌

No tests found for the CORS implementation:

  • No unit tests for the new cors.ts middleware
  • No integration tests for gateway CORS handling
  • Consider adding tests for:
    • Preflight OPTIONS requests
    • Origin echoing behavior
    • Credentials header presence
    • Vary header when origin is not "*"
    • WebSocket CORS (mentioned in comments but not explicitly tested)

Additional Notes

Removed configurations:

  • inspector.cors config option removed from InspectorConfigSchema
  • runConfig.cors removed from RunnerConfigSchema
  • Hono's cors middleware imports removed from routers

This is consistent with the gateway-level approach, but ensure all stakeholders are aware of the change in CORS configuration surface area.

Documentation:
Consider adding documentation about:

  • How to configure allowed origins (if implemented)
  • Security model for actor endpoints
  • Migration guide if teams were using custom CORS configs

Verdict

Approve with reservations: The architectural direction is solid, but the security concerns around unrestricted origin access must be addressed before this can safely go to production. The current implementation essentially disables CORS protection.

Required before merge:

  1. Implement origin validation or document why unrestricted access is acceptable
  2. Add tests for CORS behavior

Recommended:

  1. Add configuration for allowed origins
  2. Document security implications
  3. Consider different settings for dev vs prod

Review generated with Claude Code

@claude
Copy link

claude bot commented Nov 4, 2025

Code Review: chore(guard): handle cors on the gateway

Summary

This PR consolidates CORS handling at the gateway level instead of in individual routers. This is a sensible architectural change that centralizes CORS logic and solves the issue of not being able to send OPTIONS requests to actors without authentication tokens.


✅ Strengths

  1. Architectural Improvement: Moving CORS to the gateway is the right approach, as it:

    • Eliminates the need for actors to handle preflight requests without authentication
    • Provides consistent CORS behavior across all routes including WebSockets
    • Reduces code duplication across multiple routers
  2. Security Considerations:

    • Properly echoes the Origin header instead of using wildcards with credentials
    • Includes Vary: Origin header to prevent cache poisoning attacks
    • Maintains credential support correctly
  3. Code Quality:

    • Good inline documentation explaining the rationale
    • Consistent implementation between Rust and TypeScript
    • Follows existing logging patterns in Rust code

🔍 Issues & Concerns

1. Security: Overly Permissive CORS Policy (HIGH Priority)

The current implementation allows requests from any origin ("*" as fallback). This is a significant security concern:

// lib.rs:85-86
let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("*")  // ⚠️ Allows any origin!
    .to_string();

Problem: The comment on line 103 states "lets the actor handle CORS manually in onBeforeConnect", but the gateway is already responding with permissive CORS headers before the actor can validate anything.

Recommendation:

  • Add an allowlist of permitted origins at the gateway level
  • Consider implementing domain pattern matching (e.g., *.rivet.dev)
  • Document clearly that actors should still validate origins in onBeforeConnect for additional security layers

2. Header Validation Logic Issue (MEDIUM Priority)

In lib.rs:109-113, there's a subtle issue with header handling:

let requested_headers = req
    .headers()
    .get("access-control-request-headers")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("*");

Problem: Using "*" as a fallback for access-control-allow-headers may not work as expected. Per CORS spec, when credentials are included, "*" cannot be used for allowed headers - you must explicitly list them.

Recommendation: Provide a specific list of common headers instead of "*" as the fallback, or extract and validate the requested headers properly.

3. Inconsistent Behavior (LOW Priority)

The TypeScript implementation removes content-length and content-type headers from preflight responses (cors.ts:39-40), but the Rust implementation doesn't explicitly do this. While this may not cause issues, consistency would be better.

4. Missing Error Context (LOW Priority)

In lib.rs:132, the error handling could be more informative:

return Ok(response.body(ResponseBody::Full(Full::new(Bytes::new())))?);

Consider using .context() to provide more meaningful error messages if the response building fails.

5. Test Coverage (MEDIUM Priority)

No tests were added for the new CORS functionality. Consider adding tests for:

  • OPTIONS preflight requests with various origins
  • CORS headers on actual requests
  • Header validation
  • WebSocket upgrade with CORS headers

📝 Code Style Notes

  1. Rust Logging: Good use of tracing::debug! on line 106 ✓
  2. TypeScript: Consistent with project patterns ✓
  3. Documentation: Inline comments are helpful ✓

🎯 Recommendations

Before Merging:

  1. ⚠️ HIGH: Address the permissive origin policy - implement an allowlist or document the security implications
  2. ⚠️ MEDIUM: Fix the "*" fallback for allowed headers when credentials are enabled

Follow-up:
3. Add integration tests for CORS behavior
4. Document the CORS security model for developers using RivetKit
5. Consider adding configuration options for allowed origins


🔒 Security Assessment

The current implementation has moderate security concerns due to the permissive origin policy. While the Vary header prevents cache poisoning, allowing all origins with credentials enabled could lead to CSRF-like attacks. The comment suggests actors will validate origins, but this should be enforced at multiple layers.

Verdict: Approve with the understanding that origin validation must be implemented either at the gateway or thoroughly documented as an actor responsibility.


✨ Overall Assessment

This is a solid architectural improvement that centralizes CORS handling. The code quality is good, but the security model needs clarification and potentially tightening before production use.

Recommendation: Request changes for the permissive origin policy before merging.


🤖 Generated with Claude Code

@claude
Copy link

claude bot commented Nov 4, 2025

PR Review: CORS Handling at Gateway Level

Summary

This PR centralizes CORS handling at the gateway level (both Rust and TypeScript), removing the need for individual CORS configurations in the actor, manager, and inspector routers. This is a smart architectural decision that simplifies the codebase.


✅ Strengths

  1. Architectural Improvement: Moving CORS to the gateway level is the right approach - it ensures consistent CORS behavior across all endpoints and reduces duplication.

  2. Good Security Practices:

    • Properly echoes back the Origin header instead of using * when credentials are enabled (lines lib.rs:81-86, cors.ts:16)
    • Includes Vary: Origin header to prevent cache poisoning (lib.rs:128-130, cors.ts:34-36, 53-55)
    • Appropriate preflight cache duration (86400 seconds / 24 hours)
  3. Consistent Implementation: The Rust and TypeScript implementations mirror each other well, maintaining behavioral consistency across the stack.

  4. Clear Documentation: Good inline comments explaining why CORS is handled at the gateway level (lib.rs:99-104).


⚠️ Security Concerns

CRITICAL: Overly Permissive CORS Policy

The current implementation accepts requests from any origin by echoing back whatever origin the client sends. This is a significant security risk:

// lib.rs:81-86
let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("*")
    .to_string();

Why this is problematic:

  • Any website can make authenticated requests to your actors (credentials are enabled)
  • No origin validation means potential for CSRF attacks
  • Exposes your API to unauthorized cross-origin requests
  • The comment mentions "allows requests from anywhere" (lib.rs:102-103) but this should be restricted

Recommendation:
Implement origin validation with an allowlist. The deleted code from inspector/config.ts had proper origin validation:

// Example from the deleted code:
const defaultInspectorOrigins = [
    "http://localhost:43708",
    "https://studio.rivet.gg",
    "https://inspect.rivet.dev",
    // etc...
];

You should:

  1. Add origin validation in the gateway with a configurable allowlist
  2. Return a 403 or simply omit CORS headers for unauthorized origins
  3. Consider different policies for different environments (dev vs production)

Alternative approach (if truly needed):
If you genuinely need to allow any origin, at minimum:

  • Document why this is necessary and the security implications
  • Consider using Access-Control-Allow-Credentials: false when allowing all origins
  • Rely on authentication tokens for security instead of origin validation

🐛 Potential Issues

  1. Header Overwrites (lib.rs:213-226):

    // Add headers from actor
    for (key, value) in response_start.headers {
        response_builder = response_builder.header(key, value);
    }
    
    // Add CORS headers to actual request
    response_builder = response_builder
        .header("access-control-allow-origin", &origin)
        // ...

    If an actor tries to set its own CORS headers, they'll be overwritten. This is probably intentional, but should be documented. Consider logging a warning if actors attempt to set CORS headers.

  2. WebSocket CORS (Not shown in diff):
    The PR mentions CORS applies to WebSockets (lib.rs:104), but I don't see explicit WebSocket upgrade request handling with CORS headers. Verify that WebSocket handshakes properly include the CORS headers.

  3. Error Case Origin Handling (lib.rs:85):

    .unwrap_or("*")

    When there's no Origin header, falling back to "*" with credentials: true is invalid per CORS spec. You should either:

    • Not set CORS headers when there's no origin (same-origin request)
    • Or set credentials to false when origin is "*"

🔍 Code Quality

  1. Logging: Good use of structured logging (lib.rs:106)

    tracing::debug!("handling OPTIONS preflight request at gateway");
  2. Code Duplication: The CORS logic is duplicated between:

    • Preflight response (lib.rs:115-132)
    • Actual response (lib.rs:217-226)
    • TypeScript implementation (cors.ts)

    Consider extracting the header-building logic into a helper function to reduce duplication and ensure consistency.

  3. Magic Numbers: The 86400 value appears without context. Consider using a constant:

    const CORS_MAX_AGE_SECONDS: u32 = 86400; // 24 hours
  4. Import Cleanup: Good job removing unused imports from the TypeScript files.


📝 Minor Suggestions

  1. Type Safety (cors.ts:16):

    const origin = c.req.header("origin") || "*";

    This will never be "*" in practice since header() returns undefined, not empty string. The TypeScript Rust implementations differ here - make them consistent.

  2. Expose Headers: Using "*" for access-control-expose-headers is very permissive. Consider explicitly listing which headers should be exposed.

  3. Allowed Methods: The list includes PATCH which is less common. Verify this is actually needed:

    "GET, POST, PUT, DELETE, OPTIONS, PATCH"

✅ Test Coverage

Missing: This PR doesn't include tests for the new CORS functionality. Consider adding:

  • Unit tests for CORS header generation
  • Integration tests for preflight requests
  • Tests verifying origin validation (once implemented)
  • Tests for WebSocket upgrade with CORS

🎯 Recommendations Summary

Must Fix (Security):

  • Implement origin validation with allowlist
  • Fix the credentials: true with origin: "*" spec violation

Should Fix:

  • Add test coverage for CORS functionality
  • Extract CORS header building into helper functions
  • Document behavior when actors set CORS headers
  • Add constants for magic numbers

Nice to Have:

  • Log warnings when actors try to set CORS headers
  • Make TypeScript and Rust origin fallback consistent
  • Explicitly list exposed headers instead of "*"

Overall Assessment

This is a good architectural improvement that simplifies CORS handling. However, the security concerns around origin validation must be addressed before merging. The current implementation is too permissive and could expose the API to unauthorized access.

Once origin validation is added, this will be a solid improvement to the codebase.

🤖 Generated with Claude Code

@claude
Copy link

claude bot commented Nov 4, 2025

Code Review

Summary

This PR consolidates CORS handling at the gateway level, moving it from individual middleware configurations to a unified implementation in both the Rust pegboard-gateway and TypeScript RivetKit router. This is a sensible architectural change that simplifies CORS management and ensures consistent behavior.

Strengths

  1. Consistent CORS behavior across platforms: The implementation in both Rust (pegboard-gateway) and TypeScript (RivetKit) mirrors each other, ensuring uniform CORS handling.

  2. Security-conscious implementation: Properly echoes the Origin header instead of using * when credentials are enabled, adds Vary: Origin header to prevent cache poisoning attacks, and handles preflight OPTIONS requests correctly.

  3. Good code comments: The Rust implementation has clear explanations for why CORS is handled at the gateway level (lines 99-104 in lib.rs).

  4. Simplifies configuration: Removes complex CORS configuration options from InspectorConfig and RunnerConfig, reducing cognitive overhead for users.

Critical Issues

1. Security: Overly Permissive CORS Policy (CRITICAL)

The current implementation allows requests from ANY origin by echoing back whatever origin is sent. This effectively disables CORS protection. While the comment mentions letting the actor handle CORS manually in onBeforeConnect, this creates a security gap:

  • For HTTP requests: No origin validation occurs at all
  • For WebSockets: Validation only happens after the connection is established
  • Attack surface: Malicious sites can make authenticated requests to the gateway

Recommendation: Implement origin validation at the gateway level with a configurable allowlist.

2. Security: Wildcard Header Exposure

Both implementations use access-control-expose-headers: * which exposes ALL response headers to JavaScript, including potentially sensitive headers. Consider explicitly listing headers that need to be exposed.

3. Breaking Change Without Migration Path

The PR removes the cors configuration option from both InspectorConfig and RunnerConfig without deprecation warnings or migration guidance. Users who customized CORS settings will have their configurations silently ignored.

4. Testing: No Test Coverage

The PR adds significant security-sensitive code but includes no tests. Consider adding unit tests for CORS header generation, integration tests for preflight requests, and edge case tests.

Other Issues

  • Missing error handling for invalid Origin header values in TypeScript
  • Potential duplicate CORS headers if actors also set them (lines 217-226 in lib.rs)
  • Magic number 86400 should be a named constant
  • Consider extracting duplicate header-building logic into a helper function

Performance

The implementation is efficient with early returns for OPTIONS requests and minimal allocations.

Recommendations Priority

  1. HIGH: Implement origin validation/allowlist instead of accepting all origins
  2. HIGH: Add tests for CORS functionality
  3. MEDIUM: Document breaking changes and provide migration guide
  4. MEDIUM: Review and restrict access-control-expose-headers
  5. LOW: Extract duplicate header-building logic and use named constants

Conclusion

This PR makes a good architectural improvement by centralizing CORS handling. However, the overly permissive CORS policy is a critical security concern that should be addressed before merging.


Review based on CLAUDE.md conventions. Generated with Claude Code.

@graphite-app graphite-app bot changed the base branch from 11-04-chore_guard_add_support_for_routing_runner_ws_to__runners_connect_ to graphite-base/3341 November 4, 2025 20:56
@graphite-app graphite-app bot force-pushed the 11-04-chore_guard_handle_cors_on_the_gateway branch from e7f7c04 to 63d1984 Compare November 4, 2025 20:58
@graphite-app graphite-app bot force-pushed the graphite-base/3341 branch from e86c567 to f9dd2a8 Compare November 4, 2025 20:58
@graphite-app graphite-app bot changed the base branch from graphite-base/3341 to main November 4, 2025 20:58
@claude
Copy link

claude bot commented Nov 4, 2025

PR Review: chore(guard): handle cors on the gateway

Summary

This PR centralizes CORS handling at the gateway level, removing distributed CORS configuration from TypeScript routers. The implementation is solid with good security considerations, but there are a few items worth discussing.


Code Quality & Best Practices ✅

Rust Implementation (pegboard-gateway/src/lib.rs)

  • Clean separation of concerns: CORS logic is extracted before request processing
  • Good comment explaining why OPTIONS handling is at gateway level (no x-rivet-token header issue)
  • Consistent logging pattern with tracing::debug!
  • Proper error handling using ? operator

TypeScript Implementation (common/cors.ts)

  • Well-documented middleware with clear behavior description
  • Follows Hono middleware patterns correctly
  • Good code comments explaining the rationale

Configuration Cleanup

  • Properly removes unused CORS configuration from InspectorConfigSchema and RunnerConfigSchema
  • Eliminates cors imports from hono/cors throughout the codebase
  • Good simplification by removing the complex origin validation logic

Security Concerns ⚠️

1. Permissive CORS Policy

The implementation allows requests from any origin by echoing back the Origin header:

// lib.rs:81-86
let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("*")
    .to_string();
// cors.ts:16
const origin = c.req.header("origin") || "*";

Concern: This essentially allows any website to make credentialed requests to your API. The comment in lib.rs:103 mentions "This implementation allows requests from anywhere and lets the actor handle CORS manually in onBeforeConnect", but this is a significant security decision.

Previous Implementation: Had explicit allowlist of trusted origins:

// Removed from inspector/config.ts
const defaultInspectorOrigins = [
  "http://localhost:43708",
  "https://studio.rivet.gg",
  "https://inspect.rivet.dev",
  // etc...
]

Recommendations:

  • If actors are expected to handle origin validation in onBeforeConnect, ensure this is clearly documented and enforced
  • Consider if there's a risk window between gateway and actor validation
  • Verify that all actors properly validate origins in their onBeforeConnect handlers
  • Add security documentation explaining this decision and the actor's responsibilities

2. Vary Header Implementation

Good use of Vary: Origin header to prevent cache poisoning when echoing origins. However:

// lib.rs:128-130
if origin != "*" {
    response = response.header("vary", "Origin");
}

Minor issue: The origin variable is always a string extracted from the header or "*". The condition origin != "*" would be false if:

  • No Origin header is present (defaults to "*")
  • Origin header is literally the string "*" (unlikely but possible)

This is correct behavior, but worth noting that the Vary header is omitted when there's no Origin header, which is fine for security but may affect caching behavior.


Performance Considerations ⚡

Positive:

  • Gateway-level CORS handling reduces overhead by not forwarding OPTIONS requests to actors
  • Preflight responses are cached for 24 hours (max-age: 86400)
  • Consistent implementation across Rust and TypeScript reduces mental overhead

Potential Issue:

  • Echoing all requested headers (access-control-allow-headers: * or echoing access-control-request-headers) might be overly permissive
  • The previous implementation had an explicit allowlist of headers

Consistency Between Implementations ✅

The Rust and TypeScript implementations are well-aligned:

Feature Rust TypeScript
Echo Origin
Credentials Support
Allowed Methods ✅ Same list ✅ Same list
Max Age ✅ 86400 ✅ 86400
Vary Header
Expose Headers * *

Minor difference: TypeScript removes content-length/content-type headers from preflight (cors.ts:39-40), while Rust doesn't explicitly do this. This is fine since Rust returns an empty body for OPTIONS.


Test Coverage 🧪

Missing: No tests included for CORS behavior

Recommendations:

  • Add tests for OPTIONS preflight requests (both Rust and TypeScript)
  • Test that Vary: Origin header is present when Origin is provided
  • Test that CORS headers are added to actual responses
  • Test behavior when no Origin header is present
  • Test WebSocket CORS handling (mentioned in comments that CORS applies to WebSockets too)

Potential Bugs 🐛

1. Header Conflict Risk

// lib.rs:213-215
for (key, value) in response_start.headers {
    response_builder = response_builder.header(key, value);
}

// lib.rs:218-221
response_builder = response_builder
    .header("access-control-allow-origin", &origin)
    .header("access-control-allow-credentials", "true")
    .header("access-control-expose-headers", "*");

If an actor sets its own CORS headers in response_start.headers, they will be overwritten by the gateway. This might be intentional, but worth documenting.

2. TypeScript async/await Pattern

// cors.ts:14
return async (c, next) => {

This is correct for Hono middleware, but ensure that all routers properly await CORS middleware. From the diffs, it looks like it's used correctly with .use().


Breaking Changes ⚠️

Configuration Changes:

  • Removed cors field from InspectorConfig
  • Removed cors field from RunnerConfig

Impact: Any users relying on custom CORS configuration will need to migrate to actor-level origin validation in onBeforeConnect. This should be documented in release notes.


Summary

Strengths:

  • ✅ Clean architectural improvement (centralized CORS handling)
  • ✅ Consistent implementation across Rust and TypeScript
  • ✅ Good security practices (Vary header, proper preflight handling)
  • ✅ Performance improvement (OPTIONS not forwarded to actors)

Concerns:

  • ⚠️ Critical: Very permissive CORS policy - ensure actors properly validate origins
  • ⚠️ Missing test coverage for CORS behavior
  • ⚠️ Breaking change requiring migration documentation
  • ℹ️ Actor CORS headers may be silently overwritten

Recommendation: Approve with the caveat that:

  1. Origin validation in onBeforeConnect is verified and documented
  2. Tests are added for CORS behavior
  3. Release notes explain the breaking change and migration path

Overall Assessment: This is a solid refactoring that improves the architecture. The main security concern is the permissive CORS policy, which appears intentional but needs clear documentation and verification that actors handle origin validation properly.

@claude
Copy link

claude bot commented Nov 4, 2025

Code Review: CORS Handling Migration to Gateway

Summary

This PR centralizes CORS handling at the gateway level, removing per-router CORS configuration and implementing a consistent CORS policy across both Rust (pegboard-gateway) and TypeScript (RivetKit) components. The change simplifies the architecture and ensures uniform CORS behavior.


✅ Strengths

1. Architectural Improvement

  • Centralizing CORS at the gateway level is a good architectural decision that reduces duplication and ensures consistency
  • The rationale for handling OPTIONS at the gateway (no x-rivet-token header) is well-documented in comments

2. Code Quality

  • Both Rust and TypeScript implementations follow the same pattern, ensuring consistency
  • Good use of structured logging in Rust: tracing::debug\!("handling OPTIONS preflight request at gateway")
  • Proper header extraction with error handling: and_then(|v| v.to_str().ok())

3. Security Considerations

  • Correctly implements Vary: Origin header to prevent cache poisoning attacks when echoing origin
  • Properly handles credentials with access-control-allow-credentials: true
  • Sets reasonable max-age of 86400 (24 hours) for preflight caching

🔒 Security Concerns

1. CRITICAL: Overly Permissive CORS Policy

The current implementation accepts requests from any origin (* fallback) when no Origin header is present:

Rust (lib.rs:79-86)

let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("*")  // ⚠️ Accepts all origins by default
    .to_string();

TypeScript (cors.ts:16)

const origin = c.req.header("origin") || "*";  // ⚠️ Same issue

Impact:

  • Any website can make credentialed requests to your API
  • Potential for CSRF attacks if the API has state-changing operations
  • Browser will allow cross-origin requests with cookies/credentials

Recommendation:

let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .filter(|o| is_allowed_origin(o))  // Validate against allowlist
    .unwrap_or_else(|| {
        tracing::warn\!("request missing or invalid origin header");
        "null"  // Reject unknown origins
    })
    .to_string();

The comment mentions "lets the actor handle CORS manually in onBeforeConnect" but the gateway is already sending permissive headers before the actor has a chance to validate.

2. Header Exposure Risk

.header("access-control-expose-headers", "*")

This exposes all response headers to the client. While convenient, this could leak sensitive headers like:

  • Internal routing information
  • Debug headers
  • Rate limit details
  • Server versions

Recommendation: Specify only the headers that need to be exposed, or document why * is necessary.

3. Allowed Methods Too Broad?

"GET, POST, PUT, DELETE, OPTIONS, PATCH"

Consider if all these methods are needed. DELETE in particular should be carefully evaluated for CORS exposure.


🐛 Potential Bugs

1. Header Conflict Risk

lib.rs:213-215

// Add headers from actor
for (key, value) in response_start.headers {
    response_builder = response_builder.header(key, value);
}

// Add CORS headers to actual request
response_builder = response_builder
    .header("access-control-allow-origin", &origin)
    // ...

If the actor response already includes CORS headers, they will be overwritten by the gateway. This could cause issues if:

  • Actor wants to restrict CORS further
  • Actor has different CORS policies for different routes

Recommendation:

  • Check if actor headers contain CORS headers before overwriting
  • Or explicitly document that gateway CORS takes precedence

2. TypeScript: Response Header Mutation After next()

cors.ts:45-55

await next();

// Add CORS headers to actual request
c.header("access-control-allow-origin", origin);

If the response is already sent by the handler, these headers may not be applied. Verify that Hono allows header modification after next().


⚡ Performance Considerations

1. String Allocation Overhead (Minor)

lib.rs:79-86

let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("*")
    .to_string();  // String allocation

The origin is extracted before the OPTIONS check, meaning it allocates a String even for non-OPTIONS requests. Consider:

let origin_header = req.headers().get("origin");

if req.method() == hyper::Method::OPTIONS {
    let origin = origin_header
        .and_then(|v| v.to_str().ok())
        .unwrap_or("*");
    // ... handle OPTIONS
}

2. Double Header Iteration

The code iterates headers twice: once at line 90-94 for extraction, then again for CORS at line 79-86. Minor, but could be optimized.


🧪 Test Coverage

MISSING: No tests found for the new CORS functionality.

Recommended Tests:

Rust Tests:

#[cfg(test)]
mod tests {
    #[tokio::test]
    async fn test_cors_preflight_with_origin() {
        // Test OPTIONS request with Origin header
    }
    
    #[tokio::test]
    async fn test_cors_preflight_without_origin() {
        // Test OPTIONS request defaults to "*"
    }
    
    #[tokio::test]
    async fn test_cors_actual_request() {
        // Test CORS headers on actual request
    }
    
    #[tokio::test]
    async fn test_vary_header_not_added_for_wildcard() {
        // Test Vary: Origin only added when origin \!= "*"
    }
}

TypeScript Tests:
Similar coverage for the cors() middleware function.


📋 Code Style & Best Practices

✅ Good:

  • Follows Rust formatting with hard tabs (per CLAUDE.md)
  • Uses structured logging with ?x syntax
  • Lowercase log messages: "handling OPTIONS preflight request at gateway"
  • Imports use use anyhow::* pattern

Minor Issues:

  1. lib.rs:100-104 - Comment says "lets the actor handle CORS manually" but implementation doesn't actually delegate to actor
  2. cors.ts:38-40 - Deleting content headers from preflight is good, but this happens after c.body(null, 204) which may have already set them. Consider doing this before setting the body.

🔄 Breaking Changes

Configuration Removal:

  • Removed cors configuration option from RunnerConfigSchema
  • Removed cors configuration from InspectorConfigSchema

Migration Impact:

  • Users with custom CORS configurations will lose their settings
  • No migration path provided for users who need custom origins

Recommendation:

  • Document this breaking change in release notes
  • Consider providing a gateway-level CORS allowlist configuration option

📝 Documentation Needs

  1. CHANGELOG: Document the CORS policy change and breaking configuration removal
  2. Security Docs: Document the permissive CORS policy and when it's safe to use
  3. Migration Guide: Help users who had custom CORS configs understand the new behavior
  4. API Docs: Document that actors can no longer control CORS headers

🎯 Action Items

High Priority:

  1. ⚠️ SECURITY: Implement origin validation/allowlist instead of accepting all origins
  2. 🧪 Add test coverage for CORS functionality
  3. 📖 Document breaking changes

Medium Priority:
4. Review access-control-expose-headers: * - specify exact headers if possible
5. Add logic to avoid overwriting actor CORS headers (or document precedence)
6. Consider making allowed methods configurable

Low Priority:
7. Minor performance optimization: defer origin string allocation
8. Verify TypeScript header mutation after next() works as expected


✨ Overall Assessment

Code Quality: ⭐⭐⭐⭐ (Good)
Security: ⚠️ ⭐⭐ (Needs attention)
Test Coverage: ⭐ (Missing)
Documentation: ⭐⭐ (Needs work)

The implementation is clean and consistent, but the overly permissive CORS policy is a significant security concern. The PR should not be merged until origin validation is added.

Recommendation: Request changes to implement proper origin validation before merging.

@claude
Copy link

claude bot commented Nov 4, 2025

PR Review: Handle CORS on the Gateway

Summary

This PR moves CORS handling from the application layer (TypeScript middleware) to the gateway layer (Rust), consolidating CORS logic and allowing preflight OPTIONS requests to be handled without requiring authentication tokens. This is a well-architected change that simplifies the system.


✅ Strengths

  1. Architecture Improvement: Moving CORS to the gateway is the right approach - it eliminates the need for actors to handle CORS individually and allows preflight requests without authentication.

  2. Consistent Implementation: Both Rust and TypeScript implementations mirror each other closely, making the codebase easier to understand and maintain.

  3. Security Considerations:

    • Correctly echoes the Origin header instead of using wildcard when credentials are enabled
    • Includes Vary: Origin header to prevent cache poisoning attacks
    • Maintains authentication requirements for non-OPTIONS requests
  4. Good Documentation: The comment in pegboard-gateway/src/lib.rs:99-104 clearly explains the reasoning for handling CORS at the gateway level.


🔍 Issues & Concerns

1. Security: Overly Permissive CORS Policy ⚠️ HIGH PRIORITY

The current implementation allows requests from any origin with credentials enabled:

let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("*")
    .to_string();

Problems:

  • This is a significant security risk. Any website can make authenticated requests to your actors
  • The combination of access-control-allow-credentials: true with echoed origins allows CSRF attacks
  • The comment mentions "lets the actor handle CORS manually in onBeforeConnect", but this protection only applies to WebSocket connections, not HTTP requests

Recommendations:

  • Implement an allowlist of permitted origins (similar to the removed defaultInspectorOrigins)
  • Consider making CORS configuration available through environment variables or actor configuration
  • If truly allowing any origin is required, document the security implications clearly
  • For HTTP requests (non-WebSocket), ensure actors validate the origin themselves

2. Missing Origin Validation ⚠️ MEDIUM PRIORITY

.unwrap_or("*")

If no Origin header is present, the code defaults to "*". However, it then sets access-control-allow-credentials: true, which is invalid per CORS spec. When credentials are included, you cannot use wildcard origins.

Recommendation:

let origin = req
    .headers()
    .get("origin")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("null");  // Use "null" instead of "*" as fallback

3. Inconsistent Vary Header Logic ⚠️ LOW PRIORITY

Both implementations only add Vary: Origin when origin != "*", but:

  • The Vary header should always be present when echoing the Origin header
  • Even if origin is "*", it was extracted from the request, so caching behavior should vary by origin

Recommendation:

// Always add Vary header when echoing origin
response = response.header("vary", "Origin");

4. Exposed Headers Wildcard ℹ️ INFORMATIONAL

.header("access-control-expose-headers", "*")

Using "*" for exposed headers prevents exposing Authorization and other authentication headers in some browsers. This may be intentional, but worth verifying.

5. Missing Tests ⚠️ MEDIUM PRIORITY

No tests were found for the CORS functionality. Given the security implications, this should have test coverage:

  • Preflight OPTIONS request handling
  • Origin echoing behavior
  • Credentials handling
  • Vary header presence
  • Invalid origin handling

🎯 Code Quality Observations

Positive:

  • ✅ Follows Rust formatting conventions (hard tabs)
  • ✅ Good use of structured logging (tracing::debug)
  • ✅ Appropriate error handling with anyhow
  • ✅ Clean code removal in TypeScript files

Suggestions:

  • Consider extracting CORS header constants to avoid duplication
  • The max-age of 86400 seconds (24 hours) is reasonable but should be documented

📋 Recommendations Summary

Must Address:

  1. Implement origin allowlist validation or document security implications
  2. Fix invalid "*" origin with credentials combination
  3. Add test coverage for CORS behavior

Should Address:
4. Fix Vary header logic to always include when echoing origin
5. Consider extracting CORS configuration to be actor-configurable

Nice to Have:
6. Document the security model in comments or docs
7. Extract CORS header constants to reduce duplication


🏁 Verdict

This is a good architectural improvement, but it introduces significant security concerns that should be addressed before merging. The overly permissive CORS policy could expose users to CSRF attacks. Please consider implementing origin validation or clearly documenting why unrestricted access is acceptable in your threat model.

Recommendation: Request changes for security issues (items 1-2) before approval.

@graphite-app
Copy link
Contributor

graphite-app bot commented Nov 5, 2025

Merge activity

  • Nov 5, 12:08 AM UTC: NathanFlurry added this pull request to the Graphite merge queue.
  • Nov 5, 12:08 AM UTC: CI is running for this pull request on a draft pull request (#3364) due to your merge queue CI optimization settings.
  • Nov 5, 12:11 AM UTC: Merged by the Graphite merge queue via draft PR: #3364.

graphite-app bot pushed a commit that referenced this pull request Nov 5, 2025
@graphite-app graphite-app bot closed this Nov 5, 2025
@graphite-app graphite-app bot deleted the 11-04-chore_guard_handle_cors_on_the_gateway branch November 5, 2025 00:11
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.

3 participants