diff --git a/frontend/package.json b/frontend/package.json index 450060e0d..5bb847297 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,8 @@ "start": "next start", "lint": "eslint", "test": "vitest run", - "coverage": "vitest run src/components/project-trace-readiness.test.ts --coverage --coverage.provider=v8 --coverage.reporter=json-summary --coverage.reporter=json --coverage.include=src/components/project-trace-readiness.ts", + "coverage": "vitest run --coverage --coverage.provider=v8 --coverage.reporter=json-summary --coverage.reporter=json", + "coverage:trace-readiness": "vitest run src/components/project-trace-readiness.test.ts --coverage --coverage.provider=v8 --coverage.reporter=json-summary --coverage.reporter=json --coverage.include=src/components/project-trace-readiness.ts", "typecheck": "tsc --noEmit", "full:smoke": "node scripts/full-product-ui-smoke.mjs", "pilot:smoke": "node scripts/pilot-ui-smoke.mjs", diff --git a/frontend/src/app/api/[...path]/route.ts b/frontend/src/app/api/[...path]/route.ts index 7f0e1d685..551532a51 100644 --- a/frontend/src/app/api/[...path]/route.ts +++ b/frontend/src/app/api/[...path]/route.ts @@ -278,9 +278,8 @@ async function proxyApiRequest( let response: Response; try { - // `target` is rebuilt by trustedBackendOrigin() from operator-only runtime - // configuration, then constrained to the validated API path/query above. - // codeql[js/request-forgery] + // `target` is rebuilt from URI-encoded authority/path components and the + // allow-listed query parameters above. response = await fetch(target, init); } catch (error) { // If the backend isn't available (e.g. during build), return a 503 instead of throwing diff --git a/frontend/src/lib/backend-url.ts b/frontend/src/lib/backend-url.ts index 81ffa39e6..01434a8c0 100644 --- a/frontend/src/lib/backend-url.ts +++ b/frontend/src/lib/backend-url.ts @@ -93,9 +93,10 @@ export function trustedBackendOrigin(): URL { if (configured.protocol !== "https:") { throw new Error("Backend requests require HTTPS"); } + const escapedHostname = encodeURIComponent(hostname); const encodedHostname = hostname.includes(":") - ? `[${hostname}]` - : encodeURIComponent(hostname); + ? `[${escapedHostname.replaceAll("%3A", ":")}]` + : escapedHostname; const encodedPort = configured.port ? `:${encodeURIComponent(configured.port)}` : "";