I was reviewing an AI product last month — a Node/Express backend serving an LLM-powered chat interface, streaming responses, handling file uploads for RAG, the works. The prompt engineering was solid. Rate limiting on the model calls was solid. Then I ran curl -I against the API and got back a response with zero security headers. No CSP, no X-Frame-Options, no X-Content-Type-Options. Nothing.
This is a pattern I see constantly in AI engineering specifically. Teams spend weeks hardening prompts against injection, sanitizing model outputs, and locking down API keys — and then ship the actual HTTP layer wide open, because “that’s a security team thing” or “we’re just serving JSON to a frontend.” Meanwhile the same server is rendering user-generated content, streaming model outputs into a browser, and accepting file uploads — exactly the conditions where missing headers turn into real exploits.
That’s the gap helmet.js closes, in about two lines of code.
What Helmet.js Actually Is
Helmet isn’t a security product. It’s a small Express (or Fastify, via adapters) middleware that sets a collection of the HTTP response headers browsers use to enforce security policies — the same headers discussed in most security header writeups: Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, and a handful of others most people forget exist.
import helmet from 'helmet';
app.use(helmet());
That single line sets over a dozen headers with sane defaults. It doesn’t replace understanding what each header does — but it removes the “I’ll get to it eventually” excuse, because the baseline is one import away.
Why This Matters More, Not Less, for AI Applications
AI apps have a few characteristics that make missing headers riskier than in a typical CRUD app:
You’re rendering content you didn’t write. Model outputs get displayed directly to users — in chat UIs, in generated reports, in code snippets the model produces. If a prompt injection or a jailbroken response manages to produce something like <script> tags or malicious markdown, a solid CSP is what stops that content from executing in the browser, even after it slipped past your output filtering.
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
connectSrc: ["'self'", 'https://api.your-llm-provider.com'],
objectSrc: ["'none'"],
},
}),
);
You’re often embedding third-party widgets and streaming connections. Chat interfaces frequently pull in analytics scripts, feedback widgets, or embed players. Each one is a potential script-injection vector, and CSP’s connectSrc/scriptSrc directives are what keep that surface controlled instead of wide open.
Your endpoints are attractive clickjacking targets. An AI tool that lets users trigger actions — send an email, run a query, spend API credits — is exactly the kind of interface an attacker wants to frame invisibly on another page and trick a logged-in user into clicking. X-Frame-Options (and CSP’s frame-ancestors) shuts that down.
app.use(helmet.frameguard({ action: 'deny' }));
You’re handling uploads for RAG or fine-tuning. Document upload pipelines are a classic MIME-sniffing target — a file disguised as a PDF that a browser decides to interpret as HTML or JS. X-Content-Type-Options: nosniff, which Helmet sets by default, forces the browser to trust your declared content type instead of guessing.
What Helmet Sets Out of the Box
Helmet’s defaults cover the essentials without any configuration:
Content-Security-Policy— a reasonably strict default policy (worth customizing per app)Strict-Transport-Security— enforces HTTPS on repeat visitsX-Content-Type-Options: nosniff— blocks MIME-sniffingX-Frame-Options: SAMEORIGIN— basic clickjacking protectionX-DNS-Prefetch-Control— limits DNS prefetching that can leak browsing behaviorCross-Origin-Opener-PolicyandCross-Origin-Resource-Policy— isolate your app from certain cross-origin attacks, increasingly relevant as AI apps embed iframes and third-party model providers
You don’t need to memorize this list. That’s the point of the middleware — it encodes the checklist so you don’t have to reconstruct it from memory on every project.
Where Teams Get It Wrong
The most common mistake isn’t skipping Helmet entirely — it’s calling helmet() once and never touching the CSP again as the app grows. An AI app in month one might only call your own API. By month six it’s loading a charting library from a CDN, embedding a support widget, and streaming from a third-party model provider over WebSockets. If the CSP wasn’t updated alongside those additions, one of two things happens: either the feature silently breaks in production, or someone “fixes” it by adding unsafe-inline and unsafe-eval to make the errors go away — which quietly guts the entire policy.
Treat your CSP as living configuration, reviewed whenever you add a new external dependency, not a one-time setup step.
The Practical Takeaway
If you’re building or shipping an AI-powered app and haven’t touched your HTTP headers:
npm install helmetand addapp.use(helmet())today — it’s close to zero risk and immediate coverage.- Customize the CSP directives to match your actual dependencies: your LLM provider’s API domain, your CDN, your analytics.
- Re-check the CSP every time you add a new third-party script, embed, or streaming connection.
- Don’t treat this as separate from “AI security.” The prompt layer and the transport layer are both part of the same attack surface — and the transport layer is the one nobody’s excited to talk about, which is exactly why it gets skipped.
Prompt injection defenses get all the attention right now. Headers don’t get a conference talk. But an unprotected response header is a much older, much better-understood attack vector than anything happening inside your model — and it takes one line of code to close.