Build a strict Content-Security-Policy

A page sends a Content-Security-Policy whose script-src allows any HTTPS origin, so an injected external script runs despite the nonce. Repair the builder so it produces a strict nonce-based policy from the document’s declared needs.

Requires Node.js 22 or newer. No package installation or server needed. To work in a container instead, open the folder with VS Code Dev Containers or GitHub Codespaces: .devcontainer/devcontainer.json uses the Dev Containers Node.js 24 image, pinned by digest.
1. Unzip all files into one folder.
2. In that folder, run: node --test exercise.test.mjs
3. Edit csp-policy.mjs, then run the same command again.

The starter deliberately fails some tests. A successful repair passes every case, including cases that catch common wrong fixes. The browser runs this same cases.mjs with the same fixtures; locally Node runs your code directly. If you completed the optional first stage in the browser, Download my work adds your input as stage1-input.json and the tests include it. Only run code you wrote or reviewed.

1. Reproduce the weak policy
Run the starter. Its script-src carries the nonce, so browsers ignore its 'unsafe-inline' and an injected inline script is blocked; but it also allows https: without 'strict-dynamic', so an injected <script src> from any HTTPS origin runs. The directives that stop clickjacking and base-tag and object abuse are missing.

2. Lock the defaults down
Start from default-src 'none', and add object-src 'none', base-uri 'none', frame-ancestors 'none' and form-action 'self'. These do not depend on the document’s content.

3. Allow scripts by nonce only
Set script-src to 'nonce-<nonce>' and 'strict-dynamic'. Do not add 'unsafe-inline', a scheme or a host: 'strict-dynamic' makes the browser ignore host-source allowlists, and 'unsafe-inline' is ignored next to a nonce in current browsers but lets injected inline scripts run in a browser that predates nonces.

4. Open only what the document declares
Set style-src 'self'; img-src to 'self' plus the declared img origins; connect-src to 'self' plus the declared connect origins; and add report-to when a group is given.

SUPPLIED CONTRACT
buildCsp(doc) returns the Content-Security-Policy header value as a string. doc has {nonce, connect, img, reportTo}: nonce is the per-response script nonce; connect and img are arrays of exact origins the document loads from (either may be empty); reportTo is a reporting group name or undefined. Build a policy that locks everything down and opens only what the document declares. default-src, object-src, base-uri and frame-ancestors are each the keyword 'none'; form-action is 'self'. script-src is the nonce as 'nonce-<value>' together with 'strict-dynamic', and nothing else: no 'unsafe-inline', no 'unsafe-eval', no scheme such as https:, no host and no '*'. style-src is 'self'. img-src is 'self' followed by the img origins; connect-src is 'self' followed by the connect origins. When reportTo is set, add report-to <group>. Separate directives with a semicolon and a space, and a directive’s tokens with single spaces.

REFERENCE
reference.mjs contains an explained solution: The policy begins default-src 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; form-action 'self'. script-src is the nonce and 'strict-dynamic' only, so an injected inline or src script without the nonce does not run and a host allowlist cannot weaken it. style-src is 'self'. img-src and connect-src start with 'self' and then list exactly the declared origins, so a document that declares https://api.example.test for connect gets connect-src 'self' https://api.example.test and nothing wider. report-to is added only when a group is named.

SCOPE
This builds the header value from declared needs; it does not generate the nonce (which must be unpredictable and fresh per response), send the Reporting-Endpoints header, or add the hashes an inline handler would need. CSP is defense in depth against injection, not a replacement for output encoding and a sanitizer. frame-ancestors and X-Frame-Options together stop framing; a page that must be embedded lists the embedding origin instead of 'none'. Roll a new policy out in report-only mode first.

NEXT TASK
Does your application’s CSP still contain 'unsafe-inline' or a broad https: in script-src, and what would it take to move to nonces and 'strict-dynamic'?

SOURCE
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy
