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

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...

Related

How to prevent ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep?

I am attempting to access my movie API that returns data including an image of a movie poster through a React application. This image is being requested from an external website. Each time I make a request to my \movies endpoint, the image is blocked and I get the following message in the console
net::ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep 200
When looking at the request in the Network tab, I get the following message saying to enable a Cross-Origin Resource Policy
Because your site has the Cross-Origin Embedder Policy (COEP) enabled, each resource must specify a suitable Cross-Origin Resource Policy (CORP). This behavior prevents a document from loading cross-origin resources which don’t explicitly grant permission to be loaded.
To solve this, add the following to the resource’s response header:
Cross-Origin-Resource-Policy: same-site if the resource and your site are served from the same site.
Cross-Origin-Resource-Policy: cross-origin if the resource is served from another location than your website. ⚠️If you set this header, any website can embed this resource.
I am using the CORS npm module which had previously been used to solve my issue with an Access-Control-Allow-Origin error. I added some additional middleware to try and add the header as instructed. This is the app.js server with that code
App.js
'use strict';
import express, { json, urlencoded } from 'express';
import morgan from 'morgan';
import mongoose from 'mongoose';
import passport from 'passport';
import cors from 'cors';
import dotenv from 'dotenv';
import auth from './routes/auth.js';
import routes from './routes/routes.js';
dotenv.config();
const app = express();
mongoose
.connect(process.env.CONNECTION_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(res => console.log('DB Connected!'))
.catch(err => console.log(err, err.message));
app.use(cors())
app.use((req, res, next) => {
res.header("Cross-Origin-Resource-Policy", "cross-origin")
next()
})
app.use(passport.initialize());
app.use(json());
app.use(urlencoded({ extended: true }));
app.use(express.static(`public`));
app.use(morgan('common'));
auth(app);
import './authentication/passport.js';
routes(app)
app.use((req, res, err, next) => {
if (err) {
console.error(err.stack);
res.status(500).send('Something broke!');
}
next();
});
const port = process.env.PORT || 3000;
app.listen(port, '0.0.0.0', () => console.log(`Listening on Port ${port}`));
After doing this, the console throws the same error and the Cross-Origin Resource Policy still is not set. Is there something wrong with my approach or the way that I have my file structured?
You have COEP enabled in the client:
Cross-Origin-Embedder-Policy: require-corp
This is a great security feature that means:
COEP: Everything (data, images etc) on this website is mine, or I fetch from it from other websites using CORS. (There can be a third way, that is data being authorized by cookies, http-auth, etc... which is not in our discussion, so don't bother here.)
So, you have two options. The first one is to disable COEP, but I assume that you don't want to do that. So, the other option is to use CORS for everything external. For example, when you fetch something, use:
fetch('https://externalwebsite.com/image.jpg',{mode:'cors'})
or, to embed an external image in the HTML, use crossorigin
<img crossorigin="anonymous" src="https://externalwebsite.com/image.jpg">
Note that crossorigin attribute in <img> means CORS. If it is missing, it means "no-cors", which is the default. Be aware though: When you use JavaScript's fetch, the default is {mode:'cors'}, i.e. the opposite!
Now, if you try to do that (use CORS, as you should), the browser will throw another error:
Access [...] has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
That means... exactly that! That the external server has to send the header:
Access-Control-Allow-Origin: *
That setting means that every website can use the server's resources (API in your case), as long as it does not use/send/receive cookies in the request (because... security). The way to implement this in your express server is to set:
res.header('Access-Control-Allow-Origin', '*');
Every server that intends to serve things to other websites, must have this ACAO header. (You can place your other website instead of "*" if you want only that website to access your API.)
Note/Summary:
If the external server has this ACAO header, you can fetch things using CORS/crossorigin. If it does not have ACAO header, you can fetch things with no-cors / without crossorigin. But with COEP enabled in your website, you can only fetch with CORS/crossorigin, so the external server has to have an ACAO.
Now,
As for the Cross-Origin-Resource-Policy that your server has, have in mind that (https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)):
The policy is only effective for no-cors requests
During a cross-origin resource policy check, if the header is set, the browser will deny no-cors requests issued from a different origin/site.
This means that, since you make only CORS requests to that server, this header doesn't do anything (in your case). So the server can set it to "same-site"/"same-origin" for security reasons that are beyond this topic.

Content Security Policy issues with new form

What is the easiest way for me to set the content security policy for a new form I am using? I have a frontend to backend form working for my registration but this form is a lot more complicated and is throwing me the errors below. I am aware this is to do with webpack and I have tried inserting code into my publ;ic index.html file which just stopped the page from rendering.
Would this have anything to do with my CORS settings in the backend? I have the below code which references headers and the I have been getting error messages about setting headings in other forms that I am having the same issues with.
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-headers',
'Origin, X-Requested-With, Content-Type, Accept',
);
next();
});
The errors described have nothing to do with your CORS settings in the backend. They are related to CSP (Content-Security-Policy) header, which you, it would seem, do not use. But for nonexistent pages node JS is published CSP header on its own.
Pay attention to status code 404 Not Found. If you do not handle this kinds of errors, nodeJS uses own finalhandler to carry out of those by default.
Last versions of this finalhandler publish the CSP default-src 'none'; all the nitty-gritty is here.
Looks like you do not serve routes to root / folder in you server config, therefore /favicon.ico and similar urls are not found -> finalhandler publishes default-src 'none'; -> you observe CSP violation in the browser console (along with the 404 not found messages).
You have to add into server.js something like that:
app.use(express.static('client/public'));
app.get("/favicon.ico", (req, res) => {
res.sendFile(path.resolve(__dirname, "/favicon.ico"));
});
The above will solve the issue with "/favicon.ico Not Found", for other "non existent" Urls you need to add routes too.

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.

