{"record":{"id":"171ae98e95457093","repo":"RocketChat/Rocket.Chat","slug":"unauthorized-redirect-path","errorCode":null,"errorMessage":"Unauthorized redirect path","messagePattern":"Unauthorized redirect path","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"apps/meteor/server/lib/saml/lib/SAML.ts","lineNumber":446,"sourceCode":"\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\n\tprivate static async processAuthorizeAction(\n\t\treq: IIncomingMessage,\n\t\tres: ServerResponse,\n\t\tservice: IServiceProviderOptions,\n\t\tsamlObject: ISAMLAction,\n\t): Promise<void> {\n\t\tconst serviceProvider = new SAMLServiceProvider(service);","sourceCodeStart":428,"sourceCodeEnd":464,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/2a7de457074cbb4d4373fbd9a4e5bea292c9c764/apps/meteor/server/lib/saml/lib/SAML.ts#L428-L464","documentation":"Second anti-open-redirect check in the SAML SLO endpoint: once origins match, the redirect target's path must also equal the configured SLO path (trailing slashes normalized via replace(/\\/+$/, '') || '/'). A different path on the same host gets 403 'Unauthorized redirect path'.","triggerScenarios":"?redirect=https://chat.example.com/some/other/path when idp_slo_redirect_url is https://chat.example.com/slo; extra path segments; a deep link to /home or /login while the setting points at the IdP's SLO path.","commonSituations":"Wanting to land users on the home screen after logout while the configured SLO path is different; the setting changed to a new path but clients still redirect to old locations; copy-pasted redirect targets differing by one segment.","solutions":["Use exactly the configured path (trailing slashes are ignored) - derive the redirect from the configured URL","Change the provider's idp_slo_redirect_url to the path you want; it is the only redirect target this endpoint accepts","Note the comparison uses pathname only: query strings may differ, paths may not"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Reuse the configured SLO URL verbatim; vary only the query string, never the path\nconst sloTarget = (configured: string, query?: Record<string, string>): string => {\n  const u = new URL(configured);\n  if (query) for (const [k, v] of Object.entries(query)) u.searchParams.set(k, v);\n  return u.toString();\n};","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat idp_slo_redirect_url as the single source of truth for the post-logout location","When changing the configured path, update every client that builds redirect targets","Remember the endpoint compares pathname only - align paths exactly"],"tags":["saml","open-redirect","security","logout","http-403"],"backgroundTag":"redirect-path-mismatch","analyzedSha":"2a7de457074cbb4d4373fbd9a4e5bea292c9c764","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}