SEC2-043: Board fold links build hx-vals JSON by text interpolation rather than JSON encoding #985
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#985
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 on 2026-10-16 in a read-only security review of
postuloat56fc9d531, checked by a second, independent pass.Severity: Informational · Category: Injection · Reference: CWE-116 (Improper Encoding or Escaping of Output); OWASP A03:2021 (Injection)
Location:
src/postulo/templates/applications/partials/board.html:160-162src/postulo/applications/views.py:100-114, 322-350(where the value comes from)Issue
A JSON document is assembled by dropping an HTML-escaped string into a quoted JSON string. HTML escaping and JSON escaping differ (a backslash survives HTML escaping and breaks the JSON). Today the value cannot be hostile:
asked_statusesreads?status=from the address, butcolumns()keeps onlystatus in Status.valuesand writes",".join(after)from that filtered list, so only enum keys reach the attribute. The safety rests on that filter in a view, two files away from the template. The project already has the right tool,|as_json(templatetags/postulo.py:757-764), used for the map.Impact
None today. If a future column value were ever user-influenced, the attribute would silently produce invalid JSON (htmx logs and drops the request) or, with HTML autoescaping, remain confined to the attribute; no script path exists under the current CSP and htmx's
allowEval: false.Fix
Pass a dict and encode it: in
columns()add"vals": {"status": ",".join(after)}and writehx-vals="{{ column.vals|as_json }}".as_jsonis deliberately not marked safe, so autoescape entity-encodes the quotes and the browser decodes them before htmx parses, asdata-map-placesalready relies on. Done when: a template lint next toHTMX_SCRIPTINGintests/test_template_lint.pyrefuses anhx-vals=orhx-headers=value containing{{unless the only filter applied is|as_json, and the board template passes it.Related
#709 (closed) - the board accordion that introduced the attribute. #823 (closed) - htmx eval surfaces. Neither mentions the encoding.