Web Application Security Checklist

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.

Show
  1. 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 and href values that start with javascript:.

    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
  2. 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', plus object-src 'none' and base-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);
  3. 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-Only first, send reports to an endpoint with report-to (and report-uri for 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.

  4. Serve user content with the right Content-TypeShould

    Why it matters and how to check

    Uploaded files and API responses served as text/html can turn into stored XSS. JSON endpoints should return application/json, user downloads should not be served as HTML from your main origin, and X-Content-Type-Options: nosniff should stop browsers from guessing.

    To verify, upload an .html or .svg file containing a script through any upload feature, then open its public URL. It should download or render without running script, and the response should carry nosniff. Ideally, user files are served from a separate domain.