← All news
security4 min read

Security headers that AI-generated apps ship without

By Seaworthy · 20 June 2026

Code on a computer screen showing programming lines, representing HTTP server response headers and web security configuration

Photo by Chris Ried on Unsplash

AI coding tools are remarkable at getting an app off the ground fast. They will scaffold routes, wire up authentication, connect a database, and give you something running in an afternoon. What they almost never do is configure HTTP security headers.

This is not a dig at AI tools specifically. Most tutorials skip headers. Most boilerplate skips headers. But the result, when you multiply it across thousands of vibe-coded apps now running in production, is a large surface area of easily-preventable vulnerabilities.

Here is what is typically missing, and why it matters.

Content-Security-Policy

CSP is the big one. An absent or permissive CSP makes XSS attacks significantly more effective. Without a policy telling the browser which sources are allowed to load scripts, an attacker who finds a way to inject content can run arbitrary JavaScript in your users' sessions.

AI tools almost never generate a CSP. The reason is practical: CSP is hard to get right. A policy that works for your application today breaks tomorrow when you add an analytics script or a third-party widget. So tools that optimize for "runs first time" skip it entirely.

A minimal starting point for a Next.js app:

// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: [
      "default-src 'self'",
      "script-src 'self' 'unsafe-inline'",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "connect-src 'self'",
    ].join('; '),
  },
];

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: securityHeaders,
      },
    ];
  },
};

This is still permissive (unsafe-inline is not ideal), but it is an enormous improvement over no policy at all. The real work is tightening it down with nonces or hashes once you know what your application actually loads.

X-Content-Type-Options

This one is a single line, and yet it is absent from most generated apps:

X-Content-Type-Options: nosniff

Without it, some browsers will try to sniff the MIME type of a response. If an attacker can get a file with a .jpg extension to contain JavaScript, the browser might execute it. The nosniff directive stops that.

It costs nothing. It is not configurable. There is no tradeoff. AI tools just do not generate it.

Strict-Transport-Security

HSTS tells browsers to only contact your site over HTTPS, even if the user types a plain HTTP URL. Without it, the first request is vulnerable to a downgrade attack.

Strict-Transport-Security: max-age=31536000; includeSubDomains

The catch is that you should only enable HSTS when you are confident your domain and all subdomains are serving valid HTTPS. Get this wrong and you can lock users out. That complexity is probably why generated code avoids it. But for any production app on a proper TLS setup, there is no good reason to leave it out.

Referrer-Policy

When a user clicks a link from your app to an external site, the browser sends a Referer header containing the full URL. If your URLs contain user IDs, tokens, or anything sensitive in the path or query string, you are leaking that to third parties.

Referrer-Policy: strict-origin-when-cross-origin

This sends the origin only (no path, no query string) for cross-origin requests. It is the sensible default. AI-generated apps essentially never set this.

Where Express apps usually stand

If you are running an Express server rather than a Next.js setup, you are probably missing all of these. Express ships with no security headers. The conventional fix is helmet:

import helmet from 'helmet';

app.use(helmet());

Helmet sets sane defaults for most of these headers in one call. The problem is that AI-generated Express apps frequently do not include it, even though it is a well-known package. The tools know about helmet in the abstract, but the scaffolding does not reliably add it.

Why this happens

Generated code is trained on examples that prioritize making things work. The internet is full of tutorials that say "add this middleware" and never mention helmet. Stack Overflow answers showing Express setup almost never include security headers unless the question is specifically about security.

The model learns to write code that gets accepted and upvoted. Security headers do not make the app run faster or more visibly. They are invisible when correct and only visible when something goes wrong.

There is also a compounding effect. AI tools are good at following patterns in the codebase they see. If you scaffold a new route and there are no headers configured anywhere, the tool has no prior art to follow. It will not spontaneously add them.

What to actually do

Run your app through securityheaders.com. It will grade your response headers and list exactly what is missing. This takes two minutes and the output is specific.

For Next.js, put your headers in next.config.js. For Express, add helmet. For anything else, check whether your reverse proxy (Nginx, Caddy, Traefik) lets you set headers globally and handle it there.

The goal is not to achieve a perfect CSP from day one. Start with the low-hanging fruit: nosniff, Referrer-Policy, and basic HSTS if your TLS setup is stable. Add CSP incrementally as you understand what your app actually loads.

Seaworthy checks for missing security headers as part of its security scan category, reporting which headers are absent from your application's HTTP responses.

This article was generated by AI and summarises publicly available sources.

From the makers of Seaworthy

Seaworthy scans your repo for the issues covered in articles like this one.

Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.

$ npx seaworthycode