Skip to content

fix(security): externalize inline scripts to comply with default CSP (#338)#355

Merged
gogorichie merged 1 commit into05012026from
claude/csp-externalize-inline-scripts-338
May 1, 2026
Merged

fix(security): externalize inline scripts to comply with default CSP (#338)#355
gogorichie merged 1 commit into05012026from
claude/csp-externalize-inline-scripts-338

Conversation

@gogorichie
Copy link
Copy Markdown
Member

Summary

Closes #338. The two inline <script> blocks in EJS templates (_form.ejs and import-content.ejs) violated Helmet v8's default Content-Security-Policy (script-src 'self'), so per the audit they were either silently broken in modern browsers or relying on CSP not actually being enforced. This PR moves both scripts to external files under /static/js/, where Helmet's default policy already allows them.

Changes

  • New src/public/js/firearm-form.js — the disposition-section visibility toggle, lifted from _form.ejs:125-144.
  • New src/public/js/firearm-import.js — the CSV upload + drag-and-drop logic, lifted from import-content.ejs:38-124. Rewrote to satisfy project ESLint rules (no-var, prefer-template) that couldn't see the script while it lived inside EJS. Behaviour is unchanged.
  • _form.ejs and import-content.ejs — deleted the inline <script> blocks.
  • new.ejs, edit.ejs, import.ejs — added pageScript: so the layout's existing external-script loader picks the files up.

No CSP middleware change is needed; once the inline blocks are gone, Helmet's default already permits /static/js/*.

User impact

Likely positive for everyone. If Helmet's default CSP has been silently blocking the inline scripts, the form's disposition toggle and CSV-import drag-drop have been broken for these users — externalizing them restores the features. If the CSP wasn't being enforced for some reason, behaviour is unchanged. Either way, no env / config / migration work is required.

Risk

  • No new dependencies. No DB schema, route, or middleware changes.
  • No env / config changes. No migration required.
  • Risk surface: if I missed wiring a pageScript, the affected feature breaks silently (status doesn't toggle disposition, drag-drop doesn't update upload UI). The smoke test below catches this.

Test plan

  • npm run lint — clean
  • npm test — 94/94 passing
  • npm run test:ci — coverage thresholds met
  • Manual smoke test in a real browser:
    • /firearms/new — change Status to "Sold" → disposition section appears; change to "Active" → disposition section hides.
    • /firearms/:id/edit — same disposition-toggle behaviour.
    • /firearms/import — drag a CSV onto the upload area → upload-text updates to file name; click Import → POST fires, results card renders.
    • DevTools console on the above pages: zero CSP violations.

Related

https://claude.ai/code/session_016wAgkh3Z8mcM1ZjGVKTKCn


Generated by Claude Code

Helmet v8's default Content-Security-Policy applies `script-src 'self'`,
which blocks inline `<script>` blocks. Two templates shipped inline
scripts that the audit (#338) flagged as either silently broken in
modern browsers or relying on CSP not actually being enforced.

Lift each verbatim into an external file under src/public/js and load
it via the existing pageScript mechanism in layout.ejs:

  src/views/firearms/_form.ejs            → src/public/js/firearm-form.js
  src/views/firearms/import-content.ejs   → src/public/js/firearm-import.js

Wire pageScript on the wrapper EJS files so the layout's external
script loader picks them up:

  src/views/firearms/new.ejs    → pageScript: '/static/js/firearm-form.js'
  src/views/firearms/edit.ejs   → pageScript: '/static/js/firearm-form.js'
  src/views/firearms/import.ejs → pageScript: '/static/js/firearm-import.js'

The extracted firearm-import.js was rewritten to satisfy the project's
ESLint rules (no-var, prefer-template) that ESLint couldn't see when
the script lived inside an EJS file. Behaviour is unchanged.

No CSP middleware change needed — once the inline blocks are gone,
Helmet's default `script-src 'self'` already allows /static/js/* files.

Closes #338

https://claude.ai/code/session_016wAgkh3Z8mcM1ZjGVKTKCn
@gogorichie gogorichie changed the base branch from main to 05012026 May 1, 2026 14:40
@gogorichie gogorichie marked this pull request as ready for review May 1, 2026 14:40
@gogorichie gogorichie merged commit c4d9716 into 05012026 May 1, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants