PST-094: htmx script-tag and eval surfaces are left at their permissive defaults #823
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#823
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found in the security audit of 2026-10-10 (
SECURITY-AUDIT-2026-10-10.md), run againstmainatc30851bfrom 8 to 10 October 2026, as PST-094. Severity: Informational.The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it.
src/postulo/templates/base.html(27-28)Who can do it, and what they need. Anyone who finds an HTML-injection point in a fragment htmx swaps in (none was found in this audit), on an instance whose operator has relaxed the policy, or on a route whose response is served without it (for example a fragment proxied by nginx that strips headers, or a future
csp_override). …How it works.
allowEval: trueandallowScriptTags: true; the page's meta config sets onlyincludeIndicatorStyles,historyCacheSizeandrefreshOnHistoryMiss.allowEval, htmx evaluateshx-on:*attribute bodies,hx-vals="js:...",hx-headers="js:...",hx-trigger="click[cond]"filters andhx-request="js:..."throughnew Function; withallowScriptTags,<script src="/same-origin/...">elements inside swapped markup are re-created and executed.script-src 'self'with no'unsafe-eval'makes every one of those throw, and nh3's attribute allowlist (a[href],ol[start]only) and Django autoescaping keep hx-* attributes out of the DOM, so there is no live path.Impact. No impact on a correctly deployed instance. If the policy is ever relaxed (operators sometimes add 'unsafe-eval' for a plugin) or an injection appears in a fragment, the blast radius jumps from markup to script execution in the person's session.
Verification (verdict: confirmed; severity after verification: Informational). File and line are correct for the meta tag (src/postulo/templates/base.html:27-28) and the vendored defaults (src/postulo/static/js/vendor/htmx.min.js). Reachability needs two things at once: an HTML-injection point in a swapped fragment (none found) and either a relaxed CSP or, for script tags, a same-origin attacker-controlled JS file. A fragment response needs no CSP of its own: the loading document's policy governs it. Remediation: add
"allowEval":false,"allowScriptTags":falseto the htmx-config meta, and consider"allowNestedOobSwaps":false. Check first that no feature depends on swapped-in scripts. Add a test that pins the meta, so the config cannot drift.Suggested remediation. Add
"allowEval": false, "allowScriptTags": falseto the htmx-config meta; nothing in the templates useshx-on,js:prefixes or trigger filters, so this costs nothing and makes htmx refuse those attributes even where the CSP is weakened. Extend tests/test_template_lint.py to fail onhx-on,js:/javascript:prefixes and bracketedhx-triggerconditions, so the setting stays true.Fixed in
3bbb52734on main.hx-valsJSON by text interpolation rather than JSON encoding #985