Security review: the remaining core issues (#959-#985) #1010
No reviewers
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo!1010
Loading…
Reference in a new issue
No description provided.
Delete branch "sec2-remaining-core"
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?
The thirteen remaining open core issues of the 2026-10-16 security review, one commit per issue.
geojson.readand the JSON-LD reader catchRecursionError(a document nested thousands deep);logos._walkis iterative. Tests: a deeply nested GeoJSON upload gets the refusal, a deeply nested JSON-LD block ends in the "nothing" sentence.logos.from_urlspends the allowance ofby, the person acting (no longer the company's owner or the sharedNonebucket); the logo dialog'sapply_logo(..., spend=False)ends its double charge. Tests count whose allowance is spent for a grantee and for a group-owned company.notifications.slow.linkappliesabsolute_url's precedence:POSTULO_PUBLIC_URL, else the stored base, else the bare path.ATTENDEEand no notes in any calendar file (interview and appointment; the DAV plugin writes the same lines); the people met stay named as text. Both help texts say the notes are left out. The goldens of the old writer lose the same two lines. The DAV plugin's own test andMETHOD:PUBLISHchoice belong in postulo-dav.docs/THREAT-MODEL.mdsays the referee rules bind the shipped themes and thesectionsvalue, and that a plugin theme is handed full records and trusted (#820).proxy.request_origin(), the peer that delivered the request, so a trusted proxy'sX-Request-IDsurvives itsX-Forwarded-For.POSTULO_PROXY_ADDRis left whenever a trusted peer sent any forwarding header;/healthzand the probe middleware read it throughproxy.was_forwarded().logs.scrub()applieslogredact.redact()first, so the JSON log scrubs password-reset and email-confirmation keys from one list (logredactjoins the foundation layer).seed_demo --passwordis a flag meaning "prompt"; the value comes fromPOSTULO_SEED_PASSWORD; CI's PostgreSQL job passes it throughenv:.htmlstrings.sentenceand the feature's own label (each of the three plugins exposesLABEL); no|safeon a translation; a lint allows|safeinoffline.htmlandpartials/messages.htmlonly.messages_tool.foreign_markup(html.parser) is part ofmessage_problems: a draft with markup the English lacks is left out of the.mo, a reviewed one failscheck;weblate.py configureadds thesafe-htmlflag (not run); the "you are here" tooltip is built from an element withtextContent.hx-valswithas_json; a template lint refuses anhx-vals/hx-headersbuilt by interpolation.Closes #959, #960, #962, #963, #964, #965, #976, #977, #979, #980, #982, #983, #985
Deviations from the issue text
getexif()is empty" holds for any photo without an orientation tag. Also dropped: APP3, COM, and data after the image's end (JPEG);tIME(PNG).proxy.request_origin()as existing; it did not, so it was added (anddescribe()uses it).redactruns before the route pattern (not after), so the two routes the JSON log already named keep their<token>mask.<).Tested
Whole unit suite with
-n 2: 19397 passed (one commit later, the two failures it showed,test_import_layersandtest_changelog, are fixed and re-run). E2E, locally, one file at a time:test_map.py(new tooltip test, red on the old JS),test_board_fold.py,test_company_social_profiles.py, and intest_accessibility.pythe company form at 2560 px and a new upload-form test, both schemes. The CI browser job should be read carefully: templates and JS touched are the board, the company form, the contact and profile pages, the upload form and the map tooltip.🤖 Generated with Claude Code
The company form printed Block.kept_back_contact with |safe, so whatever a translator wrote in that msgstr reached the page unescaped, while the profile and contact pages printed the same sentence escaped and showed a literal <em>. The sentences now carry a {setting} slot instead of markup; Block.kept_back_html and kept_back_contact_html fill it through htmlstrings.sentence (translation escaped, only the named slot filled) with the feature's own label, which each of the three plugins now exposes as LABEL so it is translated once. The three templates print the property without |safe, and tests/test_template_lint.py refuses |safe anywhere but offline.html and partials/messages.html. Six core msgids change (their translations are redone in Weblate). Closes #982 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>The fold links built a JSON document by dropping an HTML-escaped string into a quoted JSON string, so their safety rested on a filter in a view two files away. columns() now carries the value as a dict and the template writes it with as_json, as the map attributes do. A lint beside HTMX_SCRIPTING refuses an hx-vals or hx-headers value with interpolation in it unless the only thing in it is `{{ x|as_json }}`. Closes #985 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>