Closed glensc closed 1 year ago
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.
res.set()
is alias to .header()
:
so perhaps:
res.header(headers);
Sure, that works too!
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: