---
title: "Allow only the scripts you wrote · Agent Wranglers"
url: https://agentwranglers.se/playbook/website/security/content-security-policy
updated: 2026-10-20
licence: "All rights reserved"
---

# Allow only the scripts you wrote

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Security](https://agentwranglers.se/playbook/website/security)
- Rule: Send a strict Content Security Policy, a header that says which scripts may run. Allow scripts only by nonce or hash, with `'strict-dynamic'`, `object-src 'none'`, `base-uri 'none'` and no `unsafe-inline`.
- How: On static pages, put the hash of each inline script and style in the policy (`'sha256-…'`). Load the bundle from a small hashed inline script, which `'strict-dynamic'` then trusts. On dynamic pages, use a fresh random nonce of at least 128 bits per response, never a fixed one. Add `default-src 'self'`, `object-src 'none'`, `base-uri 'none'`, `frame-ancestors 'none'` and `report-to`. Remove inline `style=` attributes and `on…=` event handlers, because hashes do not cover them. Try `Content-Security-Policy-Report-Only` first, then switch to enforcing.
- Check: `curl -sI` shows a `script-src` with a nonce or hash and without `'unsafe-inline'`, `'unsafe-eval'` or `*`. The console shows no violations. A CSP evaluator, such as Google's, reports no high-severity findings.

Example:

```http
Content-Security-Policy: default-src 'self';
  script-src 'sha256-AbC123...=' 'strict-dynamic';
  style-src 'self' 'sha256-XyZ789...=';
  object-src 'none'; base-uri 'none'; frame-ancestors 'none';
  upgrade-insecure-requests; report-to csp-endpoint
```

```html
<!-- The hash of this loader is in script-src. 'strict-dynamic' trusts what it loads. -->
<script>
  const s = document.createElement('script');
  s.src = '/app.3f9a1c.js';
  s.type = 'module';
  s.integrity = 'sha384-…';   // so an Integrity-Policy still lets it run
  document.head.append(s);
</script>
```

```js
// Express: a fresh nonce per response
import { randomBytes } from 'node:crypto';
app.use((req, res, next) => {
  res.locals.nonce = randomBytes(16).toString('base64');
  res.set('Content-Security-Policy', `script-src 'nonce-${res.locals.nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'`);
  next();
});
```

```python
# Flask: a fresh nonce per response, used in templates as {{ g.nonce }}
import secrets

@app.before_request
def make_nonce():
    g.nonce = secrets.token_urlsafe(16)

@app.after_request
def csp(response):
    response.headers["Content-Security-Policy"] = f"script-src 'nonce-{g.nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'"
    return response
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers website playbook: https://agentwranglers.se/playbook/website/security/content-security-policy`.

- Previous: [Load nothing over plain HTTP](https://agentwranglers.se/playbook/website/security/mixed-content)
- Next: [Collect reports of blocked content](https://agentwranglers.se/playbook/website/security/reporting-endpoints)
- [Security](https://agentwranglers.se/playbook/website/security)
