PST-075: The messages partial decides a message is HTML by a substring test on extra_tags #804
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#804
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-075. 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/partials/messages.html(12)Who can do it, and what they need. No attacker today: the only producers of extra_tags="safe" (capture_views.py:458, 498, 611) build their text with format_html, and allauth's messages carry no extra tag.
How it works.
A future call
messages.info(request, user_text, extra_tags="unsafe")or any tag containing the letters 'safe' (e.g. 'safe-to-retry') would be printed with |safe, since'safe' in "unsafe"is true for the string extra_tags holds.Impact. None now; a latent stored-XSS sink that depends on nobody ever choosing a tag that contains 'safe'.
Verification (verdict: confirmed; severity after verification: Informational). File and line are correct (src/postulo/templates/partials/messages.html:12). Reachability needs a future code change that introduces a message with a tag containing 'safe' and unescaped user text. There is no attacker-reachable path today. Suggested fix: compare tags as a list, for example
'safe' in message.extra_tags.split(or amessage.tagsfilter), or drop the|safebranch and rely on SafeData, which Django already preserves in storage.Suggested remediation. Test the tag as a word (
{% if 'safe' in message.extra_tags.split %}) or, better, keep a dedicated helper (e.g. messages_tool.safe_message) that is the only producer of that tag, with a lint/test overmessages.*(... extra_tags=calls.Fixed in
138e717fbon main.