Skip to content

feat(sandbox): add per-sandbox CORS header injection for port-forwarded services#514

Closed
drew wants to merge 1 commit intomainfrom
feat/per-sandbox-cors-headers
Closed

feat(sandbox): add per-sandbox CORS header injection for port-forwarded services#514
drew wants to merge 1 commit intomainfrom
feat/per-sandbox-cors-headers

Conversation

@drew
Copy link
Collaborator

@drew drew commented Mar 20, 2026

Summary

Adds opt-in, per-sandbox CORS configuration for port-forwarded services. When a sandbox policy endpoint has cors.allowed_origins configured, the SSH direct-tcpip handler uses a CORS-aware HTTP relay instead of raw copy_bidirectional, injecting CORS response headers and validating WebSocket upgrade origins.

Changes

  • proto/sandbox.proto — Added CorsConfig message (just allowed_origins) and cors field on NetworkEndpoint
  • crates/openshell-policy/src/lib.rs — Added CorsConfigDef serde struct, YAML/proto wiring, CorsEmptyOrigins validation, 4 new tests
  • crates/openshell-sandbox/src/cors_relay.rs — New CORS-aware HTTP relay module with 8 unit tests. Handles:
    • HTTP detection via first-byte peek (non-HTTP falls back to raw relay)
    • OPTIONS preflight synthesis (never hits upstream)
    • WebSocket upgrade Origin validation (rejects with 403 if disallowed)
    • CORS header injection on normal HTTP responses
  • crates/openshell-sandbox/src/ssh.rsSshHandler now carries CorsConfigMap; channel_open_direct_tcpip conditionally uses CORS relay
  • crates/openshell-sandbox/src/lib.rs — Extracts CORS configs from proto policy at startup, passes to SSH server, hot-reloads on policy update

Example policy YAML

network_policies:
  web-service:
    endpoints:
      - host: localhost
        port: 8080
        cors:
          allowed_origins:
            - "https://app.example.com"
            - "https://dashboard.example.com"

Design decisions

  • Only allowed_origins is user-configurable. Methods, headers, and max-age use hardcoded defaults. Keeps the surface minimal and avoids footgun configurations.
  • Zero overhead for non-configured ports. Ports without cors in the policy use the existing raw copy_bidirectional path unchanged.
  • WebSocket origin validation is server-enforced. Browsers don't enforce CORS on WebSocket connections, so the relay validates the Origin header during the upgrade handshake and rejects unauthorized origins with 403.

Testing

  • mise run pre-commit passes
  • Unit tests added/updated (12 new tests across policy + sandbox)
  • E2E tests added/updated (if applicable)

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

…ed services

Adds opt-in CORS configuration to sandbox network policy endpoints.
When configured, the SSH direct-tcpip handler injects CORS response
headers on HTTP traffic and validates Origin on WebSocket upgrade
requests. Non-configured ports remain raw TCP with zero overhead.
@drew drew requested a review from a team as a code owner March 20, 2026 22:46
@drew drew self-assigned this Mar 20, 2026
@drew drew closed this Mar 20, 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