I am trying to implement content security policies (CSP) in a node server, and am having trouble setting up socket.io. It looks like I am setting up the connectSrc
incorrectly in the code below. Could someone suggest the correct way to set up helmet so that web socket connections are allowed by the browser? Thanks in advance!
I am using helmet module for generation of CSP; the following is the code that sets up CSP:
securitySetup = function(app) {
var connectSources, helmet, scriptSources, styleSources;
helmet = require("helmet");
app.use(helmet());
app.use(helmet.hidePoweredBy());
app.use(helmet.noSniff());
app.use(helmet.crossdomain());
scriptSources = ["'self'", "'unsafe-inline'", "'unsafe-eval'", "ajax.googleapis.com"];
styleSources = ["'self'", "'unsafe-inline'", "ajax.googleapis.com"];
connectSources = ["'self'"];
return app.use(helmet.contentSecurityPolicy({
defaultSrc: ["'self'"],
scriptSrc: scriptSources,
styleSrc: styleSources,
connectSrc: connectSources,
reportUri: '/report-violation',
reportOnly: false,
setAllHeaders: false,
safari5: false
}));
};
This works fine for all HTTP/AJAX traffic, but fails for ws:// protocol. I get this error in chrome debugger when socket.io connection is made:
Refused to connect to 'ws://localhost:3000/socket.io/1/websocket/ubexeZHZiAHwAV53WQ7u' because it violates the following Content Security Policy directive: "connect-src 'self'".
Bit late to the party, but thought I'd throw something into the mix on this subject. @Ed4 is quite right, 'self'
will indeed only match the same host, port and scheme. A way around it is to include it in one or more forms:
connect-src: wss:
- to allow a connection to the whole wss
scheme - basically any web socket (probably not ideal)
connect-src: wss://yoursite.domain.com
- to restrict it to a specific endpoint. This is most ideal, but might be restrictive if your subdomain changes between deployments (as ours do)
connect-src: wss://*.domain.com
- can use wildcards in there to tighten security up a bit. This is what we do
TL;DR - use wildcards to make things more specific without just opening yourself up to any web sockets out there/
Refer to this passage from Google devs:
The source list in each directive is flexible. You can specify sources by scheme (data:, https:), or ranging in specificity from hostname-only (example.com, which matches any origin on that host: any scheme, any port) to a fully qualified URI (https://example.com:443, which matches only HTTPS, only example.com, and only port 443). Wildcards are accepted, but only as a scheme, a port, or in the leftmost position of the hostname: ://.example.com:* would match all subdomains of example.com (but not example.com itself), using any scheme, on any port.
https://developers.google.com/web/fundamentals/security/csp/
A close reading of the Content Security Policy spec explains why 'self'
doesn't work:
'self'
matches requests with the same host, port, and scheme. Since your original page loaded with the scheme http://
or https://
, 'self'
won't match connections using ws://
.
This makes 'self'
rather useless for websocket connections. Unless I'm missing something, I think this is a bug in the spec.
Adding the address with protocol specified solved the problem for me.
connectSources = ["'self'", "ws://localhost:3000"]
Here's a way to automatically add the current host and port in express (es6 syntax):
import csp from 'helmet-csp';
app.use((req, res, next) => {
let wsSrc = (req.protocol === 'http' ? 'ws://' : 'wss://') + req.get('host');
csp({
connectSrc: ['\'self\'', wsSrc],
})(req, res, next);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With