How to use helmet? - node.js

I want to use helmet package to do the following:
set X-Frame-Options = SAMEORIGIN
Disable the X-Powered-By header.
What should Content-Security-Policy be and how do I set it using helmet?
How about Access-Control-Allow-Origin?
I also want to use it to enable best practices for security. What do you suggest? What are these best practices and how do I set them?

Maintainer of Helmet here.
First of all, Helmet is not enough to make your Express apps secure. That requires understanding best practices, vulnerabilities, and much more. Helmet only tries to tackle a narrow piece of that puzzle: setting various HTTP response headers related to security.
For example, by default, Helmet will set a header called X-Frame-Options to SAMEORIGIN. This header doesn't magically make your app secure, but it can help mitigate clickjacking attacks. It will also disable a header called X-Powered-By by default, which
Here's how you use Helmet with all of its default settings:
app.use(helmet());
If you want to, say, override the default value for X-Frame-Options, you could do something like this:
// Sets all of the defaults except for X-Frame-Options,
// which is set to "DENY" instead of its default
app.use(helmet({
frameguard: { action: 'DENY' },
}));
And if you want Helmet to ignore the X-Frame-Options header completely:
// Sets all of the defaults except for X-Frame-Options
app.use(helmet({
frameguard: false,
}));
By default, Helmet is responsible for 11 headers, including the two mentioned above.
Helmet's most important, and most difficult to configure, header is Content-Security-Policy. It's not worth describing in depth here; I recommend reading MDN's introductory article.
Helmet can help you set the Content-Security-Policy header, which you can read more about on Helmet's docs. Here's a simple example:
app.use(
helmet.contentSecurityPolicy({
directives: {
...helmet.contentSecurityPolicy.getDefaultDirectives(),
"script-src": ["'self'", "example.com"],
},
})
);
You also asked about Access-Control-Allow-Origin. This is part of something called Cross-Origin Resource Sharing, which Helmet does not touch.

You can write something like this:
app.use(helmet({
frameguard: false // for SAMEORIGIN
}));
app.disable('x-powered-by'); // for disable the X-Powered-By header.

Related

Helmet middleware disable drop down from working and create an error in the console

Why when I add the helmet middleware to my app, the dropdown menus stops working and I get the following error in the console:
Refused to load the script 'https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/js/bootstrap.bundle.min.js' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
This is how I added helmet to my app:
const helmet = require('helmet');
...
app.use(helmet())
Helmet maintainer here.
This is happening because of something called Content Security Policy, which Helmet sets by default. To solve your problem, you will need to configure Helmet's CSP.
MDN has a good documentation about CSP which I would recommend reading for background. After that, take a look at Helmet's README to see how to configure its CSP component.
To give some help specific to this question, let's take a look at one error you're seeing:
Refused to load the script 'https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/js/bootstrap.bundle.min.js' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
This error is telling you that the script-src directive of your CSP does not allow JavaScript to load from cdn.jsdelivr.net, and so it was blocked.
There are several ways to fix this:
Update your CSP to allow JavaScript to load from this domain. You'd do something like this:
app.use(
helmet({
contentSecurityPolicy: {
useDefaults: true,
directives: {
"script-src": ["'self'", "cdn.jsdelivr.net"],
},
},
})
);
Refactor your app to avoid loading JavaScript from that domain. Probably not possible, but it is an available solution.
Disable CSP. This is the most dangerous option so I don't recommend it.
app.use(
helmet({
contentSecurityPolicy: false,
})
);
In summary: to solve your problem, you will need to tell Helmet to configure your CSP.

Ressources not loaded after setting CSP and CORP headers using Helmet

