helmetjs / helmet

Help secure Express apps with various HTTP headers

Home Page:https://helmetjs.github.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RFE: Static pre-computed headers

glensc opened this issue · comments

I have an express app, which does 301 redirects or 404 responses. so the headers that helmet() adds are always static.

Perhaps it could be possible to pre-compute the headers and in middleware just apply the pre-computed headers. as there's no real need to validate the options and apply parsing logic for each request that comes through this middleware.

since this project also removes some headers (x-powered-by) the pre-computed results could just be two variables:

  • const headersToSet = { };
  • const headersToRemove = [];

In my test app the headers diff before and after helmet are:

-X-Powered-By: Express
+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
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Resource-Policy: same-origin
+Origin-Agent-Cluster: ?1
+Referrer-Policy: no-referrer
+Strict-Transport-Security: max-age=15552000; includeSubDomains
+X-Content-Type-Options: nosniff
+X-DNS-Prefetch-Control: off
+X-Download-Options: noopen
+X-Frame-Options: SAMEORIGIN
+X-Permitted-Cross-Domain-Policies: none
+X-XSS-Protection: 0

I've created such middleware:

import { IncomingMessage, ServerResponse } from "http";

import { Express } from "express";

type T = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;

export const helmet = (app: Express): T => {
  app.disable("x-powered-by");

  const headers = {
    "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",
    ].join(";"),
    "Cross-Origin-Opener-Policy": "same-origin",
    "Cross-Origin-Resource-Policy": "same-origin",
    "Origin-Agent-Cluster": "?1",
    "Referrer-Policy": "no-referrer",
    "Strict-Transport-Security": "max-age=15552000; includeSubDomains",
    "X-Content-Type-Options": "nosniff",
    "X-DNS-Prefetch-Control": "off",
    "X-Download-Options": "noopen",
    "X-Frame-Options": "SAMEORIGIN",
    "X-Permitted-Cross-Domain-Policies": "none",
    "X-XSS-Protection": "0",
  };

  return (req: IncomingMessage, res: ServerResponse, next: () => void): void => {
    for (const [name, value] of Object.entries(headers)) {
      res.setHeader(name, value);
    }

    next();
  };
};

can be used as:

app.use(helmet(app));

I built a simple benchmarking app to compare these two approaches.

The Helmet-based approach looks like this:

app.use(helmet());

The precomputed approach looks like this:

const HEADERS = { /* ... */ };

app.disable("x-powered-by");

app.use((req, res, next) => {
  res.set(HEADERS);
  next();
});

The precomputed version is faster. On my machine, I could send 289K requests per minute, or about 4.8K requests per second.

Compare that to the Helmet-based version, which did 268K requests per minute, or about 4.5K requests per second. That's about 7% slower.

I don't think that's significant enough to add new things to Helmet, but I'll think more about this and get back to you.

After some consideration, I don't think I plan to make any code changes to Helmet.

However, I do think this is worth documenting. I added a page to the documentation showing how to set these headers yourself. (I also published it to my blog, hoping for slightly greater visibility.)

Thanks for opening this issue.

Sure, that works too!