Discovered during ad-hoc testing on PR #284. Full context in .claude/notes/2026-05-20-signup-flow-adhoc-findings.md — Finding 11.
signup_submit accepts first_name=<script>alert(1)</script> (and any other HTML/JS payload) and stores it verbatim in the identity JSONB column. Whether this turns into an active XSS depends on every render site that displays member identity fields.
Jinja autoescape covers the obvious server-rendered cases, but JS string interpolation (element.innerHTML = data.first_name, template-literal injection, dataset reads, etc.) does not auto-escape.
Suggested audit surface
- Admin portal
index.html — paginated member list cells, sidebar tabs that render data.first_name, the onboarder-picker dropdown items
- Member portal
_dashboard_base.html header greeting and any other {{ identity.* }} use that bypasses autoescape (e.g. |safe filter — should be zero, but worth confirming)
- DH2MG email templates — does identity flow into HTML email bodies?
- Nametag printer (
code/lines/nametag-printer/) — nametag_subtitle rendering
- Grafana dashboards if any pull identity data
Also worth pairing with a server-side validate_signup_data() (see related issue) to add input sanitization at the source: strip control chars, enforce charset on names.
Context: #283, PR #284
Discovered during ad-hoc testing on PR #284. Full context in
.claude/notes/2026-05-20-signup-flow-adhoc-findings.md— Finding 11.signup_submitacceptsfirst_name=<script>alert(1)</script>(and any other HTML/JS payload) and stores it verbatim in theidentityJSONB column. Whether this turns into an active XSS depends on every render site that displays member identity fields.Jinja autoescape covers the obvious server-rendered cases, but JS string interpolation (
element.innerHTML = data.first_name, template-literal injection, dataset reads, etc.) does not auto-escape.Suggested audit surface
index.html— paginated member list cells, sidebar tabs that renderdata.first_name, the onboarder-picker dropdown items_dashboard_base.htmlheader greeting and any other{{ identity.* }}use that bypasses autoescape (e.g.|safefilter — should be zero, but worth confirming)code/lines/nametag-printer/) —nametag_subtitlerenderingAlso worth pairing with a server-side
validate_signup_data()(see related issue) to add input sanitization at the source: strip control chars, enforce charset on names.Context: #283, PR #284