SEC2-043: Board fold links build hx-vals JSON by text interpolation rather than JSON encoding #985

Closed
opened 2026-10-17 10:39:18 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-10-16 in a read-only security review of postulo at 56fc9d531, 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-162
  • src/postulo/applications/views.py:100-114, 322-350 (where the value comes from)

Issue

hx-vals='{"status": "{{ column.statuses }}"}'

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_statuses reads ?status= from the address, but columns() keeps only status in Status.values and 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 write hx-vals="{{ column.vals|as_json }}". as_json is deliberately not marked safe, so autoescape entity-encodes the quotes and the browser decodes them before htmx parses, as data-map-places already relies on. Done when: a template lint next to HTMX_SCRIPTING in tests/test_template_lint.py refuses an hx-vals= or hx-headers= value containing {{ unless the only filter applied is |as_json, and the board template passes it.

#709 (closed) - the board accordion that introduced the attribute. #823 (closed) - htmx eval surfaces. Neither mentions the encoding.

Found on 2026-10-16 in a read-only security review of `postulo` at `56fc9d531`, 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-162` - `src/postulo/applications/views.py:100-114, 322-350` (where the value comes from) ## Issue ```django hx-vals='{"status": "{{ column.statuses }}"}' ``` 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_statuses` reads `?status=` from the address, but `columns()` keeps only `status in Status.values` and 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 write `hx-vals="{{ column.vals|as_json }}"`. `as_json` is deliberately not marked safe, so autoescape entity-encodes the quotes and the browser decodes them before htmx parses, as `data-map-places` already relies on. Done when: a template lint next to `HTMX_SCRIPTING` in `tests/test_template_lint.py` refuses an `hx-vals=` or `hx-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.
tiagoagueda added this to the 1.0.0 milestone 2026-10-17 10:39:20 +00:00
Sign in to join this conversation.
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
Postulo/postulo#985
No description provided.