Skip to content

Enforce CORS for custom protocols#275

Open
lord-Rheagar wants to merge 1 commit into
MultiboxLabs:mainfrom
lord-Rheagar:codex/custom-protocol-cors
Open

Enforce CORS for custom protocols#275
lord-Rheagar wants to merge 1 commit into
MultiboxLabs:mainfrom
lord-Rheagar:codex/custom-protocol-cors

Conversation

@lord-Rheagar

@lord-Rheagar lord-Rheagar commented May 20, 2026

Copy link
Copy Markdown

Summary

  • replace the wildcard custom-protocol CORS bypass with a policy-driven CORS handler
  • cancel CORS requests to Flow custom protocols from disallowed origins
  • emit exact CORS response headers only for allowed Flow-owned custom-protocol origins

Fixes #166

Validation

  • bun run typecheck
  • bun run lint

Summary by CodeRabbit

  • Refactor
    • Enhanced CORS request handling for custom protocol URLs with improved header management and validation logic.

Review Change Stack

@lord-Rheagar lord-Rheagar requested a review from iamEvanYT as a code owner May 20, 2026 10:46
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1ec9cecf-7267-4193-a7eb-7f91918ab7be

📥 Commits

Reviewing files that changed from the base of the PR and between 55b0bb6 and 644080a.

📒 Files selected for processing (3)
  • src/main/controllers/sessions-controller/intercept-rules/cors-bypass-custom-protocols.ts
  • src/main/controllers/sessions-controller/intercept-rules/custom-protocol-cors.ts
  • src/main/controllers/sessions-controller/intercept-rules/index.ts
💤 Files with no reviewable changes (1)
  • src/main/controllers/sessions-controller/intercept-rules/cors-bypass-custom-protocols.ts

Walkthrough

Replace CORS bypass mechanism with strict CORS enforcement for custom protocols. New module defines an allowlist of target protocols (flow:, flow-internal:) and validates request origin/protocol policies via Electron webRequest hooks, rejecting disallowed requests and applying conditional CORS response headers. Integration updates intercept rules initialization.

Changes

Custom Protocol CORS Enforcement

Layer / File(s) Summary
CORS allowlist, types, and utilities
src/main/controllers/sessions-controller/intercept-rules/custom-protocol-cors.ts
Protocol allowlist constants and permission mappings are defined; a CorsRequestMetadata type persists origin and header information across the request lifecycle; utility functions extract protocols and retrieve case-insensitive header values.
CORS policy validation and enforcement
src/main/controllers/sessions-controller/intercept-rules/custom-protocol-cors.ts
Core policy check determines whether a custom-protocol request is permitted based on origin/protocol allowlist; setupCorsForCustomProtocols wires three Electron webRequest hooks to inspect requests before sending (capture metadata, reject disallowed), modify response headers (strip existing CORS headers, conditionally apply new ones), and clean up request state on completion/error/redirect.
Intercept rules integration
src/main/controllers/sessions-controller/intercept-rules/index.ts
Imports and registers setupCorsForCustomProtocols in place of the removed setupCorsBypassForCustomProtocols, activating strict CORS enforcement during session initialization.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client/Page
  participant BeforeSend as onBeforeSendHeaders
  participant Policy as Policy Check
  participant HeadersRcv as onHeadersReceived
  participant Response as Response
  participant Cleanup as Cleanup

  Client->>BeforeSend: Custom protocol request
  BeforeSend->>Policy: Extract origin & protocol
  Policy-->>BeforeSend: Policy decision (allow/deny)
  alt Policy denies
    BeforeSend-->>Client: Cancel request
  else Policy allows
    BeforeSend->>HeadersRcv: Store request metadata
    HeadersRcv->>Response: Strip existing CORS headers
    Response->>Response: Apply Access-Control-Allow-*
    Response->>Response: Merge Vary header
    Response-->>Client: Modified response
    Response->>Cleanup: Request completion
  end
  Cleanup->>Cleanup: Remove request state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit hops through protocol lands,
