helmet vs express-csp-header
HTTP Security Headers in Express.js Applications
helmetexpress-csp-headerSimilar Packages:

HTTP Security Headers in Express.js Applications

express-csp-header and helmet are both npm packages designed to help Express.js applications set HTTP security headers that protect against common web vulnerabilities. helmet is a comprehensive middleware that sets multiple security-related headers by default, including Content Security Policy (CSP), XSS Protection, HSTS, and more. express-csp-header is a focused utility that only handles Content Security Policy headers, offering a programmatic way to define CSP directives without writing raw header strings.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
helmet6,893,36910,646104 kB5a year agoMIT
express-csp-header24,5132457.4 kB12 days agoWTFPL

Securing Express Apps: express-csp-header vs helmet

When building web applications with Express.js, setting proper HTTP security headers is non-negotiable. Two common tools for this are express-csp-header and helmet. While both can configure Content Security Policy (CSP), they differ significantly in scope, maintenance, and philosophy. Let’s break down how they work in practice.

🛡️ Scope: Focused Tool vs Full Security Suite

express-csp-header does one thing: generate and set the Content-Security-Policy header. It provides a clean API to define CSP directives using JavaScript objects instead of raw strings.

// express-csp-header: Only sets CSP
import express from 'express';
import csp from 'express-csp-header';

const app = express();

app.use(csp({
  policies: {
    'default-src': [csp.SELF],
    'script-src': [csp.SELF, 'https://trusted.cdn.com'],
    'style-src': [csp.SELF, 'https://fonts.googleapis.com']
  }
}));

helmet sets multiple security headers out of the box, including CSP, but also:

  • X-Content-Type-Options
  • X-Frame-Options
  • X-XSS-Protection
  • Strict-Transport-Security
  • Referrer-Policy
  • And more

You can enable all defaults or pick specific ones:

// helmet: Sets many headers, including CSP
import express from 'express';
import helmet from 'helmet';

const app = express();

// Enable all recommended headers
app.use(helmet());

// Or just CSP with custom config
app.use(helmet.contentSecurityPolicy({
  directives: {
    'default-src': ["'self'"],
    'script-src': ["'self'", 'https://trusted.cdn.com'],
    'style-src': ["'self'", 'https://fonts.googleapis.com']
  }
}));

🔧 Configuration Style: Object-Based vs String-Based Directives

Both libraries let you define CSP using JavaScript objects, but their syntax differs slightly.

express-csp-header uses constants like csp.SELF to represent 'self':

// express-csp-header
app.use(csp({
  policies: {
    'img-src': [csp.SELF, 'data:', 'https://images.example.com']
  }
}));
// Produces: img-src 'self' data: https://images.example.com

helmet expects you to write values as strings, including quotes for special keywords:

// helmet
app.use(helmet.contentSecurityPolicy({
  directives: {
    'img-src': ["'self'", 'data:', 'https://images.example.com']
  }
}));
// Produces: img-src 'self' data: https://images.example.com

Note: In helmet, you must include the single quotes around 'self' as a string literal. This is closer to the actual HTTP header format but can be error-prone.

🔄 Maintenance and Ecosystem Fit

As of recent checks, helmet is actively maintained, widely adopted, and aligns with current OWASP security recommendations. It’s updated regularly to reflect evolving best practices (e.g., deprecating X-XSS-Protection in favor of modern CSP).

express-csp-header, while functional, shows signs of reduced maintenance. Its GitHub repository has not seen significant updates in years, and it doesn’t handle newer CSP features as gracefully. For new projects, this raises sustainability concerns.

🎯 When to Use Which?

Use helmet if:

  • You’re building a production application and want defense-in-depth.
  • You’d rather not manually configure each security header.
  • You value community support and up-to-date defaults.

Example: A SaaS dashboard serving sensitive user data.

// Recommended setup for most apps
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      'default-src': ["'self'"],
      'script-src': ["'self'", "'unsafe-inline'"], // adjust as needed
      'connect-src': ["'self'", 'https://api.yourservice.com']
    }
  }
}));

Consider express-csp-header only if:

  • You’re working in a constrained environment where you only need CSP.
  • You already manage other headers via reverse proxy (e.g., Nginx) or CDN.
  • You’ve verified compatibility with your Express version and accept the maintenance risk.

Example: A microservice that only serves JSON and relies on an edge gateway for other headers.

// Minimal CSP-only setup
app.use(csp({
  policies: {
    'default-src': [csp.NONE],
    'frame-ancestors': [csp.NONE]
  }
}));

⚠️ Critical Note on CSP Reporting

Both libraries support CSP violation reporting, but implementation differs.

helmet includes report-uri and report-to in its CSP directive options:

app.use(helmet.contentSecurityPolicy({
  directives: {
    'default-src': ["'self'"],
    'report-uri': ['/csp-violation-report-endpoint']
  }
}));

