All 30 checks Missing Content-Security-Policy
Missing Content-Security-Policy
Updated
A Content-Security-Policy header tells the browser which sources scripts, styles, images and frames may load from, so that code injected into the page by an attacker is blocked instead of run.
HTTP/2 200
content-type: text/html; charset=utf-8
strict-transport-security: max-age=31536000Content-Security-Policy-Report-Only: default-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'The fix for your stack
Fix for: Next.js / Vercel
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async headers() {
return [
{
source: "/:path*",
headers: [{ key: "Content-Security-Policy-Report-Only", value: "default-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" }],
},
];
},
};
export default nextConfig;This ships as Content-Security-Policy-Report-Only, so nothing breaks yet. Watch the browser console for violations, add the origins your pages load from, then rename the header to Content-Security-Policy to enforce it. The check passes once the enforcing header is sent.
What a policy does
Cross-site scripting happens when an attacker gets their own JavaScript to run on your page, for example through an unescaped comment field or a compromised third-party script. A Content-Security-Policy narrows what the browser will accept. With a policy such as default-src 'self', the browser refuses to run a script from an origin you did not list and refuses inline code unless you allow it, so many injected payloads simply do not execute.
It is a defence in depth, not a replacement for escaping user input, and it can break a working site if written carelessly. Sites that use analytics, tag managers, chat widgets, embedded maps and web fonts load from many origins, and each one has to be listed. That is why the policy is introduced gradually rather than pasted in and forgotten.
How Skewcheck detects it
Skewcheck reads the response headers of the page and looks for content-security-policy. If the header is present with any value, the check passes. If it is absent, the check fails with medium severity and removes 4 points from the score. Skewcheck does not evaluate the policy: a very permissive one such as default-src * passes, and a strict one that breaks your page also passes.
Two limits are worth knowing. A policy delivered only through a <meta http-equiv> tag is not a header and does not count here. And the Content-Security-Policy-Report-Only header, which logs violations without blocking anything, is not the enforcing header, so it does not pass the check either. That is deliberate: a report-only policy protects nobody yet, but it is the right first step.
curl -sI https://example.com | grep -i content-security-policyRolling it out safely
Start with Content-Security-Policy-Report-Only and the starter policy above. Leave frame-ancestors out of it, because browsers ignore that directive in report-only mode; put it in the enforced header, or keep using X-Frame-Options. Browse the whole site with the developer console open and note each violation message, which names the blocked origin. Add the origins your pages legitimately use, for example your analytics host, your font provider and your embedded video, until the console is quiet. Then rename the header to Content-Security-Policy to enforce it, and test the pages again, forms and checkout included.
The per-stack snippets above show where the header goes for each host. Platforms that let you add custom headers vary in which plans allow it, so confirm that in the platform's own documentation before planning around it. Inline scripts and styles need either nonces or hashes in the policy, or the keyword 'unsafe-inline', which weakens the protection; a frame-heavy or script-heavy site may take several rounds before it is clean.
Before and after
HTTP/2 200
content-type: text/html; charset=utf-8HTTP/2 200
content-type: text/html; charset=utf-8
content-security-policy: default-src 'self'; img-src 'self' data: https:; object-src 'none'Check your page
Run all 30 checks on any URL. Free, no account.