T09 · Insecure Skill Coding Practices
- Location
- scripts/backfill_translations.py:62
- Finding
- Stored Cross-Site Scripting Through Unsafe NEWS_DATA Re-serialization## Vulnerability Details **File Location**: `scripts/backfill_translations.py`, lines 62–64 and 161–175 **Vulnerability Type**: Stored cross-site scripting caused by unsafe embedding of untrusted JSON in a JavaScript context **Risk Level**: High **Vulnerable code:** ```python def write_news_data(html: str, data: list[dict], m: re.Match) -> str: """Write NEWS_DATA back into the report.""" blob = json.dumps(data, ensure_ascii=False) return html[:m.start(1)] + blob + html[m.end(1):] ``` ```python new_html, n_cards = patch_signal_cards( write_news_data(html, data, m), data) if n_cards: print(f" 🏷️ Market signal cards updated: {n_cards}") if not gained and not n_cards: print(" ⏭️ No new translations; skipping write") continue if dry_run: print(f" 🏃 dry-run: would write {after['cn_summary']} summaries / " f"{after['cn_title']} titles / {n_cards} card annotations") else: p.write_text(new_html, encoding="utf-8") ``` ### Technical Analysis Reports store news records in a JavaScript variable named `NEWS_DATA`. These records may originate from automatically retrieved RSS feeds or externally supplied news JSON and therefore cross a third-party trust boundary. The main renderer accounts for this boundary by escaping `<`, `>`, and `&` before placing serialized JSON inside a script block. However, the translation backfill path parses the protected JSON with `json.loads()`, which converts sequences such as `\u003c` back into literal `<` characters. It then re-serializes the records using ordinary `json.dumps()` without script-context escaping. JSON string escaping alone does not protect an inline script block. HTML parsers recognize a literal `</script>` sequence even when it occurs inside a JavaScript string. Consequently, an attacker-controlled news field can terminate the containing script element and introduce a new executable element. The unsafe result i ...[truncated 1705 chars]
- Remediation
- ## Remediation Suggestions 1. Replace plain `json.dumps()` with the same script-safe serialization routine used by `scripts/aiweekly/render.py`. 2. At minimum, escape HTML-significant characters after JSON serialization: ```python def script_safe_json(value) -> str: return ( json.dumps(value, ensure_ascii=False) .replace("<", "\\u003c") .replace(">", "\\u003e") .replace("&", "\\u0026") ) ``` 3. Use this function in `write_news_data()` before inserting the JSON into the inline script block. 4. Centralize embedded-JSON serialization in one shared helper so the generator and backfill paths cannot diverge. 5. Add regression tests containing payloads such as: ```html </script><script>alert(1)</script> ``` Verify that no raw `</script` appears inside the serialized `NEWS_DATA` value after backfilling. 6. Run the existing report validation logic after every backfill and fail closed if an embedded JSON variable contains a raw script terminator or if dangerous link schemes are introduced. 7. Consider embedding the data in a non-executable `<script type="application/json">` element while retaining the same escaping protections, then parse it with `JSON.parse(textContent)` in the browser.
