Conversation
fast_html does not escape text content or attribute values, and every as_html() / as_html_thumbnail() output is rendered with | safe in the jinja2 templates. User-controlled fields (title, author, name, slug, username) could therefore inject HTML/JS. Wrap user strings with django.utils.html.escape before passing them to fast_html on Sound, Marker, Object, and Exhibit. Also remove the | safe filter from post.excerpt in post_preview.jinja2 — excerpt is a plain TextField, so default jinja autoescaping is the correct behavior (post.formatted_body is separately sanitized by ProseEditor). Closes #888 https://claude.ai/code/session_01XC1THLWgnGXGf5wgRhdyvB
7 tasks
Member
Author
Self-reviewVerdict: ✅
Combined with Scope:
Verified: Unique: No duplicate PR. Generated by Claude Code |
Resolves the conflict this PR had accumulated since April. develop has since deleted Sound.as_html_thumbnail and Exhibit.as_html_thumbnail, so the escaping this PR added to them is moot and those hunks take develop's deletion. The surviving conflict in Marker.as_html combines both sides: develop's cache-busted `src` with this PR's escape(self.title). What remains is the whole of the fix on current develop: escape() at the three as_html attribute sites, and the `| safe` dropped from the blog excerpt. Adds the tests the PR was missing. A security fix without one is how the bug returns: they assert a title of `" onerror="alert(1)` cannot break out of the attribute on Marker, Object and Sound, that a script tag is neutralised, and — deliberately — that fast_html still escapes nothing, so the call-site escaping is known to be load-bearing rather than assumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXUw7kyELDu8ycGQxtaFbb
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Brings this up to date with
develop(it had been conflicted since April) and adds the tests it was missing.The issue it closes, #888, has been rewritten: the original text described sanitising a
CommentFormthat does not exist in this repository. The concern behind it turned out to be real and reachable, and worse than the issue claimed — a stored XSS, now documented with the exact lines.Resolves (Issues)
Closes #888
The vector, for the reviewer
fast_htmlescapes nothing:titleis a plain user-supplied form field, it goes straight into that attribute inas_html(), and the modal templates render the result with| safe. Upload a marker titled" onerror="alert(1)and it runs in the browser of anyone who opens that marker's modal.What the merge changed about this PR
develophas since deletedSound.as_html_thumbnailandExhibit.as_html_thumbnail, so the escaping this PR originally added to them no longer has anywhere to live — those hunks take develop's deletion. The one surviving conflict was inMarker.as_html, where develop introduced a cache-bustedsrc; both sides are kept.Net diff against current
developis therefore small and entirely the fix: theescapeimport,escape()at the threeas_htmlattribute sites, and the| safedropped from the blog excerpt.Tests added
The original PR had none, which is how this bug comes back.
src/core/tests/test_html_escaping.py:" onerror="alert(1)cannot break out of the attribute, onMarker,ObjectandSound;<script>tag in a title is neutralised;fast_htmlstill escapes nothing — so if that ever changes, someone finds out deliberately rather than discovering that the call-site escaping had quietly become redundant.Removing the three
escape()calls again makes 4 of the 5 fail.Worth a follow-up, not done here
Escaping at each call site relies on every future
as_htmlremembering to do it, andfast_htmlwill keep not escaping. A single choke point returningSafeStringwould be sturdier — but that is a refactor, and this PR is a security fix that should merge on its own.Verification: full suite
pytest src/core src/users src/blogpasses — 277 passed.ruff format --diff src/clean,ruff check --extend-select Iat 93 findings matchingdevelopwith zeroI001, no pending migrations.🤖 Generated with Claude Code
https://claude.ai/code/session_01HXUw7kyELDu8ycGQxtaFbb
Generated by Claude Code