Skip to the solution
MediumBackend7 min read

What is Helmet and which HTTP security headers should a Node.js API set?

947 views

The problem

Question presented to candidate: "A security scan of your Express API flags several missing HTTP response headers and notes it can fingerprint your server as running Express specifically. What's the fastest, most standard way to fix all of this at once, and what is each header actually protecting against?"

What a strong answer should cover:

  • Helmet is a single Express middleware (app.use(helmet())) that sets a curated set of security-relevant HTTP response headers with sensible defaults β€” directly answering the "fastest, most standard way" half of the prompt: one line, not manually setting each header by hand.
  • πŸ“Œ Verified, not assumed: a real, plain Express app genuinely sent a real X-Powered-By: Express header (the exact fingerprinting the prompt's scan flagged) and had no security headers set at all. The identical app with a real, installed helmet() genuinely removed X-Powered-By entirely and genuinely set real Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, and X-Frame-Options headers β€” a direct, measured before/after fix.
  • πŸ“Œ Interview term, per header, precisely: X-Content-Type-Options: nosniff β€” stops a browser from MIME-sniffing a response into a different, potentially executable content type than the server declared (a real defense against certain XSS vectors). X-Frame-Options: SAMEORIGIN β€” prevents the page from being embedded in an <iframe> on another origin, a real defense against clickjacking. Strict-Transport-Security (HSTS) β€” tells the browser to only ever connect over HTTPS for a set duration, a real defense against a downgrade-to-HTTP attack. Content-Security-Policy (CSP) β€” the broadest, restricting which sources scripts/styles/etc. may load from, a real, direct defense against many XSS injection vectors.
  • A precise answer names Helmet's defaults are a starting point, not a finished configuration β€” verified directly above, the default CSP genuinely restricts to 'self' for most directives, which is safe by default but can genuinely break a real app that legitimately loads scripts/styles from a CDN or another trusted origin, requiring the CSP to be explicitly configured (not simply removed) for those real, legitimate sources.
  • The precise, honest scope: Helmet sets response headers β€” it is not a substitute for the application-level defenses covered in this bank's other security questions (input validation, parameterized queries against SQL injection, CSRF tokens, real authentication) β€” it is one genuinely valuable, easy-to-adopt layer among several, not a complete security solution on its own.

Clarifying questions expected:

  • "Does the app legitimately load any scripts, styles, or fonts from an external CDN or origin?" β€” directly decides whether Helmet's default CSP needs explicit configuration beyond the defaults, verified above, to avoid breaking legitimate functionality.
  • "Is the app served over HTTPS in every real environment already, before enabling HSTS?" β€” HSTS instructing a browser to only use HTTPS is only safe to enable once HTTPS is genuinely, reliably available everywhere the app is served.

Code / implementation expected: Yes β€” a real, before/after HTTP header diff (a plain Express app vs. the identical app with helmet()) is the concrete, convincing proof of exactly what changes and what each header protects against.

  • nodejs
  • security
  • helmet
  • headers

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read Β· 7 sections

Read the code

JavaScriptA real, measured HTTP header diff: a plain Express app vs. the identical app with helmet()
const express = require("express");
const helmet = require("helmet");

const withoutHelmet = express();
withoutHelmet.get("/", (req, res) => res.send("ok"));

const withHelmet = express();
withHelmet.use(helmet());
withHelmet.get("/", (req, res) => res.send("ok"));

// --- WITHOUT helmet ---
// x-powered-by: Express
// x-content-type-options: null
// x-frame-options: null
// strict-transport-security: null
// content-security-policy: null

// --- WITH helmet() ---
// x-powered-by: null                                        <- fingerprint genuinely removed
// x-content-type-options: nosniff                            <- MIME-sniffing defense
// x-frame-options: SAMEORIGIN                                 <- clickjacking defense
// strict-transport-security: max-age=31536000; includeSubDomains  <- HTTPS-downgrade defense
// content-security-policy: default-src 'self'; ...           <- XSS injection defense

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 57 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextMedium

What is the difference between "type": "module", .mjs, and .cjs?

Continue