Trying to make the web-app safer and force myself to control better future additions (JS and CSS assets on different CDNs), I'm running Helmet plugin in my Fastify (same as Express) web-app.
If I deactivate all Helmet controls like the following:
fastify.register(helmet, false) all works fine and all resources are loaded on client.
Then I tried to play (until exhaustion) with different configurations, nothing is working. The config and the browser error as the following:
{
// contentSecurityPolicy: false,
crossOriginResourcePolicy: { policy: 'same-site'},
contentSecurityPolicy: {
directives: {
...require("fastify-helmet").contentSecurityPolicy.getDefaultDirectives(),
"default-src": ["'self'"],
"style-src": ["'self'", "'unsafe-inline'", 'unpkg.com', 'cdn.jsdelivr.net',
'fonts.googleapis.com', 'use.fontawesome.com'],
"script-src": ["'self'", 'unpkg.com', "cdn.jsdelivr.net", "'unsafe-inline'"],
"img-src": ["'self'", "'data'", "*.tile.osm.org"],
"font-src": ["'self'", 'fonts.googleapis.com', 'fonts.gstatic.com', 'use.fontawesome.com']
},
},
};
Even setting
{ contentSecurityPolicy: false, crossOriginResourcePolicy: { policy: 'same-site'} }
with other variations of policy: same-origin, cross-origin none seems to work.
As you can see, I'm running on LOCALHOST too and I didn't test elsewhere.
tl;dr: disable the Cross-Origin-Embedder-Policy header, enabled by default in Helmet v5.
{
crossOriginEmbedderPolicy: false,
// ...
}
Helmet maintainer here.
Helmet sets the the Cross-Origin-Embedder-Policy HTTP response header to require-corp.
Setting this header means that loading cross-origin resources (like an image from another resource) is trickier. For example, loading a cross-origin like this...
<img alt="My picture" src="https://example.com/image.png">
...won't work unless example.com explicitly allows it, by setting some response headers of its own. Your browser will try to load example.com/image.png, and if it's not explicitly allowed, your browser will drop the response.
To fix this, you can prevent Helmet from setting the Cross-Origin-Embedder-Policy header, like this:
app.use(
helmet({
crossOriginEmbedderPolicy: false,
// ...
})
);
I made a small sample app you can use to play around with this.

How to configure Content-Policy-Security header on ExpressJs app?

