fix(security): externalize inline scripts to comply with default CSP (#338)#355
Merged
gogorichie merged 1 commit into05012026from May 1, 2026
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #338. The two inline
<script>blocks in EJS templates (_form.ejsandimport-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
src/public/js/firearm-form.js— the disposition-section visibility toggle, lifted from_form.ejs:125-144.src/public/js/firearm-import.js— the CSV upload + drag-and-drop logic, lifted fromimport-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.ejsandimport-content.ejs— deleted the inline<script>blocks.new.ejs,edit.ejs,import.ejs— addedpageScript: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
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— cleannpm test— 94/94 passingnpm run test:ci— coverage thresholds met/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.Related
ADMIN_PASSWORD=changeme#337 — boot-timeADMIN_PASSWORDguard, ships next as a separate PR (it's a major version bump).https://claude.ai/code/session_016wAgkh3Z8mcM1ZjGVKTKCn
Generated by Claude Code