T09 · Insecure Skill Coding Practices
- Location
bid-quote-cockpit/scripts/build_dashboard.py:1380- Finding
Stored JavaScript Injection Through Unsafe JSON Embedding in Generated Dashboards
- Content
View full analysis
Vulnerability Details
File Location:
bid-quote-cockpit/scripts/build_dashboard.py, lines 1380–1392
Vulnerability Type: Stored script injection in generated HTML
Risk Level: HighVulnerable Code
python data_js = "var DATA=" + json.dumps( {k: model[k] for k in ("meta", "cats", "vendors", "stats", "catMeans", "hist", "charts", "staticCharts", "detailCharts", "modules")}, ensure_ascii=False) + ";" html = ( "<!DOCTYPE html><html lang='zh-CN'><head><meta charset='utf-8'>" "<meta name='viewport' content='width=device-width,initial-scale=1'>" "<title>%s</title><style>%s</style>%s</head><body>%s" "<script>%s</script><script>%s</script><script>%s</script>%s" "</body></html>" % (esc(model["meta"]["project"]), CSS, panel_css, body, echarts, data_js, JS, panel_dom + panel_js) )Technical Analysis
The generator serializes the complete dashboard model using
json.dumps()and inserts the resulting text directly into an executable HTML<script>element:html <script>var DATA=...;</script>JSON string escaping does not protect the surrounding HTML parser context. In particular, Python's
json.dumps()preserves a literal</script>sequence inside string values. HTML parsers terminate a script element when they encounter that sequence, even when it appears inside what JavaScript would otherwise treat as a quoted string.The embedded model contains text originating from
config.json,quotes.json, and optional history data. The audited model construction preserves fields such as supplier names, item descriptions, notes, source names, category labels, project metadata, and other quote content. Supplier quotation material can therefore cross from externally supplied business data into an executable browser context.For example, a text field containing the following value would break out of the generated data script:
html </script><script ...[truncated 2304 chars]- Remediation
View remediation
Remediation Suggestions
- Apply script-context-safe JSON serialization. At minimum, neutralize characters that can affect HTML parsing after serialization:
python def json_for_script(value): return ( json.dumps(value, ensure_ascii=False) .replace("&", "\\u0026") .replace("<", "\\u003c") .replace(">", "\\u003e") .replace("\u2028", "\\u2028") .replace("\u2029", "\\u2029") ) data_js = "var DATA=" + json_for_script(model_subset) + ";"Escaping
<prevents any input from producing a literal</script>sequence.- Prefer a non-executable JSON container. Store serialized data in an element such as:
html <script id="dashboard-data" type="application/json">...</script>Then load it using:
javascript const DATA = JSON.parse(document.getElementById("dashboard-data").textContent);The serialized content must still neutralize literal
<characters because the HTML parser recognizes script end tags regardless of the script type.-
Keep HTML and JavaScript escaping separate. Continue using HTML escaping for visible DOM content, but introduce a dedicated serializer for values embedded into JavaScript or JSON script blocks.
-
Add regression tests for every externally sourced text field. Tests should generate and open dashboards containing values such as:
text </script><script>window.__INJECTION_TEST__=true</script>The resulting HTML must contain no literal attacker-provided
</script>sequence inside the data block, andwindow.__INJECTION_TEST__must remain undefined after browser rendering.- Add a restrictive Content Security Policy as defense in depth. Remove inline scripts where practical and use script hashes or nonces. Restrict
connect-srcto required destinations so that an injection flaw cannot freely exfiltrate dashboard data. CSP is supplementary and must not replace correct serialization.
