Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

node.js - correct content security policy for socket.io (web sockets) using helmet

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

Chrome Console Error

like image 903
PKK Avatar asked Nov 26 '14 00:11

PKK


4 Answers

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/

like image 60
Katstevens Avatar answered Nov 14 '22 08:11

Katstevens


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.

like image 27
Ed4 Avatar answered Nov 14 '22 08:11

Ed4


Adding the address with protocol specified solved the problem for me.

connectSources = ["'self'", "ws://localhost:3000"]
like image 5
Patrik Šimek Avatar answered Nov 14 '22 08:11

Patrik Šimek


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);
});
like image 2
Adrian Macneil Avatar answered Nov 14 '22 09:11

Adrian Macneil