I'm want to create a link in my website who download a PDF file but I get a white window with:
Cannot GET /file
and a Content Security Policy (CSP) error:
Content Security Policy: The page settings prevented a resource from loading at inline ("default-src").
I use ExpressJs and Nginx for the back-end.
I've tried to set CSP default-src header like this in my app.js file:
//Headers setup
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy',"default-src 'self' https://www.mydomain.fr");
next();
});
i've also tried to add this in my .conf file in Nginx:
add_header Content-Security-Policy "default-src 'self';" always;
It is still not working. Do you have any idea what is wrong?
My router:
const express = require('express');
const router = express.Router();
const fileController = require('../controllers/file');
router.get('/', fileController.getFile);
module.exports = router;
My controller:
const path = require('path');
exports.getFile = (req, res, next) => {
res.set({'Content-Type':'application/pdf'});
res.set({'Content-Disposition':'attachment, filename=file.pdf'});
const filePath = path.join(__dirname, '/public/documents/file.pdf');
res.download(filePath, 'file.pdf', (e) => {res.status(404).json({e: e})});
}
I guess your resource is embedded inline (see the csp error).
To allow that use unsafe-inline:
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy',"default-src 'self' 'unsafe-inline'");
next();
});
SabSab43, you have been given the correct leads in the comments and answers, but you shouldn't recklessly publish the CSP. You can publish CSP in several ways:
add_header in Nginx
HTTP header via res.setHeader()
specialized Helmet package
using <meta http-equiv='Content-Security-Policy' content="...CSP rules here..."> in the HTML code.
But if you do use all of above at the same time, you'll publish a several different CSPs and as result more restrictive one will acts.
Therefore remove all CSP you added and lets go step by step:
Do check the jonrsharpe's remark abt Helmet. The fact is that if you have Helmet 4 connected to Express, it issues the restrictive default-src 'self' policy by default.
Therefore after removing all your CSPs, check do you have CSP header in browser, tutorial is here. If CSP header is presents - some middleware (like Helmet) does publish it. You need to find it and modify the policy in it, or switch it off and use your res.setHeader(...)
If there is no header (and there is no meta tag either), publish the CSP with any of the methods, and add 'unsafe-inline' to it, as kmgt said. You need to have policy:
"default-src 'self' 'unsafe-inline' https://www.mydomain.fr"
to get rid of "The page settings prevented a resource from loading at inline ("default-src")"
Note that 'prevented a resource from loading at inline' can be about not only inline <script>, but about inline <style> too. But since you use `default-src', it covers both.
PS: Chrome's console more verbose than Firefox's one. If you fail to fix this issue, pls show the message about the blocking from the Сhrome's console - it will show the rules that block.
I've deleted all my CSP Rules (I don't use Helmet or any CSP dependencies) in my app and Nginx .conf file and when I look in browsers developers tools I see again the CSP Rule.
In chrome:
capture
In Firefox:
capture
therefore, I've tried to put this code:
app.use((req, res, next) => {
res.set({"Content-Security-Policy":"default-src 'self' 'unsafe-inline' https://www.myDomain.fr"});
next();
});
I've the same result than without the CSP Rules...
In Chrome I juste have a 404 error:
GET https://www.myDomain.fr/file 404 (Not found)
In Firefox
GET https://www.myDomain.fr/file [HTTP/1.1 404 Not found 96ms]
Content Security Policy: The page settings prevented a resource from loading at inline ("default-src").
The 404 is probably caused by the CSP Error...

Allowing S3 images with npm helmet

I'm using npm helmet to secure my express app, but I want to allow images from my S3 bucket to render. I get this error when I use helment Refused to load the image '<URL>' because it violates the following Content Security Policy directive: "img-src 'self' data:". which makes sense, I am violating the CSP that helmet implements.
The only thing I've been able to find in their docs is:
app.use(
helmet({
contentSecurityPolicy: false,
})
);
which does allow my images to render, but I still want the CSP that helmet provides. I just need to essentially whitelist my S3 link but I cant find anything in their docs on this topic
Maintainer of Helmet here.
Helmet sets several security-related headers by default, including one called Content-Security-Policy. Content Security Policy, or CSP, is effectively an allowlist of what your page is allowed to load and do.
You're seeing an error that indicates that your CSP doesn't allow images to be loaded from anywhere other than your domain ('self') or data URIs (data:). You could fix this with something like:
app.use(
helmet({
contentSecurityPolicy: {
directives: {
...helmet.contentSecurityPolicy.getDefaultDirectives(),
"img-src": ["'self'", "s3.amazonaws.com"],
},
},
})
);
For more on CSP, check out this introduction on MDN.
This is something that I've been thinking about making clearer in Helmet's docs, so thanks for asking this question.

How to Enable CORS for Credentials and Preflight in Node/Express

I know, there are a billion CORS questions on here. I have gone through dozens. I'm still not sure what the issue is, and I think I understand CORS decently well, but obviously I'm missing something here.
I'm trying to get my (Node/Express) API running on a real URL (i.e. deployed, not using origin: "*"), and tell the browser to actually fulfill requests from my GUI's URL.
For some routes, I need authentication, so I need to add credentials in there.
This is a pretty standard use case, but maybe something changed with the API since I last did this or maybe I just forgot, but regardless I can't seem to get this working and I'm not sure what I'm missing.
There seem to be several ways to go about enabling CORS for all requests in my Express app. I'm currently using the cors middleware with its options object.
Specifically, here's the code I'm using.
const app: express.Application = express();
const corsOptions = {
credentials: true,
origin: 'https://my-gui.example.com',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
preflightContinue: true,
}
app.use(cors(corsOptions));
What am I doing wrong? When I make a fetch request from my browser, I receive the below error.
Access to fetch at 'https://my-api.example.com/graphql' from origin 'https://my-gui.example.com'
has been blocked by CORS policy: Response to preflight request doesn't pass access control
check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an
opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource
with CORS disabled.
If I add app.options('*', cors(corsOptions)); before the app.use call (why I would need this when I specified OPTIONS in the method list, I don't know), I get a different error (which leads me to believe I do need this for some reason).
Access to fetch at 'https://my-api.example.com/graphql' from origin 'https://my-gui.example.com'
has been blocked by CORS policy: Response to preflight request doesn't pass access control
check: It does not have HTTP ok status.
I think you are trying to access this website https://my-api.example.com/graphql but the shared snip looks like you gave an access only to https://my-gui.example.com and this is different from above one.
So my answer is please change your snippet like below,
const corsOptions = {
credentials: true,
origin: "*",
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
preflightContinue: true,
}
(or) try
const corsOptions = {
credentials: true,
origin: ['https://my-gui.example.com','https://my-api.example.com/graphql'],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
preflightContinue: true,
}

Resources