Background
PR #342 (issue #19) shipped Content-Security-Policy in Report-Only mode (src/index.ts:104, reportOnly: true), with a violation collector at /csp-report (src/index.ts:91). The policy is otherwise strict: scriptSrc: ['self', 'https://esm.sh'] with no 'unsafe-inline'.
CSP cannot be flipped to enforced yet because the views still contain inline JavaScript, which the policy would block. This issue tracks the refactor needed to enable enforcement.
Current inline-JS inventory (as of main @ 442365c)
- 24 inline
script. blocks across 23 view files
- 46 inline
on* handler attributes (onclick, onchange, onsubmit, …) across 20 view files
Note: styleSrc already permits 'unsafe-inline', so inline styles are fine — this is scoped to scripts only.
Files with inline script. blocks (24)
src/views/admin/approvalBonuses.pug
src/views/admin/approvalPanel.pug
src/views/admin/editApprovedSubmission.pug
src/views/admin/members.pug
src/views/admin/mergeSpeciesDialog.pug
src/views/admin/queue.pug
src/views/admin/speciesEdit.pug
src/views/admin/speciesList.pug
src/views/admin/waitingPeriod.pug
src/views/admin/witnessQueue.pug
src/views/bapForm/imageUpload.pug
src/views/demo/emails.pug
src/views/dialog/cares-fry-share.pug
src/views/dialog/cares-register.pug
src/views/dialog/collection-add.pug
src/views/dialog/collection-edit.pug
src/views/dialog/collection-link.pug
src/views/display.pug
src/views/member.pug
src/views/submission/editMedia.pug
src/views/submission/review.pug
src/views/submit.pug
src/views/test/tom-select-demo.pug
Files with inline on* handlers (46 across these 20)
src/views/account/links.pug
src/views/account/tankPresetForm.pug
src/views/account/tankPresets.pug
src/views/admin/addCommonNameForm.pug
src/views/admin/addScientificNameForm.pug
src/views/admin/canonicalRecommendations.pug
src/views/admin/editApprovedSubmission.pug
src/views/admin/speciesEdit.pug
src/views/admin/speciesList.pug
src/views/bapForm/imageUpload.pug
src/views/demo/emails.pug
src/views/dialog/cares-fry-share.pug
src/views/dialog/cares-register.pug
src/views/dialog/collection-edit.pug
src/views/mixins/collectionCard.pug
src/views/mixins/emptyState.pug
src/views/species/detail.pug
src/views/species/explorer.pug
src/views/submission/editMedia.pug
src/views/submission/review.pug
Approach
Two viable paths (pick one; not mutually exclusive):
- Externalize to static JS (preferred, aligns with HTMX-first architecture) — move inline
script. blocks into public/js/* files loaded with <script src>, and replace on* attributes with addEventListener wired via data-* hooks or HTMX attributes. No 'unsafe-inline', no per-request nonce plumbing.
- Per-request nonces — generate a nonce per response, add
nonce= to each allowed inline block, and set scriptSrc: ['self', 'https://esm.sh', (req,res)=>\'nonce-${res.locals.nonce}'`]`. Keeps code inline but requires threading the nonce through every template.
Given the CLAUDE.md rule to prefer HTMX over custom JS, option 1 is the better fit for most of these.
Definition of done
References
Background
PR #342 (issue #19) shipped Content-Security-Policy in Report-Only mode (
src/index.ts:104,reportOnly: true), with a violation collector at/csp-report(src/index.ts:91). The policy is otherwise strict:scriptSrc: ['self', 'https://esm.sh']with no'unsafe-inline'.CSP cannot be flipped to enforced yet because the views still contain inline JavaScript, which the policy would block. This issue tracks the refactor needed to enable enforcement.
Current inline-JS inventory (as of
main@ 442365c)script.blocks across 23 view fileson*handler attributes (onclick,onchange,onsubmit, …) across 20 view filesFiles with inline
script.blocks (24)Files with inline
on*handlers (46 across these 20)Approach
Two viable paths (pick one; not mutually exclusive):
script.blocks intopublic/js/*files loaded with<script src>, and replaceon*attributes withaddEventListenerwired viadata-*hooks or HTMX attributes. No'unsafe-inline', no per-request nonce plumbing.nonce=to each allowed inline block, and setscriptSrc: ['self', 'https://esm.sh', (req,res)=>\'nonce-${res.locals.nonce}'`]`. Keeps code inline but requires threading the nonce through every template.Given the CLAUDE.md rule to prefer HTMX over custom JS, option 1 is the better fit for most of these.
Definition of done
script.blocks oron*handlers remain insrc/views(or all remaining ones carry a nonce)/csp-reportshows noscript-srcviolations during an E2E runreportOnly: true→falseinsrc/index.tsReferences
src/index.ts:104-123src/index.ts:91-101