Where CORS rules now take their stands,
No more bypass—strict checks in place,
Origin validation wins the race,
Custom protocols, now safe and sound! 🔒

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Enforce CORS for custom protocols' directly and concisely summarizes the main change: replacing a wildcard CORS bypass with policy-driven CORS enforcement for custom protocols.
Linked Issues check ✅ Passed The PR successfully implements CORS enforcement for custom protocols (flow, flow-internal, flow-external) by replacing the wildcard bypass with a policy-driven handler that cancels disallowed requests and emits exact CORS headers, addressing all requirements from issue #166.
Out of Scope Changes check ✅ Passed All changes are directly related to CORS enforcement for custom protocols: removal of the old bypass function, addition of the new conservative CORS handler, and integration into the intercept rules pipeline. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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 and usage tips.

@greptile-apps

greptile-apps Bot commented May 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the old wildcard Access-Control-Allow-Origin: * bypass for flow: and flow-internal: with a policy-driven CORS handler that validates the request origin against a per-target-protocol allowlist and cancels requests from disallowed origins, while also extending enforcement to the previously unhandled flow-external: protocol.

  • New handler (custom-protocol-cors.ts): tracks in-flight CORS request metadata in onBeforeSendHeaders, injects exact-origin CORS response headers in onHeadersReceived, and cleans up via onCompleted/onErrorOccurred/onBeforeRedirect.
  • Redirect handling is correct: onHeadersReceived processes the redirect response while metadata is still present, onBeforeRedirect then clears the slot, and onBeforeSendHeaders for the redirected URL may re-populate it.
  • Two gaps in CORS header restoration: Access-Control-Allow-Credentials and Access-Control-Expose-Headers are stripped from every custom-protocol response but never re-emitted, which will silently break credentialed fetches and custom header exposure for allowed origins.

Confidence Score: 4/5

The core security improvement is sound — wildcard CORS is gone, origins are validated against a strict allowlist, and disallowed requests are cancelled before they reach the protocol handler. The change is safe to merge for typical flows between the custom protocols.

The origin-validation and request-lifecycle logic are correct and well-structured. Two headers (Access-Control-Allow-Credentials and Access-Control-Expose-Headers) are stripped from every custom-protocol response but never restored, which would silently break any credentialed cross-origin fetch or header-exposure scenario from an allowed origin. A minor Vary deduplication issue with case-sensitive Set comparisons could produce duplicate tokens. Neither issue affects the existing protocol handlers today, but they are behaviour regressions worth fixing before the allowlist is widened.

src/main/controllers/sessions-controller/intercept-rules/custom-protocol-cors.ts — specifically the onHeadersReceived injection block and the appendVaryHeader helper.

Important Files Changed

