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

  1. Fix the proxy to pass the original Origin through unchanged (or clear it) instead of rewriting it to a scheme-less value
  2. If you set Origin manually in client code, send a fully-qualified URL like https://example.com or omit the header
  3. 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

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


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)