{"record":{"id":"dbf889ad5019ab93","repo":"RocketChat/Rocket.Chat","slug":"unauthorized-redirect-origin","errorCode":null,"errorMessage":"Unauthorized redirect origin","messagePattern":"Unauthorized redirect origin","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"apps/meteor/server/lib/saml/lib/SAML.ts","lineNumber":439,"sourceCode":"\t\t\tres.writeHead(400);\n\t\t\tres.end('Missing redirect parameter');\n\t\t\treturn;\n\t\t}\n\n\t\tlet configuredURL: URL;\n\t\tlet requestURL: URL;\n\n\t\ttry {\n\t\t\tconfiguredURL = new URL(idpSLORedirectURL);\n\t\t\trequestURL = new URL(userRedirect);\n\t\t} catch {\n\t\t\tres.writeHead(400);\n\t\t\tres.end('Invalid URL format');\n\t\t\treturn;\n\t\t}\n\n\t\tif (configuredURL.origin !== requestURL.origin) {\n\t\t\tres.writeHead(403);\n\t\t\tres.end('Unauthorized redirect origin');\n\t\t\treturn;\n\t\t}\n\n\t\tconst normalizePath = (p: string): string => p.replace(/\\/+$/, '') || '/';\n\t\tif (normalizePath(configuredURL.pathname) !== normalizePath(requestURL.pathname)) {\n\t\t\tres.writeHead(403);\n\t\t\tres.end('Unauthorized redirect path');\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(302, {\n\t\t\tLocation: requestURL.toString(),\n\t\t});\n\n\t\tres.end();\n\t}\n","sourceCodeStart":421,"sourceCodeEnd":457,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/2a7de457074cbb4d4373fbd9a4e5bea292c9c764/apps/meteor/server/lib/saml/lib/SAML.ts#L421-L457","documentation":"Anti-open-redirect protection in the SAML SLO endpoint: after parsing, the `redirect` target's origin (scheme + host + port) must exactly equal the origin of the configured idpSLORedirectURL. On any mismatch the endpoint responds 403 'Unauthorized redirect origin'.","triggerScenarios":"?redirect=https://other-host.example.com/... while the provider's SLO URL points at chat.example.com; scheme mismatch (http vs https); port mismatch (example.com vs example.com:8443); subdomain mismatch (www.example.com vs example.com).","commonSituations":"Workspace served on a different domain than the configured SLO URL; domain migrations without updating SAML settings; attempts to send users to a status page or external portal after logout; test environments using a different host than the production config.","solutions":["Use a redirect URL whose scheme, host and port exactly match the configured idp_slo_redirect_url","Update the provider's idp_slo_redirect_url setting to the origin you actually want users to land on after logout","Do not attempt cross-domain post-logout redirects - this endpoint only ever returns to the configured origin"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Build the redirect target from the configured SLO URL so origins always match\nconst buildSloRedirect = (configuredSloUrl: string, path = '/'): string => {\n  const base = new URL(configuredSloUrl);\n  return new URL(path, base).toString();\n};","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep post-logout targets inside the origin of the configured idp_slo_redirect_url","Update SAML provider settings whenever the workspace domain changes","Cover origin equality with a unit test when configuring SLO flows"],"tags":["saml","open-redirect","security","logout","http-403"],"backgroundTag":"open-redirect-blocked","analyzedSha":"2a7de457074cbb4d4373fbd9a4e5bea292c9c764","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}