decolua/9router · warning
Cross-origin callback rejected
Error message
Cross-origin callback rejected
What it means
startTraeProxy enforces an anti-CSRF check on the OAuth callback: requests must either carry no Origin header (like a genuine browser redirect from a top-level navigation) or have an Origin that resolves to a loopback host. A request with a cross-origin Origin header is answered 403 with 'Cross-origin callback rejected'. This prevents a malicious web page from scripting fetches against the loopback callback endpoint to inject forged authorization codes.
Source
Thrown at src/lib/oauth/utils/server.js:483
return;
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, "http://localhost");
if (url.pathname !== TRAE_CONFIG.callbackPath && url.pathname !== "/auth/callback") {
res.writeHead(404);
res.end("Not found");
return;
}
const session = traeSession;
if (!session) {
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.end(renderCodexResultPage(false, "No active Trae login session"));
return;
}
// Anti-CSRF: reject cross-origin fetches (legit redirects send no Origin),
// and reject state mismatch when state is present.
if (!isLoopbackOrigin(req.headers.origin)) {
res.writeHead(403, { "Content-Type": "text/html; charset=utf-8" });
res.end(renderCodexResultPage(false, "Cross-origin callback rejected"));
return;
}
const cbState = url.searchParams.get("state");
if (cbState && session.state && cbState !== session.state) {
session.status = "error";
session.error = "Trae callback state mismatch";
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.end(renderCodexResultPage(false, session.error));
stopTraeProxy();
return;
}
// Pass the raw callback query to exchangeTokens → parseTraeCallback
const rawCallback = `${url.pathname}?${url.searchParams.toString()}`;
try {
const { exchangeTokens } = await import("../providers.js");
const { createProviderConnection } = await import("@/models");
const tokenData = await exchangeTokens("trae", rawCallback);View on GitHub (pinned to 90b52e06ff)
Solutions
- Complete the login through the normal redirect flow — top-level redirects send no Origin header and pass the check.
- If testing through a tunnel, test against http://127.0.0.1 directly instead of the public tunnel URL.
- If an extension/proxy injects an Origin header, disable it or exclude localhost from its rewriting rules.
Example fix
// before (test via curl with a foreign Origin) curl -H 'Origin: https://evil.example' 'http://127.0.0.1:3456/callback?code=...&state=...' // after curl 'http://127.0.0.1:3456/callback?code=...&state=...' // no Origin header → accepted
Defensive patterns
Strategy: try-catch
Validate before calling
const origin = req.headers.origin;
if (origin && !['http://127.0.0.1','http://localhost'].some(h => origin.startsWith(h))) {
// cross-origin scripted request — will be rejected; use a top-level redirect instead
} Type guard
function isLoopbackOrigin(origin) {
if (!origin) return true; // redirects send no Origin
try { const u = new URL(origin); return ['127.0.0.1','localhost','[::1]'].includes(u.hostname); }
catch { return false; }
} Try / catch
const res = await fetch(callbackUrl, { redirect: 'manual' });
if (res.status === 403 && (await res.text()).includes('Cross-origin callback rejected')) {
console.warn('CSRF guard fired — complete login via normal redirect, not scripted fetch');
} Prevention
- Never fetch/post to the loopback callback URL from browser scripts or other tabs.
- Disable extensions/proxies that add Origin headers to localhost traffic.
- Test callbacks with plain curl (no Origin header) or via the app's real redirect.
- If using tunnels for testing, drive the browser through http://127.0.0.1, not the tunnel URL.
When it happens
Trigger: A browser page on a non-loopback origin (e.g. http://evil.example) issues a fetch/XHR to http://127.0.0.1:<traePort>/...callback during an active Trae session; CORS preflight or credentialed cross-site calls; misconfigured redirect landing from a non-loopback host header context that sets Origin.
Common situations: Clickjacking/phishing page attempting a CSRF login attack; browser extensions proxying requests with an Origin header; corporate proxies that rewrite requests and attach an external Origin; developer testing the callback via a forwarded public tunnel (ngrok) whose Origin is not loopback.
Related errors
- Windsurf callback state mismatch
- This request isn't valid. Please restart the Kimchi login fl
- Invalid state parameter
- Invalid state parameter
- Kiro tool input must be a JSON object
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/6642703b4c950a1d.
Report an issue: GitHub.