From 1df2cc5f024650d3cc1ff9db80a7715c69e3429f Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 20 Jun 2026 05:56:36 +0200 Subject: [PATCH] fix(qa): preserve adjacent control ui redaction --- extensions/qa-lab/src/lab-server.test.ts | 13 ++++++++++++ extensions/qa-lab/src/lab-server.ts | 26 +++++++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/extensions/qa-lab/src/lab-server.test.ts b/extensions/qa-lab/src/lab-server.test.ts index 16db2d8fe4e..bb0ae92c2de 100644 --- a/extensions/qa-lab/src/lab-server.test.ts +++ b/extensions/qa-lab/src/lab-server.test.ts @@ -425,6 +425,19 @@ describe("qa-lab server", () => { }; expect(startupStatus.status.gateway.url).toBe("https://gateway.example.test/?panel=chat"); + lab.setControlUi({ + controlUiUrl: + "/control-ui/?token=late-token&api_key=late-api-key&id_token=late-id-token&panel=chat#token=fragment-token", + }); + const relativeBootstrap = (await ( + await fetchWithRetry(`${lab.baseUrl}/api/bootstrap`) + ).json()) as { + controlUiUrl: string | null; + controlUiEmbeddedUrl: string | null; + }; + expect(relativeBootstrap.controlUiUrl).toBe("/control-ui/?panel=chat"); + expect(relativeBootstrap.controlUiEmbeddedUrl).toBe("/control-ui/?panel=chat"); + const messageResponse = await fetch(`${lab.baseUrl}/api/inbound/message`, { method: "POST", headers: { diff --git a/extensions/qa-lab/src/lab-server.ts b/extensions/qa-lab/src/lab-server.ts index 6324ac81f11..4449acca5a2 100644 --- a/extensions/qa-lab/src/lab-server.ts +++ b/extensions/qa-lab/src/lab-server.ts @@ -148,6 +148,24 @@ const CONTROL_UI_CREDENTIAL_QUERY_KEYS = new Set([ "refresh_token", "token", ]); +const CONTROL_UI_CREDENTIAL_QUERY_PATTERN = + /([?&])(?:access_token|api_?key|auth|deviceToken|id_token|password|refresh_token|token)=[^&#\s]*&?/gi; + +function stripSensitiveQueryParamsFromText(rawUrl: string): string { + let sanitized = rawUrl; + for (;;) { + const next = sanitized + .replace(CONTROL_UI_CREDENTIAL_QUERY_PATTERN, (match: string, separator: string) => + match.endsWith("&") ? separator : "", + ) + .replace(/[?&]$/, "") + .replace("?&", "?"); + if (next === sanitized) { + return next; + } + sanitized = next; + } +} function stripSensitiveQueryParams(rawUrl: string): string { try { @@ -159,13 +177,7 @@ function stripSensitiveQueryParams(rawUrl: string): string { } return url.toString(); } catch { - return rawUrl - .replace( - /([?&])(?:access_token|api_?key|auth|deviceToken|id_token|password|refresh_token|token)=[^&#\s]*&?/gi, - (match: string, separator: string) => (match.endsWith("&") ? separator : ""), - ) - .replace(/[?&]$/, "") - .replace("?&", "?"); + return stripSensitiveQueryParamsFromText(rawUrl); } }