Added a Content-Security-Policy to Node.js/Express.js server code, then removed it, but Google Chrome is still blocking content

NOTICE: Revised issue down below at the EDIT section.
I've been learning about Content-Security-Policies in an attempt to learn various security concepts, because I am building a website from scratch. I realize it may not be a huge deal since I won't be receiving any user input, however I want to learn these concepts and begin to apply them to keep my site from being vulnerable to things like Javascript injection.
In my server.js file, I serve my pages via Express in the following code:
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "/views/home.html"));
});
To add the Content-Security-Policy (CSP) to my server code, I added the following code before the above code, and before using Express to serve static files:
//the following code is called right after the creation of the express server
app.use(function(req, res, next) {
res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self' https://apis.google.com");
return next();
});
//static css/js/img files served here:
app.use('/public', express.static(__dirname + '/public'));
//then the app.get method I defined above
I restarted my node server, opened up chrome, went to http://localhost:8080 where I have my node website listening, and as expected, my home page wasn't loading the various external scripts, CDNs, and external styles I have in my project.
After seeing that the CSP worked, I decided to remove the entire app.use function that set the CSP so I could create my list of directives so I could add the CSP later.
I removed that code block, saved, restarted the node server, and when I opened up Google Chrome, I was still getting notices under the security tab in the Chrome Dev Tools saying that all of my external content was being blocked due a violation of "default-src 'self'" or the "script-src 'self'" directives - again, this is after I removed the CSP code above from my server file. However, when I opened up my application in Safari, everything loaded fine. Why is Chrome still blocking content even though I removed the code to set a CSP in the HTTP headers in my server code?
EDIT: I ended up reseting my Chrome settings which fixed the issue. However, I'm unsure of which setting to go to in Chrome now, rather than resetting all settings. Also obviously the CSP only applied to the home page in that instance, I would need to set the CSP within each html page's GET method, yes?

No 'Access-Control-Allow-Origin'

I am requesting an image from Cloudfront CDN. each time I make a request from the client I am getting this error:
Access to Image at https://cdn.mywebsite/image.png from origin
http://localhost:5000 has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin http://localhost:5000 is therefore not allowed
access. The response had HTTP status code 403.
I'm using express for the server and have added the following to allow Access but still no luck..
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', "*");
res.setHeader('Access-Control-Allow-Methods', 'GET');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next();
})
Any Advice would be much appreciated!
==========================================================================
Update
Hi #jfriend00
So what my goal is to serve protected content over CF CDN.
For this I am sending signed Cookies to the client using the following module below.
var cf = require('aws-cloudfront-sign')
var options = {keypairId: 'keypairId', privateKeyPath: '/foo/bar'}
var signedCookies = cf.getSignedCookies('https://cdn.mywebsite.com/*', options);
for(var cookieId in signedCookies) {
res.cookie(cookieId, signedCookies[cookieId]);
}
Then I am simply making a request from the client to the cdn to fetch the image with:
<img src="https://cdn.mywebsite.com/image.png" crossorigin="anonymous" alt="test picture">
At this point the Access-Control-Allow-Origin error is displayed in the console.
Note:
var signedUrl = cf.getSignedUrl('https://cdn.mywebsite.com/image.png', options)
This signedUrl works when directly accessing it but not if I make the request from localhost or the website it self.
CORS headers have to be on the server that is serving the resource. So, if the resource that you are getting the CORS error on is https://cdn.mywebsite/image.png, then that's the host that has to allows CORS access. You can't fix that by allowing CORS on localhost.
FYI, it seems odd that you are getting a CORS error when accessing an image. If you use <img> tag for the access, then the <img> tag will not be subject to same origin restrictions. The same origin restrictions apply to Ajax calls made from browser Javascript.
I also not that you appear to be mixing http and https in the same page which can also cause issues.
Are you trying to download the image with Ajax? Please show your client code that is causing this error and explain what you are trying to accomplish and perhaps we can offer a different solution.

Resources