Filename Overview
src/main/controllers/sessions-controller/intercept-rules/custom-protocol-cors.ts New policy-driven CORS handler for Flow custom protocols; correctly enforces origin-protocol allowlists and cleans up request metadata, but strips Access-Control-Allow-Credentials and Access-Control-Expose-Headers without restoring them, and the Vary deduplication is case-sensitive.
src/main/controllers/sessions-controller/intercept-rules/cors-bypass-custom-protocols.ts Deleted file — old wildcard CORS bypass for flow: and flow-internal: protocols, correctly removed in favour of the new policy-driven handler.
src/main/controllers/sessions-controller/intercept-rules/index.ts Trivial import and call-site swap from the old bypass function to the new CORS handler; no logic concerns.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant onBeforeSendHeaders
    participant corsRequests as corsRequests Map
    participant onHeadersReceived
    participant ProtocolHandler as Protocol Handler (flow://)

    Browser->>onBeforeSendHeaders: request with Origin header
    onBeforeSendHeaders->>onBeforeSendHeaders: check target protocol in CUSTOM_PROTOCOLS
    alt origin not allowed
        onBeforeSendHeaders-->>Browser: cancel: true
    else origin allowed
        onBeforeSendHeaders->>corsRequests: "set(id, {origin, requestedMethod, requestedHeaders})"
        onBeforeSendHeaders-->>Browser: proceed
        Browser->>ProtocolHandler: send request
        ProtocolHandler-->>onHeadersReceived: response headers
        onHeadersReceived->>onHeadersReceived: stripCorsResponseHeaders()
        onHeadersReceived->>corsRequests: get(id)
        corsRequests-->>onHeadersReceived: metadata
        onHeadersReceived->>onHeadersReceived: "set Access-Control-Allow-Origin = exact origin"
        onHeadersReceived->>onHeadersReceived: set ACAM / ACAH (if preflight)
        onHeadersReceived->>onHeadersReceived: appendVaryHeader()
        onHeadersReceived-->>Browser: modified response headers
        Browser->>corsRequests: onCompleted / onErrorOccurred / onBeforeRedirect delete(id)
    end
Loading

Reviews (1): Last reviewed commit: "Enforce CORS for custom protocols" | Re-trigger Greptile

Comment on lines +61 to +76
function appendVaryHeader(responseHeaders: Record<string, string[]>, values: string[]) {
const existingHeader = Object.keys(responseHeaders).find((header) => header.toLowerCase() === "vary");
const existingValues = existingHeader ? responseHeaders[existingHeader] : [];
const mergedValues = new Set<string>();

for (const value of existingValues) {
value
.split(",")
.map((item) => item.trim())
.filter(Boolean)
.forEach((item) => mergedValues.add(item));
}

values.forEach((value) => mergedValues.add(value));
responseHeaders[existingHeader ?? "Vary"] = [Array.from(mergedValues).join(", ")];
}

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 Case-sensitive deduplication in appendVaryHeader

The mergedValues Set uses string equality for deduplication, so it is case-sensitive. If the existing Vary header already contains a value like "origin" (lowercase) and this function appends "Origin" (title-case), both tokens survive as distinct Set entries and the resulting header becomes "origin, Origin, ...". HTTP/1.1 header field names are case-insensitive, so proxies and cache layers will see duplicate effective tokens. Normalize all values to lowercase (or a single canonical casing) before adding them to mergedValues.

Comment on lines +140 to +149
if (corsRequest && isAllowedCustomProtocolCorsRequest(details.url, corsRequest.origin)) {
responseHeaders["Access-Control-Allow-Origin"] = [corsRequest.origin];
if (corsRequest.requestedMethod) {
responseHeaders["Access-Control-Allow-Methods"] = [corsRequest.requestedMethod];
}
if (corsRequest.requestedHeaders) {
responseHeaders["Access-Control-Allow-Headers"] = [corsRequest.requestedHeaders];
}
appendVaryHeader(responseHeaders, ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]);
}

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 Access-Control-Allow-Credentials stripped but never restored

stripCorsResponseHeaders removes the access-control-allow-credentials header from every custom-protocol response, and the injection block never adds it back. A credentialed fetch (e.g. fetch(url, { credentials: 'include' })) from an allowed origin will receive a response with Access-Control-Allow-Origin set to the exact origin, but without Access-Control-Allow-Credentials: true. The browser will block the response even though the origin is allowed. Access-Control-Expose-Headers has the same problem — it is stripped and never re-applied, so custom response headers are silently hidden from JavaScript.

Suggested change
if (corsRequest && isAllowedCustomProtocolCorsRequest(details.url, corsRequest.origin)) {
responseHeaders["Access-Control-Allow-Origin"] = [corsRequest.origin];
if (corsRequest.requestedMethod) {
responseHeaders["Access-Control-Allow-Methods"] = [corsRequest.requestedMethod];
}
if (corsRequest.requestedHeaders) {
responseHeaders["Access-Control-Allow-Headers"] = [corsRequest.requestedHeaders];
}
appendVaryHeader(responseHeaders, ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]);
}
if (corsRequest && isAllowedCustomProtocolCorsRequest(details.url, corsRequest.origin)) {
responseHeaders["Access-Control-Allow-Origin"] = [corsRequest.origin];
responseHeaders["Access-Control-Allow-Credentials"] = ["true"];
if (corsRequest.requestedMethod) {
responseHeaders["Access-Control-Allow-Methods"] = [corsRequest.requestedMethod];
}
if (corsRequest.requestedHeaders) {
responseHeaders["Access-Control-Allow-Headers"] = [corsRequest.requestedHeaders];
}
appendVaryHeader(responseHeaders, ["Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]);
}

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.

[SECURITY] CORS for custom protocols

1 participant