Helmet for Express: what the 13 default HTTP response headers actually do
Help secure Express apps with various HTTP headers
At a glance
- What is it?
- Helmet is an Express middleware that sets HTTP response headers such as Content-Security-Policy and Strict-Transport-Security. The defaults are a starting point, not a finished policy: CSP is the header you will almost certainly have to configure.
- Who is it for?
- Adopt Helmet if you run an Express app and want the standard security headers set without hand-writing each one; the one-line app.use(helmet()) is the whole integration for many projects. Do not adopt it expecting a complete security layer: it touches response headers only, and its own README says it performs very little validation on your CSP.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 20 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem Helmet solves for Express apps
An Express app that never touches response headers ships with browser defaults, and those defaults are permissive. Helmet exists to change that in one call. According to the README, app.use(helmet()) sets 13 HTTP response headers, among them Content-Security-Policy, Cross-Origin-Opener-Policy and Strict-Transport-Security. The stated goal is to be quick to integrate and low maintenance afterward, which is a fair description of the scope: it is middleware, not a scanner, not a WAF and not an authentication layer.
The audience is narrow and clear. You are building a Node service on Express, you want the conventional header set, and you would rather not assemble it by hand from MDN pages. Helmet is also usable per-header: the README notes each middleware can be mounted standalone, for example app.use(helmet.contentSecurityPolicy()). That matters for apps that already set some headers elsewhere and only need a subset.
How Helmet sets headers: middleware, defaults, and per-header overrides
Helmet is a collection of small middlewares behind one factory. Calling helmet() with no arguments applies the default configuration of every header it enables; calling it with an options object lets you disable a header (contentSecurityPolicy: false) or pass header-specific options. The repository layout matches this: an index.ts at the root and a middlewares/ directory, with the package built from TypeScript source via a build script.
Content-Security-Policy is the interesting case. The default policy is a string of directives: default-src 'self', base-uri 'self', font-src 'self' https: data:, form-action 'self', frame-ancestors 'self', img-src 'self' data:, object-src 'none', script-src 'self', script-src-attr 'none', style-src 'self' https: 'unsafe-inline' and upgrade-insecure-requests. When you pass directives, they are merged into that default set unless you set useDefaults to false. Directive keys accept camel case (defaultSrc) or kebab case (default-src), and values are arrays of strings or functions. A function in the array is called with the request and response objects, which is how the README's nonce example works.
The README is explicit that this is where Helmet stops helping: it performs very little validation on your CSP and points you at external CSP checkers instead. Treat the default policy as a template, not an audit result. Two defaults deserve attention before you deploy. upgrade-insecure-requests is on by default, and the README notes Safari will upgrade http://localhost to https://localhost, which can cause problems in development. And style-src includes 'unsafe-inline', a default that weakens the policy for the sake of getting apps running.
Installing Helmet and setting your first policy
Helmet is published on npm and the README's quick start is a two-line integration. It is an ES module package (the package.json sets "type": "module") and declares Node >=18.0.0 in engines, so check your runtime before upgrading.
npm install helmetThen mount it on the app. The README's example imports the default export and passes it to app.use:
import helmet from "helmet";
const app = express();
app.use(helmet());With that in place, responses carry the default header set. You can confirm by inspecting any response in your browser's network panel or with curl against a running route.
The first real configuration step is almost always script-src. This example, taken from the README's configuration section, keeps the defaults but adds an allowed script origin:
app.use(
helmet({
contentSecurityPolicy: {
directives: {
"script-src": ["'self'", "example.com"],
},
},
}),
);If you want to see what a stricter policy would break before enforcing it, the README documents a report-only mode: pass reportOnly: true inside the contentSecurityPolicy options to emit Content-Security-Policy-Report-Only instead. If you need the exact default directive object to build on, helmet.contentSecurityPolicy.getDefaultDirectives() returns it.
Where Helmet's defaults get in the way
The most common failure mode is a Content-Security-Policy that blocks something the page needs. The default script-src is 'self' and script-src-attr is 'none', so inline event handlers and third-party script tags will not run until you add sources or a nonce. Helmet will not tell you which of your assets broke; the browser console will, and the README directs you to CSP Evaluator rather than offering built-in diagnostics.
The second is the development/production split. Because upgrade-insecure-requests is on by default, the README recommends disabling it in development, showing a pattern that reads app.get("env") and sets the directive to null when the environment is development. If you skip this, local HTTP testing can behave oddly in Safari specifically. That is a real cost of the default: the header set is tuned for deployed HTTPS apps, and you inherit a small amount of environment-specific configuration.
A third boundary is scope. Helmet sets response headers. It does not sanitize input, does not rate limit, and does not know whether your application logic is sound. If your problem is injection through request bodies, Helmet is the wrong tool; it will not touch that path.
How Helmet compares to setting headers by hand or using a reverse proxy
The realistic alternative is not another npm package so much as a different layer: setting the same headers in nginx, Caddy or your CDN configuration. The difference is where the policy lives. A proxy-level header applies to every response that passes through it, including static assets and services that are not Express, and it can be changed without redeploying the app. Helmet applies inside the Node process, which means the policy travels with the code, can branch on the request object (the nonce example depends on this), and works in environments where you do not control the proxy.
That request-awareness is the concrete argument for Helmet over a static proxy config. Generating a per-request CSP nonce requires access to the response, which the README's example gets through res.locals. A proxy cannot do that without its own scripting. The counterargument is operational: if you already terminate TLS and manage headers centrally, adding Helmet means two places define security headers, and whichever runs later wins. Pick one layer rather than both.
Maintenance, licensing and upgrade cost
The repository is not archived and the last push was on 2026-09-11, so changes are still landing. The package version in package.json is 8.3.0, and the project is written in TypeScript with a build script that produces the published package, plus a test suite run through tsx --test and separate lint, formatting and type-check scripts. For an adopter, the practical upgrade cost is the CSP surface: when a new browser default or a new directive lands, you may need to revisit your directives object.
Helmet is MIT licensed, which permits commercial use and modification; the repository carries a LICENSE file and a SECURITY.md for vulnerability reports. That is a permissive arrangement with no copyleft obligation on your application code. It is not legal advice, and if your organization has a policy on dependency licences, the LICENSE file is the authoritative text.
Editorial conclusion
Adopt Helmet if you run an Express app and want the standard security headers set without hand-writing each one; the one-line app.use(helmet()) is the whole integration for many projects. Do not adopt it expecting a complete security layer: it touches response headers only, and its own README says it performs very little validation on your CSP. Before shipping, verify two things: that your Content-Security-Policy does not block scripts or styles your app actually loads, and whether upgrade-insecure-requests is breaking local HTTP development in Safari.
Frequently asked questions
How do I use Helmet in an Express app?
Import the default export and pass it to app.use, as in the README's quick start: app.use(helmet()). That single call sets the 13 default HTTP response headers; you can then disable or configure individual headers through the options object.
How much configuration does Helmet need before it is safe to deploy?
The defaults are a starting point, not a finished policy. The README states that Content-Security-Policy is powerful but likely requires configuration for your specific app, and that Helmet performs very little validation on your CSP.
Can I turn off a single Helmet header instead of all of them?
Yes. Each header can be disabled by passing false for that key, for example helmet({ contentSecurityPolicy: false, xDownloadOptions: false }). Individual middlewares can also be mounted on their own, such as app.use(helmet.contentSecurityPolicy()).
Does Helmet work with the Content-Security-Policy-Report-Only header?
Yes. Passing reportOnly: true inside the contentSecurityPolicy options sets the Content-Security-Policy-Report-Only header instead of the enforcing one, which the README shows as a way to test a policy before applying it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/helmetjs-helmet)