T09 · Insecure Skill Coding Practices
- Location
src/core/render.ts:6- Finding
Arbitrary JavaScript Execution Through Unsafe Chart Option Embedding
- Content
View full analysis
Vulnerability Details
File Location:
src/core/render.ts:6-28
Vulnerability Type: Stored HTML and JavaScript injection
Risk Level: HighVulnerable Code
ts export function renderHtml(option: ChartOption, width: number, height: number): string { const optionJson = JSON.stringify(option, null, 2); return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ECharts Preview</title> <script src="${CDN_URL}"></script> <style> html, body { margin: 0; padding: 0; background: #f3f5f7; font-family: "Segoe UI", sans-serif; } .wrap { padding: 24px; } #chart { width: ${width}px; height: ${height}px; margin: 0 auto; background: #fff; border-radius: 16px; box-shadow: 0 12px 40px rgba(15, 23, 42, 0.10); } </style> </head> <body> <div class="wrap"> <div id="chart"></div> </div> <script> const chart = echarts.init(document.getElementById("chart")); const option = ${optionJson}; chart.setOption(option); </script> </body> </html>`; }The same vulnerable implementation is present in the distributed executable file at
dist/core/render.js:2-25.Technical Analysis
JSON.stringify()produces valid JSON but does not make the result safe for direct insertion into an HTML script element. In particular, an attacker-controlled string can contain the HTML parser terminator</script>.Although such a sequence remains part of a JavaScript string from the JavaScript parser's perspective, the HTML parser processes the script element first and terminates it when it encounters
</script>. The remaining attacker-controlled content can then introduce a new script element.For example, an option property containing the following value can break out of the generated inline script:
json { "t ...[truncated 1970 chars]- Remediation
View remediation
Remediation Suggestions
-
Escape serialized JSON for safe placement inside an HTML script element. At minimum, encode HTML-significant characters and JavaScript line separators:
ts function serializeForInlineScript(value: unknown): string { return JSON.stringify(value, null, 2) .replace(/&/g, "\\u0026") .replace(/</g, "\\u003c") .replace(/>/g, "\\u003e") .replace(/\u2028/g, "\\u2028") .replace(/\u2029/g, "\\u2029"); }Then replace:
ts const optionJson = JSON.stringify(option, null, 2);with:
ts const optionJson = serializeForInlineScript(option); -
Prefer separating data from executable code. Place safely encoded JSON in a non-executable element and parse it from a fixed script:
html <script id="chart-option" type="application/json">SAFE_JSON</script>Ensure
<,&, and the script terminator cannot appear literally in that element before reading it withtextContentand callingJSON.parse(). -
Add runtime schema validation for chart options or accept a restricted option schema rather than trusting arbitrary parsed JSON.
-
Add regression tests with payloads containing:
text </script><script>alert(1)</script>Verify that the generated HTML contains no literal attacker-controlled closing script tag.
-
Rebuild and commit
dist/core/render.jsafter correcting the TypeScript source because users execute the distributed JavaScript implementation. -
Apply a restrictive Content Security Policy to hosted previews as defense in depth. Avoid allowing arbitrary inline scripts where possible.
-
