Chapter 4 of 8 · 4 checks · 2 min
Cross-site scripting and Content Security Policy
XSS lets an attacker run script as your user, which defeats CSRF tokens and steals data. Encode output by default and add a strict CSP as a second layer.
Let the framework escape output and review every escape hatchMust
Why it matters and how to check
React, Vue, Angular and server templates escape text by default. XSS usually comes back through the escape hatches:
dangerouslySetInnerHTML,v-html,innerHTML, unescaped template tags andhrefvalues that start withjavascript:.To verify, search for each of these and confirm the input is either trusted constant content or passed through a maintained sanitizer such as DOMPurify. Then save
<img src=x onerror=alert(1)>in every user-editable text field, including names and bios, and view it where other users see it. No alert should fire.Avoid
- Building HTML strings from user input
- Allowing user-supplied URLs in href without checking the scheme
Send a strict, nonce-based Content Security PolicyShould
Why it matters and how to check
A strict CSP stops injected scripts from running even when an encoding bug slips through. The OWASP cheat sheet recommends a fresh random nonce per response with
'strict-dynamic', plusobject-src 'none'andbase-uri 'none'. Avoid'unsafe-inline'for scripts, and never add nonces with middleware that rewrites every script tag.To verify, load a page and read the response header in dev tools. Reload and confirm the nonce changes. Open the console and run a small inline script injection test: the browser should report a CSP violation.
Example
TypeScript // A fresh nonce for every response, also added to your script tags const nonce = crypto.randomBytes(16).toString('base64'); const csp = [ `script-src 'nonce-${nonce}' 'strict-dynamic'`, "object-src 'none'", "base-uri 'none'", ].join('; '); res.setHeader('Content-Security-Policy', csp);Roll out CSP in report-only mode first and collect violationsNice to have
Why it matters and how to check
A strict policy can break analytics, chat widgets and inline handlers you forgot about. Ship it as
Content-Security-Policy-Report-Onlyfirst, send reports to an endpoint withreport-to(andreport-urifor older browsers), fix what breaks, then switch the header name to enforce.To verify, confirm your reporting endpoint receives violation reports while you click through the main flows. Before enforcing, the report volume from real pages should be near zero, with any remaining reports traced to extensions rather than your own code.
Serve user content with the right Content-TypeShould
Why it matters and how to check
Uploaded files and API responses served as
text/htmlcan turn into stored XSS. JSON endpoints should returnapplication/json, user downloads should not be served as HTML from your main origin, andX-Content-Type-Options: nosniffshould stop browsers from guessing.To verify, upload an
.htmlor.svgfile containing a script through any upload feature, then open its public URL. It should download or render without running script, and the response should carrynosniff. Ideally, user files are served from a separate domain.