Skip to content

fix(execute): block cross-origin session-authenticated workflow runs#5062

Merged
TheodoreSpeaks merged 2 commits into
stagingfrom
feat/kill-bots
Jun 15, 2026
Merged

fix(execute): block cross-origin session-authenticated workflow runs#5062
TheodoreSpeaks merged 2 commits into
stagingfrom
feat/kill-bots

Conversation

@TheodoreSpeaks

@TheodoreSpeaks TheodoreSpeaks commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add a CSRF guard to POST /api/workflows/[id]/execute, scoped strictly to session-cookie auth: reject requests that are provably cross-origin (Sec-Fetch-Site anything other than same-origin, or a mismatched Origin)
  • Closes a real CSRF hole — a malicious site could previously drive a logged-in user's browser into executing workflows (drive-by compute spend)
  • Real Run button (same-origin browser fetch) is unaffected; API-key / public-API / internal-JWT callers don't use cookies, so they're untouched
  • Returns the route's standard Access denied 403 so the rejection is indistinguishable from a normal authorization failure

Scope / what this is not

  • This is CSRF protection, not a defense against a non-browser client that forges headers directly (e.g. a script replaying a borrowed session cookie). No header-based check can stop that — the request controls its own headers. That surface is covered by the credit and execution rate-limit gates, not this guard.

Type of Change

  • Bug fix

Testing

  • Unit tests for the guard (8 cases) + a route-level cross-origin rejection test — all passing
  • bun run check:api-validation:strict passed
  • tsc --noEmit clean; biome clean on changed files

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 7:28pm

Request Review

@cursor

cursor Bot commented Jun 15, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches a high-impact execution endpoint and session auth, but the change is narrowly scoped to session cookies with early 403; legitimate same-origin UI runs and non-cookie callers should be unaffected.

Overview
Adds a CSRF guard on POST /api/workflows/[id]/execute for session-cookie auth only: after checkHybridAuth, provably cross-origin browser requests are rejected with 403 Access denied before workflow authorization or execution enqueue.

New helper isCrossOriginSessionRequest (lib/core/security/same-origin) treats anything other than Sec-Fetch-Site: same-origin as cross-origin, with an Origin same-origin fallback when Sec-Fetch-Site is missing; requests with neither header are allowed (documented as browser CSRF-only, not anti-forgery for non-browser clients). API key / public API / internal JWT paths are unchanged.

Coverage: 8 unit tests for the helper and a route test asserting cross-site session POSTs fail without calling authorize or enqueue.

Reviewed by Cursor Bugbot for commit 80c0ebf. 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

Adds a Fetch Metadata–based CSRF guard to POST /api/workflows/[id]/execute, scoped exclusively to session-cookie authentication. The guard uses Sec-Fetch-Site as the primary signal (only same-origin is trusted) and falls back to an Origin header check; when neither header is present the request is allowed (fail-open), which is intentional and safe because a genuine browser-driven CSRF attack cannot omit both headers.

  • apps/sim/lib/core/security/same-origin.ts: New isCrossOriginSessionRequest utility — rejects anything other than same-origin on Sec-Fetch-Site, falls back to isSameOrigin() on Origin, and correctly wraps both paths in try/catch so a missing NEXT_PUBLIC_APP_URL cannot propagate.
  • route.ts: Guard is injected immediately after checkHybridAuth, conditional on auth.success && auth.authType === AuthType.SESSION, so API-key, public-API, and internal-JWT callers are completely unaffected.
  • Tests: 8-case unit suite in same-origin.test.ts and one route-level integration test added to route.async.test.ts; together they cover same-origin allow, same-site/cross-site/none reject, Origin fallback, no-headers allow, and header-precedence.

Confidence Score: 5/5

Safe to merge — the CSRF guard is correctly scoped to session auth, cannot block legitimate API-key or internal callers, and the fail-open behavior for header-less requests is intentional and documented in the code.

The guard logic, its tests, and the route integration are all consistent and correct. The only finding is a PR description that contradicts the actual behavior on two points (same-site and no-headers), which does not affect runtime behaviour.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/lib/core/security/same-origin.ts New CSRF guard utility — correct Fetch Metadata + Origin fallback logic, well-documented trade-offs, no issues.
apps/sim/lib/core/security/same-origin.test.ts 8-case unit test suite covering same-origin allow, same-site/cross-site/none reject, Origin fallback, no-headers allow, and header precedence — comprehensive coverage.
apps/sim/app/api/workflows/[id]/execute/route.ts CSRF guard injected correctly after checkHybridAuth and scoped to SESSION auth type — API-key / internal-JWT paths are unaffected.
apps/sim/app/api/workflows/[id]/execute/route.async.test.ts Integration test added confirming 403 on cross-site session request before permission checks run.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser (same-origin)
    participant Attacker as Attacker Site (CSRF)
    participant Script as Non-browser Client
    participant Route as POST /api/workflows/[id]/execute
    participant Auth as checkHybridAuth
    participant Guard as isCrossOriginSessionRequest
    participant Perm as authorizeWorkflow

    Browser->>Route: POST (Sec-Fetch-Site: same-origin, session cookie)
    Route->>Auth: checkHybridAuth → SESSION
    Route->>Guard: "secFetchSite === same-origin → false"
    Route->>Perm: proceed normally
    Perm-->>Browser: 200 OK

    Attacker->>Route: POST (Sec-Fetch-Site: cross-site, borrowed cookie)
    Route->>Auth: checkHybridAuth → SESSION
    Route->>Guard: "secFetchSite !== same-origin → true"
    Route-->>Attacker: 403 Access denied

    Script->>Route: POST (no Sec-Fetch-Site, no Origin, session cookie)
    Route->>Auth: checkHybridAuth → SESSION
    Route->>Guard: no headers → false (fail-open)
    Route->>Perm: proceed normally
    Perm-->>Script: 200 OK

    Note over Route,Guard: API-key / internal-JWT callers skip guard entirely
Loading

Reviews (2): Last reviewed commit: "fix(execute): scope session origin guard..." | Re-trigger Greptile

Comment thread apps/sim/lib/core/security/same-origin.ts
Comment thread apps/sim/lib/core/security/same-origin.ts
Address review on #5062:
- Reject session-cookie execution only when provably cross-origin (Sec-Fetch-Site
  cross-site/same-site/none, or a mismatched Origin) instead of failing closed on
  absent headers. Fixes route tests that 403'd on header-less session requests, and
  reflects that this is CSRF protection, not anti-cookie-replay.
- Drop same-site from the trusted set: only same-origin is our front-end.
- Guard the Origin fallback in try/catch so a getBaseUrl() throw can't escape.
- Add a route-level cross-origin rejection test.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@TheodoreSpeaks TheodoreSpeaks merged commit 67e02fa into staging Jun 15, 2026
15 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the feat/kill-bots branch June 15, 2026 19:39
TheodoreSpeaks added a commit that referenced this pull request Jun 15, 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