PST-076: A template helper builds markup with an f-string and interpolates the picture address unescaped #805

Closed
opened 2026-10-10 10:56:46 +00:00 by tiagoagueda · 1 comment
Owner

Found in the security audit of 2026-10-10 (SECURITY-AUDIT-2026-10-10.md), run against main at c30851b from 8 to 10 October 2026, as PST-076. 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.


  • Severity: Informational
  • Category: html-building / defence-in-depth
  • Location: src/postulo/core/templatetags/postulo.py (253-256)
  • Found by: XSS and CSP

Who can do it, and what they need. None today: every caller passes reverse(...) plus an integer stamp (profile.picture_version, int(logo_fetched_at.timestamp()), or a bare reverse for a plugin logo).

How it works.

A future caller passing a stored or remote picture address (the model has company.logo_source_url, a CharField of up to 500 characters) would land unescaped inside src="..." under mark_safe, giving attribute injection.

Impact. None now; it is the one mark_safe in the tag library whose interpolated value is not escaped.

Verification (verdict: confirmed; severity after verification: Informational). No change to the attack path (there is none). Remediation: escape(picture) (or format_html) in _tile, so the one mark_safe in the tag library that takes an unescaped value stops being a trap for a future caller.

Suggested remediation. Use format_html for the tile (format_html('<span class="avatar {} {}"><img src="{}" alt=""></span>', box, shape, picture)), which also escapes a future caller's mistake.

Found in the security audit of 2026-10-10 (`SECURITY-AUDIT-2026-10-10.md`), run against `main` at `c30851b` from 8 to 10 October 2026, as **PST-076**. 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. --- - **Severity:** Informational - **Category:** html-building / defence-in-depth - **Location:** `src/postulo/core/templatetags/postulo.py` (253-256) - **Found by:** XSS and CSP **Who can do it, and what they need.** None today: every caller passes `reverse(...)` plus an integer stamp (profile.picture_version, int(logo_fetched_at.timestamp()), or a bare reverse for a plugin logo). **How it works.** A future caller passing a stored or remote picture address (the model has company.logo_source_url, a CharField of up to 500 characters) would land unescaped inside src="..." under mark_safe, giving attribute injection. **Impact.** None now; it is the one mark_safe in the tag library whose interpolated value is not escaped. **Verification** (verdict: confirmed; severity after verification: Informational). No change to the attack path (there is none). Remediation: `escape(picture)` (or `format_html`) in `_tile`, so the one mark_safe in the tag library that takes an unescaped value stops being a trap for a future caller. **Suggested remediation.** Use format_html for the tile (`format_html('<span class="avatar {} {}"><img src="{}" alt=""></span>', box, shape, picture)`), which also escapes a future caller's mistake.
tiagoagueda added this to the 1.0.0 milestone 2026-10-10 10:58:09 +00:00
Author
Owner

Fixed in 6d17e801f on main.

Fixed in 6d17e801f on main.
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#805
No description provided.