Remove unsafe-inline from CSP script-src by externalising template JS #48

Closed
opened 2026-06-15 04:22:07 +00:00 by coding-agent-marvin8 · 1 comment
coding-agent-marvin8 commented 2026-06-15 04:22:07 +00:00 (Migrated from codeberg.org)

All UI JavaScript in FenLiu lives in inline <script> blocks across 6 Jinja2 templates. This forces 'unsafe-inline' in Content-Security-Policy: script-src, which negates most XSS protection: an attacker who can inject arbitrary HTML can also run arbitrary inline JavaScript.

The fix is to move each inline script block to a dedicated external file under src/fenliu/static/assets/ and remove 'unsafe-inline' from script-src.

Affected templates (6): dashboard.html, hashtag_streams.html, queue_preview.html, settings.html, topical_tags.html, stats.html

Unaffected (no inline scripts): review.html, login.html, change_password.html, post_details.html, setup.html, partials/header.html

All UI JavaScript in FenLiu lives in inline `<script>` blocks across 6 Jinja2 templates. This forces `'unsafe-inline'` in `Content-Security-Policy: script-src`, which negates most XSS protection: an attacker who can inject arbitrary HTML can also run arbitrary inline JavaScript. The fix is to move each inline script block to a dedicated external file under `src/fenliu/static/assets/` and remove `'unsafe-inline'` from `script-src`. Affected templates (6): `dashboard.html`, `hashtag_streams.html`, `queue_preview.html`, `settings.html`, `topical_tags.html`, `stats.html` Unaffected (no inline scripts): `review.html`, `login.html`, `change_password.html`, `post_details.html`, `setup.html`, `partials/header.html`
coding-agent-marvin8 commented 2026-06-15 04:22:27 +00:00 (Migrated from codeberg.org)

Exploration findings

5 templates use {{ api_key }} directly inside the <script> block. Moving to an external file requires an alternative injection mechanism. Solution: <meta name="fenliu-api-key" content="{{ api_key }}"> in the <head>; external JS reads document.querySelector('meta[name="fenliu-api-key"]')?.content. Meta tags are data, not executable code — CSP script-src does not apply.

stats.html injects 4 chart-data arrays via | tojson directly into Chart.js constructor calls. Solution: a <script type="application/json" id="chart-data"> JSON island. type="application/json" marks the block as non-executable data; CSP ignores it. External JS parses it with JSON.parse(document.getElementById('chart-data').textContent).

Inline onclick attributes in several templates reference template variables (e.g. onclick="fetchStream({{ stream.id }}, this)"). These are also blocked by CSP without unsafe-inline. Solution: data-* attributes + event delegation in external JS.

Dynamically-generated HTML strings in settings.html and queue_preview.html build rows with onclick attributes in JS template literals. These must also switch to data-* attributes + event delegation.

Chart.js is loaded from cdn.jsdelivr.net in stats.html but that host is absent from the current script-src. It must be added at the same time as 'unsafe-inline' is dropped.

Plan of attack

  1. Write failing test asserting 'unsafe-inline' is absent from script-src (TDD RED)
  2. Extract each inline <script> block to static/assets/<page>.js, applying the three strategies above
  3. Update each template: add meta tag / JSON island, remove inline <script>, add <script src="/static/assets/<page>.js" defer>, convert inline event handlers to data-* attributes
  4. Update middleware.py: drop 'unsafe-inline' from script-src, add https://cdn.jsdelivr.net
  5. Confirm tests go GREEN; run full DoD checks
  6. Mark TODO item 2 complete; commit, push, open PR
## Exploration findings **5 templates use `{{ api_key }}` directly inside the `<script>` block.** Moving to an external file requires an alternative injection mechanism. Solution: `<meta name="fenliu-api-key" content="{{ api_key }}">` in the `<head>`; external JS reads `document.querySelector('meta[name="fenliu-api-key"]')?.content`. Meta tags are data, not executable code — CSP `script-src` does not apply. **`stats.html` injects 4 chart-data arrays via `| tojson` directly into Chart.js constructor calls.** Solution: a `<script type="application/json" id="chart-data">` JSON island. `type="application/json"` marks the block as non-executable data; CSP ignores it. External JS parses it with `JSON.parse(document.getElementById('chart-data').textContent)`. **Inline `onclick` attributes** in several templates reference template variables (e.g. `onclick="fetchStream({{ stream.id }}, this)"`). These are also blocked by CSP without `unsafe-inline`. Solution: `data-*` attributes + event delegation in external JS. **Dynamically-generated HTML strings** in `settings.html` and `queue_preview.html` build rows with `onclick` attributes in JS template literals. These must also switch to `data-*` attributes + event delegation. **Chart.js is loaded from `cdn.jsdelivr.net` in `stats.html`** but that host is absent from the current `script-src`. It must be added at the same time as `'unsafe-inline'` is dropped. ## Plan of attack 1. Write failing test asserting `'unsafe-inline'` is absent from `script-src` (TDD RED) 2. Extract each inline `<script>` block to `static/assets/<page>.js`, applying the three strategies above 3. Update each template: add meta tag / JSON island, remove inline `<script>`, add `<script src="/static/assets/<page>.js" defer>`, convert inline event handlers to `data-*` attributes 4. Update `middleware.py`: drop `'unsafe-inline'` from `script-src`, add `https://cdn.jsdelivr.net` 5. Confirm tests go GREEN; run full DoD checks 6. Mark TODO item 2 complete; commit, push, open PR
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marvin8/dujiangyan#48
No description provided.