From a100702d198a843c4338cddc43aadde562ece693 Mon Sep 17 00:00:00 2001 From: bourgeoa Date: Fri, 21 Aug 2026 16:01:48 +0200 Subject: [PATCH] fix(http): don't redirect query-string URLs on private containers PivotResponseWriter intercepted 401/403/404 GET responses and redirected to a trailing-slash variant of the URL, but it checked the raw request URL (which includes the query string) for the trailing slash. As a result, OIDC redirects back to a private container (e.g. /?code=...&state=...&iss=...) were 301-redirected to a query-less '//' URL, dropping the authorization code and breaking login on non-public-readable containers. Only canonicalize when the identifier path genuinely lacks a trailing slash, so private containers return their normal 401 with the query string intact and the login flow can complete. --- src/http/output/PivotResponseWriter.ts | 39 +++++++++++++++----------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/http/output/PivotResponseWriter.ts b/src/http/output/PivotResponseWriter.ts index 4732ebd..8e62e47 100644 --- a/src/http/output/PivotResponseWriter.ts +++ b/src/http/output/PivotResponseWriter.ts @@ -30,23 +30,30 @@ export class PivotResponseWriter extends BasicResponseWriter { if ( (input.response.req.method === 'GET') && (typeof input.response.req.url === 'string') && - ([401, 403, 404].indexOf(input.result.statusCode) !== -1) && - (hasTrailingSlash(input.response.req.url) === false)) { + ([401, 403, 404].indexOf(input.result.statusCode) !== -1) + ) { const target = await this.targetExtractor.handleSafe({ request: input.response.req as HttpRequest }); - const withSlash = addTrailingSlash(target.path); - let exists = false; - try { - exists = await this.store.hasResource({ path: withSlash }); - } catch (e) { - // leave as false - } - // console.log('exists', withSlash, exists); - if (exists) { - // console.log('rewriting', input.response.req.method, input.response.req.url, input.result.statusCode); - input.response.statusCode = 301; - input.response.setHeader('Location', withSlash); - input.response.end('Try adding a slash at the end of the URL.\n'); - return; + // Only canonicalize when the identifier path genuinely lacks a trailing slash. + // The raw request URL includes the query string, so checking it would treat + // OIDC redirects (e.g. /?code=...&state=...) as missing a slash and redirect + // to a query-less "//" URL, dropping the authorization code and breaking + // login on private containers. + if (!target.path.endsWith('/')) { + const withSlash = addTrailingSlash(target.path); + let exists = false; + try { + exists = await this.store.hasResource({ path: withSlash }); + } catch (e) { + // leave as false + } + // console.log('exists', withSlash, exists); + if (exists) { + // console.log('rewriting', input.response.req.method, input.response.req.url, input.result.statusCode); + input.response.statusCode = 301; + input.response.setHeader('Location', withSlash); + input.response.end('Try adding a slash at the end of the URL.\n'); + return; + } } } } catch (e) {