remix-run/react-router · error · Error
`origin` header is not a valid URL. Aborting the action.
Error message
`origin` header is not a valid URL. Aborting the action.
What it means
validateActionOrigin() guards forwarded data-mode actions against CSRF by parsing the request's Origin header. The literal string 'null' is tolerated, but any other value must parse via new URL(); when parsing throws, the action is aborted with this error rather than proceeding with an unverifiable origin. The header is attacker-controllable input, so the library fails closed.
Solutions
- Fix the proxy to pass the original Origin through unchanged (or clear it) instead of rewriting it to a scheme-less value
- If you set Origin manually in client code, send a fully-qualified URL like https://example.com or omit the header
- Verify with curl -H 'Origin: https://example.com' that requests now pass the check
Example fix
# before (nginx — sets Origin to a bare host, not a valid URL) proxy_set_header Origin $host; # after proxy_set_header Origin $scheme://$host;
Defensive patterns
Strategy: type-guard
Validate before calling
// validate before forwarding action requests (proxy/gateway side)
function isValidOriginHeader(origin: string | null): boolean {
if (origin === null || origin === "null") return true;
try {
new URL(origin);
return true;
} catch {
return false;
}
}
if (!isValidOriginHeader(req.headers.get("origin"))) {
return new Response("Bad Request", { status: 400 });
} Type guard
function isValidOriginHeader(origin: string | null): boolean {
if (origin === null || origin === "null") return true;
try {
new URL(origin);
return true;
} catch {
return false;
}
} Prevention
- Never rewrite Origin to a scheme-less value in proxy config — pass it through or set $scheme://$host
- Always send fully-qualified origins (https://example.com) from custom clients
- Add an integration test that posts an action with a valid Origin header
When it happens
Trigger: A request reaches the router with an Origin header that is not a valid absolute URL and not the literal 'null' — e.g. 'example.com' (no scheme), '[::1]:3000' (bare host:port), or garbage injected by a proxy directive like proxy_set_header Origin $host.
Common situations: Reverse proxies rewriting Origin to a scheme-less host; custom fetch wrappers or curl invocations setting Origin manually to a bare hostname; API gateways appending values to the header.
Related errors
- The `request.url` origin does not match `origin` header…
- Invalid redirect location
- Invalid redirect location
- Invalid redirect location
- Prerender: Request failed for
AI-assisted analysis of remix-run/react-router@c091832969 (2026-08-21).
Data as JSON: /api/errors/6b3554626473d9ab.
Report an issue: GitHub.
Appendix: source
Thrown at packages/react-router/lib/actions.ts:17
export function throwIfPotentialCSRFAttack(
request: Request,
allowedActionOrigins: string[] | undefined,
) {
let originHeader = request.headers.get("origin");
let originDomain: string | null = null;
let originUrl: URL | null = null;
try {
if (typeof originHeader === "string" && originHeader !== "null") {
originUrl = new URL(originHeader);
originDomain = originUrl.host;
} else {
originDomain = originHeader;
}
} catch {
throw new Error(
`\`origin\` header is not a valid URL. Aborting the action.`,
);
}
let requestUrl = new URL(request.url);
let originMatchesRequest = originUrl
? originUrl.origin === requestUrl.origin
: originDomain === requestUrl.host;
if (originDomain && !originMatchesRequest) {
if (!isAllowedOrigin(originDomain, allowedActionOrigins)) {
// This seems to be an CSRF attack. We should not proceed with the action.
throw new Error(
"The `request.url` origin does not match `origin` header from a forwarded " +
"action request. Aborting the action.",
);
}
}
}View on GitHub (pinned to c091832969)