{"record":{"id":"b01cb7b4e5be61f3","repo":"TryGhost/Ghost","slug":"request-made-from-incorrect-origin-expected-ad","errorCode":null,"errorMessage":"Request made from incorrect origin. Expected '${adminOrigin}' received '${origin}'.","messagePattern":"Request made from incorrect origin\\. Expected '(.+?)' received '(.+?)'\\.","errorType":"http","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"ghost/core/core/server/services/auth/session/session-service.js","lineNumber":137,"sourceCode":"    }\n\n    /**\n     * cookieCsrfProtection\n     *\n     * @param {Req} req\n     * @param {Session} session\n     * @returns {Promise<void>}\n     */\n    function cookieCsrfProtection(req, session) {\n        const origin = getOriginOfRequest(req);\n\n        // Check that the origin matches the admin URL to prevent cross-origin\n        // requests (e.g. no-cors form submissions from phishing sites)\n        const adminUrl = urlUtils.getAdminUrl() || urlUtils.getSiteUrl();\n        const adminOrigin = new URL(adminUrl).origin;\n\n        if (origin !== adminOrigin) {\n            throw new BadRequestError({\n                message: `Request made from incorrect origin. Expected '${adminOrigin}' received '${origin}'.`\n            });\n        }\n\n        // If there is no origin on the session object it means this is a *new*\n        // session, that hasn't been initialised yet. So we don't need CSRF protection\n        if (!session.origin) {\n            return;\n        }\n\n        if (session.origin !== origin) {\n            throw new BadRequestError({\n                message: `Request made from incorrect origin. Expected '${session.origin}' received '${origin}'.`\n            });\n        }\n    }\n\n    /**","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/ghost/core/core/server/services/auth/session/session-service.js#L119-L155","documentation":"A BadRequestError from CSRF/origin protection in `cookieCsrfProtection`. Every authenticated request's `Origin` (or `Referer`-derived origin) must match the configured admin URL origin (`urlUtils.getAdminUrl()` falling back to site URL). A mismatch means the request came from a different host, which the server treats as a cross-origin form post and rejects before touching the session.","triggerScenarios":"An authenticated request whose `Origin` header (or `Referer`-derived origin) differs from the admin URL origin. Happens with a misconfigured `url`/`admin.url` in config, behind a proxy that rewrites the host, when accessing admin via an alternate domain, or when a phishing/no-cors form posts to the admin API.","commonSituations":"Config `url` set to `http://example.com` but the user accesses `https://example.com`; a reverse proxy/Cloudflare rewrites Host and the `Origin` becomes the proxy host; the site was migrated to a new domain but config wasn't updated; the admin is reached via an IP or localhost while `url` is the public domain.","solutions":["Set `url` (and `admin.url` if used) in `config.*.json` to the exact origin users reach the admin through, including scheme.","Ensure the reverse proxy preserves/sets `X-Forwarded-Host`/`X-Forwarded-Proto` and that Ghost trusts the proxy (`url` matches the public origin).","Make all admin links/embeds use the configured admin origin consistently.","After a domain migration, update `url` and run `ghost setup ssl`/update the config to the new origin."],"exampleFix":"// before — config.production.json\n{\n  \"url\": \"http://example.com\",\n  \"admin\": {\"url\": \"https://admin.example.com\"}\n}\n\n// after — single consistent public origin\n{\n  \"url\": \"https://example.com\",\n  \"admin\": {\"url\": \"https://example.com/ghost\"}\n}","handlingStrategy":"validation","validationCode":"function assertOriginMatchesConfig(reqOrigin, adminUrl) {\n  const expected = new URL(adminUrl).origin;\n  if (reqOrigin !== expected) {\n    throw new Error(`Origin mismatch: expected ${expected}, got ${reqOrigin}. Fix config.url/admin.url.`);\n  }\n}","typeGuard":"const originsMatch = (reqOrigin, adminUrl) => {\n  try { return reqOrigin === new URL(adminUrl).origin; } catch { return false; }\n};","tryCatchPattern":"try {\n  await api.admin.someAction();\n} catch (err) {\n  if (err.type === 'BadRequestError' && /incorrect origin/i.test(err.message)) fixConfigOrigin();\n  else throw err;\n}","preventionTips":["Keep `url`/`admin.url` in config equal to the public origin users actually visit.","Configure the reverse proxy to preserve Host and X-Forwarded-Proto/Host.","After any domain change, update config and re-issue SSL."],"tags":["csrf","origin","security","config","session","proxy"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}