How to stop cross site scripting (XSS)?

xss-no-csp-protection global

The issue

Cross-site scripting lets someone else’s code run inside your page, with the visitor’s session. It can read what the page shows, capture what the visitor types and act as them. A single output-encoding mistake anywhere on the site is enough, and without a Content Security Policy nothing limits what the injected code can do.

The cause is a policy that is missing, or one that still allows the routes injected code uses: inline scripts, eval(), and scripts from any origin.

Scope

The policy applies to the whole document that receives it. It is normally set once for the site, so a weak policy weakens every page.

How to check you have it

  • In probeo: the related findings are csp-missing when there is no policy, and csp:script-src-unsafe-inline, csp:script-src-unsafe-eval, csp:script-src-wildcard and csp:script-src-data, each graded major, when the policy allows inline scripts, eval(), any origin or data: scripts.
  • By hand: run curl -sI https://your-site.example/ | grep -i content-security-policy and read script-src, or default-src if it is absent. Look for 'unsafe-inline', 'unsafe-eval', *, https: and data:.
  • With a tool: Google’s CSP Evaluator reports each of these.

The fix

Enforce a script-src that allows only named sources, with no 'unsafe-inline' and no 'unsafe-eval'.

Move inline scripts into files, or give each one a nonce or hash. Replace inline event handlers with addEventListener, and remove eval() and new Function(). Keep output encoding as it is; the policy is the second layer, not a replacement.

Done when: the enforced policy allows scripts only from named sources or by nonce or hash, and the check above comes back clean.

Do not

  • Do not keep 'unsafe-inline' or 'unsafe-eval' in a production policy.
  • Do not add data: or blob: to script-src.
  • Do not drop output encoding because a policy exists.
  • Do not allow every origin with a wildcard.

FAQ

Does CSP eliminate the need for output encoding?

No. Encoding stops injected content from becoming code. The policy stops it from running if encoding fails somewhere. Both are needed.

What breaks when unsafe-inline is removed?

Inline <script> blocks without a nonce or hash, and inline event handlers such as onclick. Inline styles are governed separately, by style-src.

Can CSP be deployed gradually?

Yes. Send the policy as Content-Security-Policy-Report-Only first, fix what the reports show, then enforce it.

How do nonces work for inline scripts?

The server generates a random value for each response and puts it in the policy and in the nonce attribute of each script it trusts. The browser runs only inline scripts whose nonce matches. An injected script can’t know the value.

Does CSP protect against all XSS types?

No. It blocks injected scripts from running, which covers much of reflected and stored XSS. DOM-based XSS through APIs such as innerHTML needs safe code as well; the Trusted Types directives are the policy-side control for that.

What happens when third-party scripts are necessary?

List their origins in script-src. A listed origin can run any code on the page, so keep the list short. For scripts that don’t change, Subresource Integrity checks each file against a known hash.

Which product detects this

Probeo checks this on every page of your site as part of Site Clarity. This page explains one instance; the product tells you every page that has it.