remix/middleware/cop
Cross-origin protection middleware for Remix. It mirrors Go's CrossOriginProtection by rejecting unsafe cross-origin browser requests without synchronizer tokens.
Features
- Browser Provenance Checks - Uses
Sec-Fetch-Sitewhen present and falls back toOrigin - Trusted Origins - Allow specific cross-origin callers by exact origin
- Explicit Escape Hatches - Support insecure bypass patterns for endpoints like webhooks
- No Session State - Does not require synchronizer tokens or server-side CSRF storage
Installation
npm i remixUsage
import { createRouter } from 'remix/router'
import { cop } from 'remix/middleware/cop'
let router = createRouter({
middleware: [cop()],
})Behavior
For requests whose original method is unsafe (POST, PUT, PATCH, DELETE), cop() follows the same broad model as Go's CrossOriginProtection. Routing middleware may change context.method, but method overrides do not change whether cop() checks the request:
- Allow
Sec-Fetch-Site: same-origin - Allow
Sec-Fetch-Site: none - Reject other
Sec-Fetch-Sitevalues unless the request matches a trusted origin or insecure bypass - If
Sec-Fetch-Siteis missing, compareOriginto the request host - If both
Sec-Fetch-SiteandOriginare missing, allow the request
This middleware is intentionally tokenless. If you cannot guarantee the deployment assumptions behind that model, prefer csrf-middleware.
Caveats
cop()is a browser-origin guard, not a universal CSRF solution. It is designed for deployments that can rely on modern browser provenance signals and same-origin request handling.- If both
Sec-Fetch-SiteandOriginare missing on an unsafe request,cop()allows the request to continue. This is intentional so older clients and non-browser callers do not fail closed by default. - If
Sec-Fetch-Siteis missing,cop()only rejects whenOriginis present and does not match the request host. - If you need stronger guarantees for session-backed form workflows, mixed deployment environments, or requests that should not fall through when browser provenance headers are missing, use
csrf-middlewareor layer both middlewares together.
Using with csrf-middleware
You can also layer cop() in front of csrf() when you want both browser provenance checks and session-backed synchronizer tokens.
import { createCookie } from 'remix/cookie'
import { createRouter } from 'remix/router'
import { createCookieSessionStorage } from 'remix/session-storage/cookie'
import { session } from 'remix/middleware/session'
import { cop } from 'remix/middleware/cop'
import { csrf } from 'remix/middleware/csrf'
let sessionCookie = createCookie('__session', { secrets: ['secret1'] })
let sessionStorage = createCookieSessionStorage()
let router = createRouter({
middleware: [cop(), session(sessionCookie, sessionStorage), csrf()],
})In this setup, cop() runs first and rejects unsafe cross-origin browser requests early using Sec-Fetch-Site and Origin. Requests that pass cop() continue into csrf(), which still enforces synchronizer-token validation and origin checks for the remaining traffic.
Trusted Origins
import { createRouter } from 'remix/router'
import { cop } from 'remix/middleware/cop'
let router = createRouter({
middleware: [
cop({
trustedOrigins: ['https://admin.example.com'],
}),
],
})Trusted origins must be exact origin values in the form scheme://host[:port].
Insecure Bypass Patterns
Bypass patterns intentionally weaken protection for specific endpoints. Method prefixes match the original request method, even when routing middleware overrides context.method. They support:
- Optional method prefixes, for example
POST /webhooks/{provider} - Exact paths, for example
/healthz - Trailing-slash subtree patterns, for example
/webhooks/ - Single-segment wildcards with
{name} - Tail wildcards with
{name...}
import { createRouter } from 'remix/router'
import { cop } from 'remix/middleware/cop'
let router = createRouter({
middleware: [
cop({
insecureBypassPatterns: ['POST /webhooks/{provider}', '/healthz'],
}),
],
})Related Packages
csrf-middleware- Session-backed CSRF protection with synchronizer tokensfetch-router- Router for the web Fetch API
License
See LICENSE