{"record":{"id":"6cc9e9b1e489e1ec","repo":"ramensoftware/windhawk","slug":"the-webview-template-has-no-head-tag-to-inject-the-content","errorCode":null,"errorMessage":"The webview template has no <head> tag to inject the Content-Security-Policy into","messagePattern":"The webview template has no <head> tag to inject the Content-Security-Policy into","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/windhawk-vscode/src/extension.ts","lineNumber":2184,"sourceCode":"\tconst nonce = crypto.randomBytes(16).toString('hex');\n\n\tconst cspRulesWithNonce = cspRules.map(rule =>\n\t\trule.startsWith('script-src ') ? rule + ` 'nonce-${nonce}'` : rule\n\t);\n\n\tconst webviewPathOnDisk = baseDebugReactUiPath\n\t\t? vscode.Uri.file(baseDebugReactUiPath)\n\t\t: vscode.Uri.joinPath(extensionUri, 'webview');\n\n\tconst baseWebviewUri = webview.asWebviewUri(webviewPathOnDisk);\n\tlet html = fs.readFileSync(vscode.Uri.joinPath(webviewPathOnDisk, 'index.html').fsPath, 'utf8');\n\n\t// The base href, the CSP and the body markers are injected by patching the\n\t// bundled template. A template change which drops either anchor tag has to\n\t// fail here, not silently produce a webview without a CSP.\n\tconst headTag = '<head>';\n\tif (!html.includes(headTag)) {\n\t\tthrow new Error('The webview template has no <head> tag to inject the Content-Security-Policy into');\n\t}\n\n\t// The replacements go through replacer functions, not replacement strings,\n\t// so that a `$` sequence in an injected value is inserted verbatim instead\n\t// of being expanded as a replacement pattern.\n\thtml = html.replace(headTag, () => `<head>\n\t\t<base href=\"${baseWebviewUri.toString()}/\">\n\t\t<meta http-equiv=\"Content-Security-Policy\" content=\"${cspRulesWithNonce.join('; ')};\">\n\t\t<script nonce=\"${nonce}\">(() => {\n\t\t\tlet lastFocused = null;\n\t\t\tdocument.addEventListener('focusin', (e) => { lastFocused = e.target; });\n\t\t\tdocument.addEventListener('focusout', () => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tif (document.hasFocus() && (!document.activeElement || document.activeElement === document.body)) {\n\t\t\t\t\t\tlastFocused = null;\n\t\t\t\t\t}\n\t\t\t\t}, 0);\n\t\t\t});","sourceCodeStart":2166,"sourceCodeEnd":2202,"githubUrl":"https://github.com/ramensoftware/windhawk/blob/61d99ed8e182e1af1b60109612b6763ad1b4b74e/src/windhawk-vscode/src/extension.ts#L2166-L2202","documentation":"The webview's HTML template must contain a literal <head> tag because the extension injects the base href and a strict Content-Security-Policy by patching that anchor. If the bundled template no longer includes '<head>', the extension throws instead of silently producing a webview without a CSP (which would be a security regression).","triggerScenarios":"The bundled webview template file changed shape — a build/minification step emits <head ...attrs> or uppercase <HEAD>, a framework rewrites the template, or the template was hand-edited and the '<head>' anchor was removed or renamed.","commonSituations":"Upgrading a bundler/minifier that lowercases-transforms or expands the head tag (e.g. <head profile=\"\">); switching the template to a different framework boilerplate; a manual template edit dropping or renaming the head element; string minification altering the literal.","solutions":["Inspect the bundled webview template and restore a literal '<head>' opening tag.","If the build step rewrites the tag, fix the bundler/minifier config to preserve the plain <head> literal.","Update the injection code to match the new template anchor if the template intentionally changed (e.g. match /<head[^>]*>/i).","Rebuild the template bundle after fixing and confirm the CSP is injected into the served webview HTML."],"exampleFix":"// before (template)\n<head profile=\"\">\n// after\n<head>\n// or, code-side: match the actual tag\n// const headTag = html.match(/<head[^>]*>/i)?.[0];","handlingStrategy":"validation","validationCode":"// before patching/injecting\nconst html = fs.readFileSync(templatePath, 'utf8');\nif (!html.includes('<head>')) {\n  throw new Error(`${templatePath}: '<head>' anchor missing; fix template or bundler`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const html = buildWebviewHtml(templatePath, nonce);\n} catch (e) {\n  if (e.message.includes('no <head> tag')) {\n    reportTemplateAnchorMissing(templatePath); // fail the build/dev loop loudly\n  } else { throw e; }\n}","preventionTips":["Add a unit test asserting the bundled template contains the literal '<head>' anchor.","Lock bundler/minifier settings that rewrite HTML tag shapes (attributes, casing) on the template.","Treat the webview template as a contract: document the required anchors (<head>, body markers).","Run the dev build once after any tooling upgrade to confirm CSP injection still succeeds."],"tags":["csp","webview","template","security","internal-invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"61d99ed8e182e1af1b60109612b6763ad1b4b74e","analyzedAt":"2026-09-12T14:02:41.115Z","contentChangedAt":"2026-09-12T14:02:41.115Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}