T09 · Insecure Skill Coding Practices
Note
- Location
- scripts/build-report.mjs:65
- Finding
- Stored Script Injection Through Unsafe Report Payload Embedding<![CDATA[ ## Vulnerability Details **File Location**: `scripts/build-report.mjs:65-70` and `assets/report-template.html:254-258` **Vulnerability Type**: Stored script injection in a generated HTML report **Risk Level**: Moderate ### Vulnerable Code From `scripts/build-report.mjs:65-70`: ```js const ANCHOR = /(const DATA = )\/\*SLOP_DATA\*\/[\s\S]*?\/\*END_SLOP_DATA\*\//; if (!ANCHOR.test(template)) { console.error('build-report: could not find the `const DATA = /*SLOP_DATA*/ ... /*END_SLOP_DATA*/` block in the template'); process.exit(1); } const out = template.replace(ANCHOR, '$1/*SLOP_DATA*/' + JSON.stringify(payload, null, 2) + '/*END_SLOP_DATA*/'); ``` The payload is inserted into the executable script context established in `assets/report-template.html:254-258`: ```html <script> // Default is a PLACEHOLDER sentinel, never a plausible-looking example. If injection // fails for any reason, the page must shout "not built" instead of rendering a fake // report — that exact silent-default failure shipped a blank report once. const DATA = /*SLOP_DATA*/{ "__notBuilt__": true }/*END_SLOP_DATA*/; ``` ### Technical Analysis The report builder uses `JSON.stringify()` and inserts the resulting text directly into an inline `<script>` element. JSON serialization protects JavaScript string syntax, but it does not protect the surrounding HTML parser context. In particular, it does not neutralize the case-insensitive `</script>` sequence. The HTML parser recognizes `</script>` even when it appears inside a JavaScript string literal. Therefore, a payload field containing content such as: ```html </script><script>/* attacker-controlled JavaScript */</script> ``` can terminate the legitimate report script and create a new executable script element. This condition is reachable because the report schema includes repository-derived values such as project names, file paths, finding descriptions, and fix-it prompts. A malicious repository can influence these valu ...[truncated 2292 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Encode serialized JSON for HTML script context.** At minimum, replace HTML-significant characters after serialization: ```js const serializedPayload = JSON.stringify(payload, null, 2) .replace(/&/g, '\\u0026') .replace(/</g, '\\u003c') .replace(/>/g, '\\u003e') .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); const out = template.replace( ANCHOR, '$1/*SLOP_DATA*/' + serializedPayload + '/*END_SLOP_DATA*/' ); ``` Escaping `<` prevents an attacker from creating the `</script>` delimiter. 2. **Prefer a non-executable JSON container.** Place the serialized payload in an element such as: ```html <script id="slop-data" type="application/json">...</script> ``` Then parse it using: ```js const DATA = JSON.parse(document.getElementById('slop-data').textContent); ``` The payload must still have `<` escaped because the HTML parser recognizes `</script>` regardless of the script element's type. 3. **Add regression tests.** Build reports containing adversarial values such as: ```text </script><script>globalThis.reportInjectionExecuted = true</script> ``` Verify that: - The generated file contains no literal attacker-controlled `</script>` sequence inside the data block. - The payload remains valid after browser-side parsing. - No injected script executes when the report is opened. 4. **Apply a restrictive Content Security Policy.** Remove inline event handlers and inline executable scripts where practical, then use a policy that disallows unauthorized script execution. A CSP provides defense in depth but should not replace correct serialization. 5. **Treat all repository-derived report fields as untrusted.** Validate types and reasonable size limits for project names, paths, descriptions, category fields, and prompts. Continue using text-safe DOM APIs such as `textContent` wherever HTML markup is unnecessary. ]]>
