Skip to content

Commit

Permalink
Merge pull request #110 from krakenjs/nonce
Browse files Browse the repository at this point in the history
Add support for CSP nonces
  • Loading branch information
linkRace authored Aug 7, 2017
2 parents 6ae971a + a923714 commit 1007930
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 1 deletion.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
##### v1.5.0

* Support for nonce for either style-src, script-src, or both
* Lower case headers for improved performance
* Support for referrer-policy
* Allow CSRF cookie options to be set
* Bugfix: return to suppress promise warning


##### v1.4.1

* Bugfix: typo in `nosniff` header
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ Furthermore, parsers must be registered before lusca.
* `[{ "img-src": "'self' http:" }, "block-all-mixed-content"]`
* `options.reportOnly` Boolean - Enable report only mode.
* `options.reportUri` String - URI where to send the report data
* `options.styleNonce` Boolean - Enable nonce for inline style-src, access from `req.locals.nonce`
* `options.scriptNonce` Boolean - Enable nonce for inline script-src, access from `req.locals.nonce`

Enables [Content Security Policy](https://www.owasp.org/index.php/Content_Security_Policy) (CSP) headers.

Expand Down
13 changes: 12 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
│ limitations under the License. │
\*───────────────────────────────────────────────────────────────────────────*/
'use strict';
var crypto = require('crypto');


/**
Expand All @@ -24,10 +25,14 @@
*/
var lusca = module.exports = function (options) {
var headers = [];
var nonce;

if (options) {
Object.keys(lusca).forEach(function (key) {
var config = options[key];
if (key === "csp" && options[key] && (options[key]['styleNonce'] || options[key]['scriptNonce'])) {
nonce = true;
}

if (config) {
headers.push(lusca[key](config));
Expand All @@ -38,14 +43,20 @@ var lusca = module.exports = function (options) {
return function lusca(req, res, next) {
var chain = next;

if (nonce) {
Object.defineProperty(res.locals, 'nonce', {
value: crypto.pseudoRandomBytes(36).toString('base64'),
enumerable: true
});
}
headers.forEach(function (header) {
chain = (function (next) {
return function (err) {
if (err) {
next(err);
return;
}
header(req, res, next);
return header(req, res, next);
};
}(chain));
});
Expand Down
18 changes: 18 additions & 0 deletions lib/csp.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ module.exports = function (options) {
var policyRules = options && options.policy,
isReportOnly = options && options.reportOnly,
reportUri = options && options.reportUri,
styleNonce = options && options.styleNonce,
scriptNonce = options && options.scriptNonce,
value, name;

name = 'content-security-policy';
Expand All @@ -27,6 +29,22 @@ module.exports = function (options) {
}

return function csp(req, res, next) {
if (styleNonce) {
if (value.match(/style-src 'nonce-.{48}'/)) {
value = value.replace(value.match(/'style-src nonce-.{48}'/), 'style-src \'nonce-' + res.locals.nonce + '\'');
}
else {
value = value.replace('style-src', 'style-src \'nonce-' + res.locals.nonce + '\'');
}
}
if (scriptNonce) {
if (value.match(/script-src 'nonce-.{48}'/)) {
value = value.replace(value.match(/script-src 'nonce-.{48}'/)[0], 'script-src \'nonce-' + res.locals.nonce + '\'');
}
else {
value = value.replace('script-src', 'script-src \'nonce-' + res.locals.nonce + '\'');
}
}
res.header(name, value);
next();
};
Expand Down
30 changes: 30 additions & 0 deletions test/csp.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,34 @@ describe('CSP', function () {
.expect(200, done);
});
});

describe('nonce checks', function () {
it('no nonce specified', function (done) {
var config = require('./mocks/config/cspEnforce'),
app = mock({ csp: config });

app.get('/', function (req, res) {
res.status(200).end();
});

request(app)
.get('/')
.expect('Content-Security-Policy', /^(?!.*nonce).*$/)
.expect(200, done);
});

it('nonce specified', function (done) {
var config = require('./mocks/config/nonce'),
app = mock({ csp: config });

app.get('/', function (req, res) {
res.status(200).end();
});

request(app)
.get('/')
.expect('Content-Security-Policy', /nonce/)
.expect(200, done);
});
});
});
11 changes: 11 additions & 0 deletions test/mocks/config/nonce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';


module.exports = {
reportOnly: false,
scriptNonce: true,
policy: {
"default-src": "*",
"script-src": "'unsafe-inline"
}
};

0 comments on commit 1007930

Please sign in to comment.