A client asked me to look at their site after a competitor's got defaced, more out of nervousness than any specific concern about their own setup. I expected to find nothing much, since the app itself was well built: parameterized queries, no obvious injection points, dependencies reasonably current. What I actually found was a site serving zero security headers. No Content-Security-Policy, no Strict-Transport-Security, no X-Content-Type-Options. Nothing wrong with the code, and nothing telling the browser how to defend it either.
That's the more common story than a dramatic breach. Most sites aren't fending off a targeted attacker. They're missing the boring, well-documented headers that cost nothing to add and meaningfully shrink what an attacker can do if something else ever goes wrong. This is a hygiene scan, not a penetration test: it tells you what's misconfigured or missing, not whether a determined attacker could get in. For that you want an actual pentest, scoped and run by a person.
Worth being upfront about what "AI security audit" means here, since the phrase gets used loosely. It's not a model reasoning about your threat model or trying to break in. It's a crawler reading your HTTP responses against a known-good baseline, an agent reading the resulting report, and that agent making the same kind of mechanical fix it would make for a missing meta tag. The intelligence is in connecting "this header is missing" to "here's the config change," not in discovering a novel vulnerability.
The headers worth checking
Content-Security-Policy restricts what a page is allowed to load and execute: which domains can serve scripts, styles, images, and fonts. It's the single header that does the most to contain an XSS bug if one ever slips through, because even injected script has nowhere to run if the policy doesn't allow it. MDN's CSP reference covers the full directive list; start with default-src 'self' and a report-only mode before enforcing anything, since a strict policy can break third-party embeds you forgot you had.
Strict-Transport-Security tells the browser to never downgrade the connection to plain HTTP, even if a link or a stale bookmark points there. Once a browser has seen the header once, it enforces HTTPS on its own for the max-age window. MDN's HSTS page recommends starting with a short max-age while you confirm nothing breaks, then raising it toward a year and adding includeSubDomains.
X-Content-Type-Options: nosniff stops the browser from guessing a file's type based on content rather than trusting the declared Content-Type. It's a one-line header with no real downside, and it closes off a class of attack where a file that looks like an image gets executed as a script instead.
X-Frame-Options, or the frame-ancestors directive in CSP, controls whether your pages can be embedded in an iframe on someone else's domain. Skip it and you're exposed to clickjacking: an attacker frames your login page invisibly under a button that looks harmless, and clicks land on your page instead of theirs.
Referrer-Policy and Permissions-Policy are lower-severity but still worth setting deliberately rather than leaving to browser defaults, since the defaults vary and can leak more of your URL structure to third-party sites than you'd choose to.
The OWASP Secure Headers Project keeps a maintained reference for all of these, including recommended values and a compatibility matrix, and it's the resource I check when a header's exact syntax has slipped my mind.
Beyond headers: mixed content and exposed secrets
Two more things show up constantly in a crawl-based scan. Mixed content is an HTTPS page quietly loading an image, script, or stylesheet over plain HTTP, which breaks the security guarantee the padlock is supposed to represent, and modern browsers increasingly block the resource outright rather than loading it insecurely. The fix is almost always mechanical: swap the URL to https:// or a protocol-relative form, then add Content-Security-Policy: upgrade-insecure-requests as a backstop for anything you missed.
The other is exposed secrets in client-side JavaScript: an API key, a database connection string, or a third-party token that got bundled into the frontend because an environment variable wasn't actually server-only. A crawler can catch the pattern in shipped JS the same way tools like Gitleaks or TruffleHog catch it in a git history, and it's worth treating as urgent, since a leaked key doesn't need an attacker to find a bug, just to read your page source.
A couple of things are worth checking by hand alongside any automated scan, since they're common enough to mention even though a crawl typically won't catch them: Subresource Integrity hashes on any third-party <script> tag you load from a CDN, so a compromised CDN can't silently swap the file you're serving, and the Secure, HttpOnly, and SameSite flags on your session cookies, which stop a cookie from being readable by JavaScript or sent cross-site where it shouldn't be.
The agent workflow
The loop is the same shape as any other audit-fix-reaudit cycle. Run a scan, read what's missing, patch the config, confirm it's actually being sent.
squirrel audit https://oldbrand.example --format llmFor a site on Next.js, the fix usually lands in next.config.js:
// next.config.js
module.exports = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self'" },
],
},
];
},
};That's a real API, documented in Next.js's headers configuration reference, and it's the kind of change an agent working from a squirrelscan report can make directly: read which headers are missing, add the block, and leave the exact CSP allowlist for you to tighten once you've confirmed nothing legitimate breaks. On a different stack it might be a Cloudflare Worker response header, an Nginx add_header line, or Express middleware like Helmet, but the pattern is identical. Deploy, then re-run the audit against the live URL and confirm the header shows up in the response, not just in the config file.
That last step matters more here than almost anywhere else in an audit, because a header that's in your config but not actually reaching production, behind a CDN that strips it, say, is indistinguishable from not having written it at all. Trust the re-scan over the diff.
What this doesn't replace
Say this plainly: a headers-and-hygiene scan is not a security audit in the sense a security team means it. It won't find a business logic flaw, an authorization bug that lets one user see another user's data, or a vulnerable dependency with a known CVE that needs a version bump rather than a header. It's the equivalent of checking smoke detectors are installed, not fireproofing the building. Worth doing, cheap to automate, and not a substitute for an actual review if your site handles anything sensitive.
If you want to see what this looks like on a real site, the sample report includes the security category alongside everything else squirrelscan checks. To run the same scan against your own headers specifically, the security headers checker gives you a fast read without a full crawl. And if you're already running this loop with an agent, the SEO-focused version of the same workflow and squirrelscan for Claude Code cover the parts of the setup this page didn't repeat.