express-csp-header requires you to add the report-uri as a string in the policy array:

app.use(csp({
  policies: {
    'default-src': [csp.SELF],
    'report-uri': ['/csp-violation-report-endpoint']
  }
}));

However, note that report-uri is deprecated in favor of report-to, which requires additional header configuration. helmet makes this easier by allowing full control over all directives.

✅ Final Recommendation

For nearly all Express.js applications, helmet is the better choice. It’s secure by default, actively maintained, and reduces the chance of missing critical headers. Use express-csp-header only in niche cases where you have full control over your infrastructure and deliberately isolate CSP management — and even then, audit its compatibility carefully.

Remember: Security isn’t just about CSP. Headers like X-Content-Type-Options and Strict-Transport-Security prevent entire classes of attacks. helmet gives you those for free.

How to Choose: helmet vs express-csp-header

  • helmet:

    Choose helmet if you want a well-maintained, batteries-included solution that automatically configures a suite of essential security headers beyond just CSP — including XSS protection, frame guards, and strict transport security. It’s the standard choice for production Express applications where defense-in-depth matters, and it allows you to customize or disable individual headers as needed.

  • express-csp-header:

    Choose express-csp-header if you need fine-grained, programmatic control over only the Content Security Policy header and prefer to manage other security headers manually or through other means. It’s suitable for projects where CSP is the primary concern and you want to avoid pulling in a broader security toolkit. However, note that this package hasn’t seen active maintenance recently, so verify compatibility with your Express version before adoption.

README for helmet

Helmet

Help secure Express apps by setting HTTP response headers.

import helmet from "helmet";

const app = express();

app.use(helmet());

Helmet sets the following headers by default:

Each header can be configured. For example, here's how you configure the Content-Security-Policy header:

// Configure the Content-Security-Policy header.
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        "script-src": ["'self'", "example.com"],
      },
    },
  }),
);

Headers can also be disabled. For example, here's how you disable the Content-Security-Policy and X-Download-Options headers:

// Disable the Content-Security-Policy and X-Download-Options headers
app.use(
  helmet({
    contentSecurityPolicy: false,
    xDownloadOptions: false,
  }),
);

Reference

Content-Security-Policy

Default:

Content-Security-Policy: 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';upgrade-insecure-requests

The Content-Security-Policy header mitigates a large number of attacks, such as cross-site scripting. See MDN's introductory article on Content Security Policy.

This header is powerful but likely requires some configuration for your specific app.

To configure this header, pass an object with a nested directives object. Each key is a directive name in camel case (such as defaultSrc) or kebab case (such as default-src). Each value is an array (or other iterable) of strings or functions for that directive. If a function appears in the array, it will be called with the request and response objects.

// Sets all of the defaults, but overrides `script-src`
// and disables the default `style-src`.
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        "script-src": ["'self'", "example.com"],
        "style-src": null,
      },
    },
  }),
);
// Sets the `script-src` directive to
// "'self' 'nonce-e33cc...'"
// (or similar)
app.use((req, res, next) => {
  res.locals.cspNonce = crypto.randomBytes(32).toString("hex");
  next();
});
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],
      },
    },
  }),
);

These directives are merged into a default policy, which you can disable by setting useDefaults to false.

// Sets "Content-Security-Policy: default-src 'self';
// script-src 'self' example.com;object-src 'none';
// upgrade-insecure-requests"
app.use(
  helmet({
    contentSecurityPolicy: {
      useDefaults: false,
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "example.com"],
        objectSrc: ["'none'"],
        upgradeInsecureRequests: [],
      },
    },
  }),
);

You can get the default directives object with helmet.contentSecurityPolicy.getDefaultDirectives(). Here is the default policy (formatted for readability):

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';
upgrade-insecure-requests

The default-src directive can be explicitly disabled by setting its value to helmet.contentSecurityPolicy.dangerouslyDisableDefaultSrc, but this is not recommended.

You can set the Content-Security-Policy-Report-Only instead:

// Sets the Content-Security-Policy-Report-Only header
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        /* ... */
      },
      reportOnly: true,
    },
  }),
);

Helmet performs very little validation on your CSP. You should rely on CSP checkers like CSP Evaluator instead.

To disable the Content-Security-Policy header:

app.use(
  helmet({
    contentSecurityPolicy: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.contentSecurityPolicy()).

Cross-Origin-Embedder-Policy

This header is not set by default.

The Cross-Origin-Embedder-Policy header helps control what resources can be loaded cross-origin. See MDN's article on this header for more.

// Helmet does not set Cross-Origin-Embedder-Policy
// by default.
app.use(helmet());

// Sets "Cross-Origin-Embedder-Policy: require-corp"
app.use(helmet({ crossOriginEmbedderPolicy: true }));

// Sets "Cross-Origin-Embedder-Policy: credentialless"
app.use(helmet({ crossOriginEmbedderPolicy: { policy: "credentialless" } }));

You can use this as standalone middleware with app.use(helmet.crossOriginEmbedderPolicy()).

Cross-Origin-Opener-Policy

Default:

Cross-Origin-Opener-Policy: same-origin

The Cross-Origin-Opener-Policy header helps process-isolate your page. For more, see MDN's article on this header.

// Sets "Cross-Origin-Opener-Policy: same-origin"
app.use(helmet());

// Sets "Cross-Origin-Opener-Policy: same-origin-allow-popups"
app.use(
  helmet({
    crossOriginOpenerPolicy: { policy: "same-origin-allow-popups" },
  }),
);

To disable the Cross-Origin-Opener-Policy header:

app.use(
  helmet({
    crossOriginOpenerPolicy: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.crossOriginOpenerPolicy()).

Cross-Origin-Resource-Policy

Default:

Cross-Origin-Resource-Policy: same-origin

The Cross-Origin-Resource-Policy header blocks others from loading your resources cross-origin in some cases. For more, see "Consider deploying Cross-Origin Resource Policy" and MDN's article on this header.

// Sets "Cross-Origin-Resource-Policy: same-origin"
app.use(helmet());

// Sets "Cross-Origin-Resource-Policy: same-site"
app.use(helmet({ crossOriginResourcePolicy: { policy: "same-site" } }));

To disable the Cross-Origin-Resource-Policy header:

app.use(
  helmet({
    crossOriginResourcePolicy: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.crossOriginResourcePolicy()).

Origin-Agent-Cluster

Default:

Origin-Agent-Cluster: ?1

The Origin-Agent-Cluster header provides a mechanism to allow web applications to isolate their origins from other processes. Read more about it in the spec.

This header takes no options and is set by default.

// Sets "Origin-Agent-Cluster: ?1"
app.use(helmet());

To disable the Origin-Agent-Cluster header:

app.use(
  helmet({
    originAgentCluster: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.originAgentCluster()).

Referrer-Policy

Default:

Referrer-Policy: no-referrer

The Referrer-Policy header which controls what information is set in the Referer request header. See "Referer header: privacy and security concerns" and the header's documentation on MDN for more.

// Sets "Referrer-Policy: no-referrer"
app.use(helmet());

policy is a string or array of strings representing the policy. If passed as an array, it will be joined with commas, which is useful when setting a fallback policy. It defaults to no-referrer.

// Sets "Referrer-Policy: no-referrer"
app.use(
  helmet({
    referrerPolicy: {
      policy: "no-referrer",
    },
  }),
);

// Sets "Referrer-Policy: origin,unsafe-url"
app.use(
  helmet({
    referrerPolicy: {
      policy: ["origin", "unsafe-url"],
    },
  }),
);

To disable the Referrer-Policy header:

app.use(
  helmet({
    referrerPolicy: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.referrerPolicy()).

Strict-Transport-Security

Default:

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

The Strict-Transport-Security header tells browsers to prefer HTTPS instead of insecure HTTP. See the documentation on MDN for more.

// Sets "Strict-Transport-Security: max-age=31536000; includeSubDomains"
app.use(helmet());

maxAge is the number of seconds browsers should remember to prefer HTTPS. If passed a non-integer, the value is rounded down. It defaults to 365 days.

includeSubDomains is a boolean which dictates whether to include the includeSubDomains directive, which makes this policy extend to subdomains. It defaults to true.

preload is a boolean. If true, it adds the preload directive, expressing intent to add your HSTS policy to browsers. See the "Preloading Strict Transport Security" section on MDN for more. It defaults to false.

// Sets "Strict-Transport-Security: max-age=123456; includeSubDomains"
app.use(
  helmet({
    strictTransportSecurity: {
      maxAge: 123456,
    },
  }),
);

// Sets "Strict-Transport-Security: max-age=123456"
app.use(
  helmet({
    strictTransportSecurity: {
      maxAge: 123456,
      includeSubDomains: false,
    },
  }),
);

// Sets "Strict-Transport-Security: max-age=123456; includeSubDomains; preload"
app.use(
  helmet({
    strictTransportSecurity: {
      maxAge: 63072000,
      preload: true,
    },
  }),
);

To disable the Strict-Transport-Security header:

app.use(
  helmet({
    strictTransportSecurity: false,
  }),
);

You may wish to disable this header for local development, as it can make your browser force redirects from http://localhost to https://localhost, which may not be desirable if you develop multiple apps using localhost. See this issue for more discussion.

You can use this as standalone middleware with app.use(helmet.strictTransportSecurity()).

X-Content-Type-Options

Default:

X-Content-Type-Options: nosniff

The X-Content-Type-Options mitigates MIME type sniffing which can cause security issues. See documentation for this header on MDN for more.

This header takes no options and is set by default.

// Sets "X-Content-Type-Options: nosniff"
app.use(helmet());

To disable the X-Content-Type-Options header:

app.use(
  helmet({
    xContentTypeOptions: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.xContentTypeOptions()).

X-DNS-Prefetch-Control

Default:

X-DNS-Prefetch-Control: off

The X-DNS-Prefetch-Control header helps control DNS prefetching, which can improve user privacy at the expense of performance. See documentation on MDN for more.

// Sets "X-DNS-Prefetch-Control: off"
app.use(helmet());

allow is a boolean dictating whether to enable DNS prefetching. It defaults to false.

Examples:

// Sets "X-DNS-Prefetch-Control: off"
app.use(
  helmet({
    xDnsPrefetchControl: { allow: false },
  }),
);

// Sets "X-DNS-Prefetch-Control: on"
app.use(
  helmet({
    xDnsPrefetchControl: { allow: true },
  }),
);

To disable the X-DNS-Prefetch-Control header and use the browser's default value:

app.use(
  helmet({
    xDnsPrefetchControl: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.xDnsPrefetchControl()).

X-Download-Options

Default:

X-Download-Options: noopen

The X-Download-Options header is specific to Internet Explorer 8. It forces potentially-unsafe downloads to be saved, mitigating execution of HTML in your site's context. For more, see this old post on MSDN.

This header takes no options and is set by default.

// Sets "X-Download-Options: noopen"
app.use(helmet());

To disable the X-Download-Options header:

app.use(
  helmet({
    xDownloadOptions: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.xDownloadOptions()).

X-Frame-Options

Default:

X-Frame-Options: SAMEORIGIN

The legacy X-Frame-Options header to help you mitigate clickjacking attacks. This header is superseded by the frame-ancestors Content Security Policy directive but is still useful on old browsers or if no CSP is used. For more, see the documentation on MDN.

// Sets "X-Frame-Options: SAMEORIGIN"
app.use(helmet());

action is a string that specifies which directive to use—either DENY or SAMEORIGIN. (A legacy directive, ALLOW-FROM, is not supported by Helmet. Read more here.) It defaults to SAMEORIGIN.

Examples:

// Sets "X-Frame-Options: DENY"
app.use(
  helmet({
    xFrameOptions: { action: "deny" },
  }),
);

// Sets "X-Frame-Options: SAMEORIGIN"
app.use(
  helmet({
    xFrameOptions: { action: "sameorigin" },
  }),
);

To disable the X-Frame-Options header:

app.use(
  helmet({
    xFrameOptions: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.xFrameOptions()).

X-Permitted-Cross-Domain-Policies

Default:

X-Permitted-Cross-Domain-Policies: none

The X-Permitted-Cross-Domain-Policies header tells some clients (mostly Adobe products) your domain's policy for loading cross-domain content. See the description on OWASP for more.

// Sets "X-Permitted-Cross-Domain-Policies: none"
app.use(helmet());

permittedPolicies is a string that must be "none", "master-only", "by-content-type", or "all". It defaults to "none".

Examples:

// Sets "X-Permitted-Cross-Domain-Policies: none"
app.use(
  helmet({
    xPermittedCrossDomainPolicies: {
      permittedPolicies: "none",
    },
  }),
);

// Sets "X-Permitted-Cross-Domain-Policies: by-content-type"
app.use(
  helmet({
    xPermittedCrossDomainPolicies: {
      permittedPolicies: "by-content-type",
    },
  }),
);

To disable the X-Permitted-Cross-Domain-Policies header:

app.use(
  helmet({
    xPermittedCrossDomainPolicies: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.xPermittedCrossDomainPolicies()).

X-Powered-By

Default: the X-Powered-By header, if present, is removed.

Helmet removes the X-Powered-By header, which is set by default in Express and some other frameworks. Removing the header offers very limited security benefits (see this discussion) and is mostly removed to save bandwidth, but may thwart simplistic attackers.

Note: Express has a built-in way to disable the X-Powered-By header, which you may wish to use instead.

The removal of this header takes no options. The header is removed by default.

To disable this behavior:

// Not required, but recommended for Express users:
app.disable("x-powered-by");

// Ask Helmet to ignore the X-Powered-By header.
app.use(
  helmet({
    xPoweredBy: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.xPoweredBy()).

X-XSS-Protection

Default:

X-XSS-Protection: 0

Helmet disables browsers' buggy cross-site scripting filter by setting the legacy X-XSS-Protection header to 0. See discussion about disabling the header here and documentation on MDN.

This header takes no options and is set by default.

To disable the X-XSS-Protection header:

// This is not recommended.
app.use(
  helmet({
    xXssProtection: false,
  }),
);

You can use this as standalone middleware with app.use(helmet.xXssProtection()).