Skip to content

Commit 1007930

Browse files
authored
Merge pull request #110 from krakenjs/nonce
Add support for CSP nonces
2 parents 6ae971a + a923714 commit 1007930

File tree

6 files changed

+82
-1
lines changed

6 files changed

+82
-1
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
##### v1.5.0
2+
3+
* Support for nonce for either style-src, script-src, or both
4+
* Lower case headers for improved performance
5+
* Support for referrer-policy
6+
* Allow CSRF cookie options to be set
7+
* Bugfix: return to suppress promise warning
8+
9+
110
##### v1.4.1
211

312
* Bugfix: typo in `nosniff` header

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ Furthermore, parsers must be registered before lusca.
7777
* `[{ "img-src": "'self' http:" }, "block-all-mixed-content"]`
7878
* `options.reportOnly` Boolean - Enable report only mode.
7979
* `options.reportUri` String - URI where to send the report data
80+
* `options.styleNonce` Boolean - Enable nonce for inline style-src, access from `req.locals.nonce`
81+
* `options.scriptNonce` Boolean - Enable nonce for inline script-src, access from `req.locals.nonce`
8082

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

index.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
│ limitations under the License. │
1717
\*───────────────────────────────────────────────────────────────────────────*/
1818
'use strict';
19+
var crypto = require('crypto');
1920

2021

2122
/**
@@ -24,10 +25,14 @@
2425
*/
2526
var lusca = module.exports = function (options) {
2627
var headers = [];
28+
var nonce;
2729

2830
if (options) {
2931
Object.keys(lusca).forEach(function (key) {
3032
var config = options[key];
33+
if (key === "csp" && options[key] && (options[key]['styleNonce'] || options[key]['scriptNonce'])) {
34+
nonce = true;
35+
}
3136

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

46+
if (nonce) {
47+
Object.defineProperty(res.locals, 'nonce', {
48+
value: crypto.pseudoRandomBytes(36).toString('base64'),
49+
enumerable: true
50+
});
51+
}
4152
headers.forEach(function (header) {
4253
chain = (function (next) {
4354
return function (err) {
4455
if (err) {
4556
next(err);
4657
return;
4758
}
48-
header(req, res, next);
59+
return header(req, res, next);
4960
};
5061
}(chain));
5162
});

lib/csp.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ module.exports = function (options) {
99
var policyRules = options && options.policy,
1010
isReportOnly = options && options.reportOnly,
1111
reportUri = options && options.reportUri,
12+
styleNonce = options && options.styleNonce,
13+
scriptNonce = options && options.scriptNonce,
1214
value, name;
1315

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

2931
return function csp(req, res, next) {
32+
if (styleNonce) {
33+
if (value.match(/style-src 'nonce-.{48}'/)) {
34+
value = value.replace(value.match(/'style-src nonce-.{48}'/), 'style-src \'nonce-' + res.locals.nonce + '\'');
35+
}
36+
else {
37+
value = value.replace('style-src', 'style-src \'nonce-' + res.locals.nonce + '\'');
38+
}
39+
}
40+
if (scriptNonce) {
41+
if (value.match(/script-src 'nonce-.{48}'/)) {
42+
value = value.replace(value.match(/script-src 'nonce-.{48}'/)[0], 'script-src \'nonce-' + res.locals.nonce + '\'');
43+
}
44+
else {
45+
value = value.replace('script-src', 'script-src \'nonce-' + res.locals.nonce + '\'');
46+
}
47+
}
3048
res.header(name, value);
3149
next();
3250
};

test/csp.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,34 @@ describe('CSP', function () {
9494
.expect(200, done);
9595
});
9696
});
97+
98+
describe('nonce checks', function () {
99+
it('no nonce specified', function (done) {
100+
var config = require('./mocks/config/cspEnforce'),
101+
app = mock({ csp: config });
102+
103+
app.get('/', function (req, res) {
104+
res.status(200).end();
105+
});
106+
107+
request(app)
108+
.get('/')
109+
.expect('Content-Security-Policy', /^(?!.*nonce).*$/)
110+
.expect(200, done);
111+
});
112+
113+
it('nonce specified', function (done) {
114+
var config = require('./mocks/config/nonce'),
115+
app = mock({ csp: config });
116+
117+
app.get('/', function (req, res) {
118+
res.status(200).end();
119+
});
120+
121+
request(app)
122+
.get('/')
123+
.expect('Content-Security-Policy', /nonce/)
124+
.expect(200, done);
125+
});
126+
});
97127
});

test/mocks/config/nonce.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
4+
module.exports = {
5+
reportOnly: false,
6+
scriptNonce: true,
7+
policy: {
8+
"default-src": "*",
9+
"script-src": "'unsafe-inline"
10+
}
11+
};

0 commit comments

Comments
 (0)