Skip to content

fix(execute): reject only cross-site session execution (CSRF guard)#5068

Merged
TheodoreSpeaks merged 1 commit into
stagingfrom
fix/execute-cross-site-guard
Jun 15, 2026
Merged

fix(execute): reject only cross-site session execution (CSRF guard)#5068
TheodoreSpeaks merged 1 commit into
stagingfrom
fix/execute-cross-site-guard

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

Why #5062 was reverted, and what changed

#5062 rejected anything that wasn't same-origin (it 403'd same-site). On a multi-subdomain deployment — e.g. hitting the app at www.staging.sim.ai while the canonical origin is staging.sim.ai — a legitimate Run POST is same-site, so it got 403'd and Run broke. That same-site tightening came from a review suggestion; it's unsafe for this topology.

This version accepts same-site so sibling-subdomain Run works, while still blocking real cross-site CSRF (Sec-Fetch-Site: cross-site is browser-set and cannot be forged by page JS). The brittle Origin/getBaseUrl() fallback is removed entirely.

Note for reviewers/Greptile: accepting same-site is intentional and required for the multi-subdomain deployment — please don't suggest narrowing to same-origin only; that's the exact change that broke staging.

Scope / what this is not

CSRF protection only. It does not defend against a non-browser client that forges headers directly (no header check can); that surface is covered by the credit and execution rate-limit gates.

Type of Change

  • Bug fix

Testing

  • Unit tests (5 cases incl. same-site allowed) + route tests: a cross-site request → 403, and a same-site Run → 202 (the exact regression that broke staging)
  • Validated against current origin/staging code: bun run check:api-validation:strict passed, tsc --noEmit clean, biome clean

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 15, 2026 8:11pm

Request Review

@cursor

cursor Bot commented Jun 15, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches authentication on a high-traffic execution endpoint; mis-tuning fetch-site rules could block legitimate runs or weaken CSRF protection, though scope is limited to session auth and behavior is heavily tested.

Overview
Re-lands a session-cookie CSRF guard on POST /api/workflows/[id]/execute, blocking only requests where the browser reports Sec-Fetch-Site: cross-site. Session-authenticated runs that are same-origin, same-site (e.g. www → apex), none, or missing the header still proceed; API key, public API, and internal JWT paths are unchanged.

A new helper isCrossSiteSessionRequest centralizes that check (replacing a stricter same-origin-only approach that broke multi-subdomain Run). The route returns 403 Access denied immediately after hybrid auth, before authorization or queuing. Unit and route tests cover cross-site rejection and same-site async acceptance.

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

@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR re-lands the session-cookie CSRF guard on POST /api/workflows/[id]/execute, fixing the regression from #5062 which incorrectly blocked same-site requests and broke Run on multi-subdomain deployments. The guard now only rejects requests where Sec-Fetch-Site: cross-site is explicitly set, while allowing same-origin, same-site, none, and absent headers.

  • apps/sim/lib/core/security/same-origin.ts: New utility isCrossSiteSessionRequest — a single-line header check scoped to the browser's unforgeable Sec-Fetch-Site header, with thorough JSDoc explaining the same-site allowance and the known non-browser limitation.
  • apps/sim/app/api/workflows/[id]/execute/route.ts: Guard is placed after checkHybridAuth (intentional — auth type must be known first) and fires only for AuthType.SESSION, leaving API-key, public-API, and internal-JWT paths untouched.
  • Tests: Five unit tests on the helper and two new route-level integration tests — one covering the cross-site → 403 path and one covering the same-site → 202 regression scenario.

Confidence Score: 4/5

Safe to merge — the guard is narrowly scoped to session-authenticated execute calls and the same-site allowance is correctly preserved to avoid repeating the staging regression.

The core logic is a single-line header comparison with thorough tests. The only non-trivial design point is that session validation runs before the CSRF check, meaning a valid session cookie is still looked up even for requests the guard will ultimately reject — a minor efficiency trade-off, not a correctness issue.

No files require special attention; the route change is small and well-isolated.

Important Files Changed

Filename Overview
apps/sim/lib/core/security/same-origin.ts New utility that returns true only when Sec-Fetch-Site is exactly 'cross-site'; well-documented with clear rationale for allowing same-site and absent headers.
apps/sim/lib/core/security/same-origin.test.ts Five unit tests covering cross-site, same-origin, same-site, none, and absent header cases — complete coverage of the function's decision surface.
apps/sim/app/api/workflows/[id]/execute/route.ts CSRF guard added after checkHybridAuth; guard is scoped correctly to AuthType.SESSION and fires before workflow-level authorization, though session validation still runs before CSRF rejection.
apps/sim/app/api/workflows/[id]/execute/route.async.test.ts Two new route-level tests: cross-site → 403 (guard fires before authz work) and same-site → 202 (the exact regression that broke staging); both exercise the right mock configuration.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant ExecuteRoute as POST /execute
    participant HybridAuth as checkHybridAuth
    participant CSRFGuard as isCrossSiteSessionRequest
    participant WorkflowAuthz as authorizeWorkflow

    Browser->>ExecuteRoute: POST (with session cookie)
    ExecuteRoute->>HybridAuth: checkHybridAuth(req)
    HybridAuth-->>ExecuteRoute: "{ success, authType, userId }"

    alt "authType === SESSION"
        ExecuteRoute->>CSRFGuard: isCrossSiteSessionRequest(req)
        alt Sec-Fetch-Site: cross-site
            CSRFGuard-->>ExecuteRoute: true
            ExecuteRoute-->>Browser: 403 Access denied
        else "same-origin | same-site | none | absent"
            CSRFGuard-->>ExecuteRoute: false
            ExecuteRoute->>WorkflowAuthz: authorizeWorkflowByWorkspacePermission(...)
            WorkflowAuthz-->>ExecuteRoute: "{ allowed, workflow }"
            ExecuteRoute-->>Browser: 200/202 execution result
        end
    else API_KEY / INTERNAL_JWT / PUBLIC_API
        Note over ExecuteRoute: CSRF guard skipped
        ExecuteRoute->>WorkflowAuthz: authorizeWorkflowByWorkspacePermission(...)
        WorkflowAuthz-->>ExecuteRoute: "{ allowed, workflow }"
        ExecuteRoute-->>Browser: 200/202 execution result
    end
Loading

Reviews (1): Last reviewed commit: "fix(execute): reject only cross-site ses..." | Re-trigger Greptile

Comment thread apps/sim/app/api/workflows/[id]/execute/route.ts
@TheodoreSpeaks TheodoreSpeaks merged commit 2cf5172 into staging Jun 15, 2026
15 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the fix/execute-cross-site-guard branch June 15, 2026 20:19
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