From f620b884c6af3cd9a79b69fdcb7bf89c06195da9 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Fri, 8 May 2026 13:18:09 +0200 Subject: [PATCH] fix(web/security): tighten CSP and harden /go bootstrap error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop 'unsafe-eval' from script-src CSP — Next.js prod build doesn't need it; closes an XSS escalation vector for any future inline payload. - Cap error messages embedded in the generated OOBE PowerShell script at 200 chars to prevent a multi-MB upstream message from being inlined verbatim into a script that runs as SYSTEM during Windows OOBE. - Add X-Content-Type-Options: nosniff to the /go error response for consistency with the success response. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../autopilot-monitor-web/app/go/[code]/route.ts | 14 +++++++++++++- src/Web/autopilot-monitor-web/next.config.ts | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Web/autopilot-monitor-web/app/go/[code]/route.ts b/src/Web/autopilot-monitor-web/app/go/[code]/route.ts index c3fcdadb..08a5d51e 100644 --- a/src/Web/autopilot-monitor-web/app/go/[code]/route.ts +++ b/src/Web/autopilot-monitor-web/app/go/[code]/route.ts @@ -65,15 +65,27 @@ export async function GET( } } +// Hard cap on error messages embedded in the generated PS script. +// Defense against an upstream/backend bug or compromise emitting a multi-MB +// `message` that would otherwise be inlined verbatim into the script body. +const MAX_ERROR_MESSAGE_LENGTH = 200; + function errorScript(message: string): Response { + // Cap length BEFORE quote-escaping so the cap is on raw input, not the + // post-escape size (which can double for ' -heavy strings). + const truncated = + message.length > MAX_ERROR_MESSAGE_LENGTH + ? message.slice(0, MAX_ERROR_MESSAGE_LENGTH) + "..." + : message; // Escape single quotes for PowerShell string - const safeMsg = message.replace(/'/g, "''"); + const safeMsg = truncated.replace(/'/g, "''"); const script = `Write-Host ''; Write-Host 'ERROR: ${safeMsg}' -ForegroundColor Red; Write-Host ''`; return new Response(script, { status: 200, // Return 200 so irm | iex works; the Write-Host will show the error headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store, no-cache", + "X-Content-Type-Options": "nosniff", }, }); } diff --git a/src/Web/autopilot-monitor-web/next.config.ts b/src/Web/autopilot-monitor-web/next.config.ts index 94032458..ebe086d0 100644 --- a/src/Web/autopilot-monitor-web/next.config.ts +++ b/src/Web/autopilot-monitor-web/next.config.ts @@ -23,7 +23,7 @@ const nextConfig: NextConfig = { key: "Content-Security-Policy", value: [ "default-src 'self'", - "script-src 'self' 'unsafe-inline' 'unsafe-eval'", + "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob: https://*.tile.openstreetmap.org", "font-src 'self'",