Skip to content

Refactor inline JS in views to enable CSP enforcement (follow-up to #19) #359

Description

@jra3

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):

  1. 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.
  2. 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

  • No inline script. blocks or on* handlers remain in src/views (or all remaining ones carry a nonce)
  • /csp-report shows no script-src violations during an E2E run
  • Flip reportOnly: truefalse in src/index.ts
  • Verify all interactive flows still work (dialogs, admin panels, image upload, species explorer)

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsecuritySecurity improvements and vulnerability fixes

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions