csp-header
Advanced tools
Comparing version 0.0.4 to 1.0.0
107
index.js
@@ -1,2 +0,2 @@ | ||
var allowedPolicies = [ | ||
const allowedPolicies = [ | ||
'base-uri', | ||
@@ -34,19 +34,17 @@ 'block-all-mixed-content', | ||
function buildCSPString(policies, reportUri){ | ||
var cspString = Object.keys(policies).map(function(policyName){ | ||
let cspString = Object.keys(policies).map(policyName => { | ||
if(policies[policyName] === true || policies[policyName].length === 0){ | ||
return policyName; | ||
} | ||
return policyName + ' ' + policies[policyName].join(' '); | ||
}).join('; ') + ';'; | ||
return `${policyName} ${policies[policyName].join(' ')}`; | ||
}).join('; '); | ||
if(typeof reportUri === 'string'){ | ||
cspString += ' report-uri ' + reportUri + ';'; | ||
cspString += `; report-uri ${reportUri}`; | ||
} | ||
return cspString; | ||
return `${cspString};`; | ||
} | ||
function csp(params){ | ||
var policies; | ||
// params should be an object | ||
@@ -57,9 +55,12 @@ if(typeof params !== 'object'){ | ||
// property policies is required | ||
if(typeof params.policies !== 'object'){ | ||
return; | ||
if (!params.policies) { | ||
if (params.presets || params.extend) { | ||
params.policies = {}; | ||
} else { | ||
return; | ||
} | ||
} | ||
// filter disallowed policies | ||
policies = Object.keys(params.policies).reduce(function(policies, policyName){ | ||
let policies = Object.keys(params.policies).reduce((policies, policyName) => { | ||
if(allowedPolicies.indexOf(policyName) > -1){ | ||
@@ -73,2 +74,20 @@ if(params.policies[policyName] !== false){ | ||
if (params.presets) { | ||
params.presets.forEach(preset => { | ||
let presetPolicies; | ||
if (typeof preset === 'string') { | ||
presetPolicies = requirePreset(preset); | ||
} else { | ||
presetPolicies = preset; | ||
} | ||
policies = extendPolicies(policies, presetPolicies); | ||
}); | ||
} | ||
if (params.extend) { | ||
policies = extendPolicies(policies, params.extend); | ||
} | ||
return buildCSPString(policies, params['report-uri']); | ||
@@ -78,2 +97,54 @@ } | ||
/** | ||
* Resolves require string | ||
* @param {string} presetName Relative/absolute path or full/short module name | ||
* @returns {string} | ||
*/ | ||
function resolvePreset(presetName) { | ||
const isFullModuleName = presetName.indexOf('csp-preset') === 0; | ||
if (isFullModuleName) { | ||
return presetName; | ||
} else { | ||
return `csp-preset-${presetName}`; | ||
} | ||
} | ||
function requirePreset(presetName) { | ||
try { | ||
return require(resolvePreset(presetName)); | ||
} catch(err) { | ||
throw new Error(`CSP preset ${presetName} is not found`); | ||
} | ||
} | ||
/** | ||
* Extends policies object | ||
* @param {Object} original Original policies | ||
* @param {Object} extension Additional policies | ||
* @returns {Object} Extended policies | ||
*/ | ||
function extendPolicies(original, extension){ | ||
const extended = Object.assign(original); | ||
Object.keys(extension).forEach(policyName => { | ||
const extPolicy = extension[policyName]; | ||
const origPolicy = original[policyName]; | ||
if (origPolicy === undefined) { | ||
extended[policyName] = extPolicy; | ||
} else if(Array.isArray(extPolicy) && extPolicy.length > 0 && Array.isArray(origPolicy)){ | ||
extPolicy.forEach(rule => { | ||
if(typeof rule === 'string' && origPolicy.indexOf(rule) === -1){ | ||
extended[policyName].push(rule); | ||
} | ||
}); | ||
} else { | ||
extended[policyName] = extPolicy[policyName]; | ||
} | ||
}); | ||
return extended; | ||
} | ||
/** | ||
* Build nonce param | ||
@@ -84,10 +155,12 @@ * @param nonceId {string} Nonce param id | ||
csp.nonce = function(nonceId){ | ||
return '\'nonce-' + nonceId + '\''; | ||
return `'nonce-${nonceId}'`; | ||
}; | ||
csp.NONE = '\'none\''; | ||
csp.SELF = '\'self\''; | ||
csp.INLINE = '\'unsafe-inline\''; | ||
csp.EVAL = '\'unsafe-eval\''; | ||
csp.resolvePreset = resolvePreset; | ||
csp.NONE = "'none'"; | ||
csp.SELF = "'self'"; | ||
csp.INLINE = "'unsafe-inline'"; | ||
csp.EVAL = "'unsafe-eval'"; | ||
module.exports = csp; |
{ | ||
"name": "csp-header", | ||
"version": "0.0.4", | ||
"version": "1.0.0", | ||
"description": "Content-Security-Policy header generator", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha test" | ||
"test": "ava" | ||
}, | ||
@@ -17,5 +17,7 @@ "keywords": [ | ||
"license": "WTFPL", | ||
"engines": { | ||
"node": ">=4" | ||
}, | ||
"devDependencies": { | ||
"mocha": "^2.4.5", | ||
"should": "^8.3.0" | ||
"ava": "^0.18.2" | ||
}, | ||
@@ -22,0 +24,0 @@ "directories": { |
@@ -6,3 +6,3 @@ # csp-header | ||
```js | ||
var csp = require('csp-header'); | ||
const csp = require('csp-header'); | ||
csp({ | ||
@@ -21,3 +21,3 @@ policies: { | ||
] | ||
}, | ||
} | ||
'report-uri': 'https://cspreport.com/send' | ||
@@ -28,1 +28,37 @@ }); | ||
``` | ||
## Extending | ||
If you want to extend your config by some rules: | ||
```js | ||
const myCSPPolicies = require('./my-csp-rules'); | ||
csp({ | ||
policies: myCSPPolicies, | ||
extend: { | ||
'connect-src': ['test.com'] | ||
} | ||
}); | ||
``` | ||
## Presets | ||
You can use csp presets prefixed by 'csp-preset'. If you have a web-service it would be great if you write preset with rules for your service users. | ||
E.g. your service is called ``my-super-service.com``. You publish preset ``csp-preset-my-super-service`` containing following code: | ||
```js | ||
modules.exports = { | ||
'script-src': ['api.my-super-service.com'], | ||
'img-src': ['images.my-super-service.com'] | ||
}; | ||
``` | ||
Then someone wants to configure its CSP to work with your service. And now it's so easy: | ||
```js | ||
const myCSPPolicies = require('./my-csp-rules'); | ||
csp({ | ||
policies: myCSPPolicies, | ||
presets: ['my-super-service'] | ||
}); | ||
``` | ||
And you will get a lot of thanks ;) |
@@ -1,77 +0,130 @@ | ||
var should = require('should'); | ||
var csp = require('../index'); | ||
import test from 'ava'; | ||
import csp from '../index'; | ||
describe('Input params', function(){ | ||
it('should returns undefined if params was not specified', function(){ | ||
should(csp()).be.type('undefined'); | ||
test('Empty args', t => { | ||
t.is(csp(), undefined); | ||
}); | ||
test('Empty policies', t => { | ||
const actual = csp({ | ||
nonce: true, | ||
foo: 'bar' | ||
}); | ||
t.is(actual, undefined); | ||
}); | ||
it('should returns undefined if policies property was not specified', function(){ | ||
should(csp({ | ||
nonce: true, | ||
foo: 'bar' | ||
})).be.type('undefined'); | ||
test('Disallowed policies', t => { | ||
const actual = csp({ | ||
policies: { | ||
'script-src': [ 'test.com', csp.SELF ], | ||
'foo-bar-src': [ 'foo', 'bar' ] | ||
} | ||
}); | ||
const expected = "script-src test.com 'self';"; | ||
t.is(actual, expected); | ||
}); | ||
it('should ignore disallowed policies', function(){ | ||
csp({ | ||
policies: { | ||
'script-src': [ 'test.com', csp.SELF ], | ||
'foo-bar-src': [ 'foo', 'bar' ] | ||
} | ||
}).should.be.equal('script-src test.com \'self\';'); | ||
test('report-uri', t => { | ||
const actual = csp({ | ||
policies: { | ||
'script-src': [ csp.SELF ] | ||
}, | ||
'report-uri': 'https://test.com/cspreport' | ||
}) | ||
const expected = "script-src 'self'; report-uri https://test.com/cspreport;"; | ||
t.is(actual, expected); | ||
}); | ||
test('Valueless directives', t => { | ||
const actualTrue = csp({ | ||
policies: { | ||
'script-src': ['test.com'], | ||
'block-all-mixed-content': true | ||
} | ||
}); | ||
it('should add report-uri param', function(){ | ||
csp({ | ||
policies: { | ||
'script-src': [ csp.SELF ] | ||
}, | ||
'report-uri': 'https://test.com/cspreport' | ||
}).should.be.equal('script-src \'self\'; report-uri https://test.com/cspreport;'); | ||
const actualEmptyArray = csp({ | ||
policies: { | ||
'script-src': ['test.com'], | ||
'block-all-mixed-content': [] | ||
} | ||
}); | ||
it('should support valueless directives', function(){ | ||
csp({ | ||
policies: { | ||
'script-src': [ 'test.com' ], | ||
'block-all-mixed-content': true | ||
} | ||
}).should.be.equal('script-src test.com; block-all-mixed-content;'); | ||
const actualEmptyString = csp({ | ||
policies: { | ||
'script-src': ['test.com'], | ||
'block-all-mixed-content': '' | ||
} | ||
}); | ||
csp({ | ||
policies: { | ||
'script-src': [ 'test.com' ], | ||
'block-all-mixed-content': [] | ||
} | ||
}).should.be.equal('script-src test.com; block-all-mixed-content;'); | ||
const expected = 'script-src test.com; block-all-mixed-content;'; | ||
csp({ | ||
policies: { | ||
'script-src': [ 'test.com' ], | ||
'block-all-mixed-content': '' | ||
} | ||
}).should.be.equal('script-src test.com; block-all-mixed-content;'); | ||
t.is(actualTrue, expected); | ||
t.is(actualEmptyArray, expected); | ||
t.is(actualEmptyString, expected); | ||
}); | ||
test('Presets | extend', t => { | ||
const actual = csp({ | ||
presets: [require('./fixtures/presets/csp-preset-test')] | ||
}); | ||
t.is(actual, 'script-src test.com; style-src test.com;'); | ||
}); | ||
describe('Utils', function(){ | ||
it('should build nonce param', function(){ | ||
csp.nonce('vg3eer#E4gEbw34gwq3fgqGQWBWQh').should.be.equal('\'nonce-vg3eer#E4gEbw34gwq3fgqGQWBWQh\''); | ||
test('Presets | resolve', t => { | ||
t.is(csp.resolvePreset('csp-preset-test'), 'csp-preset-test'); | ||
t.is(csp.resolvePreset('test'), 'csp-preset-test'); | ||
}); | ||
test('Extend | new rule', t => { | ||
const actual = csp({ | ||
policies: { | ||
'script-src': [ 'myhost.com' ] | ||
}, | ||
extend: { | ||
'script-src': [ 'additional.host.com' ] | ||
} | ||
}); | ||
describe('Constants', function(){ | ||
it('should contains \'self\'', function(){ | ||
csp.SELF.should.be.equal('\'self\''); | ||
}); | ||
it('should contains \'unsafe-inline\'', function(){ | ||
csp.INLINE.should.be.equal('\'unsafe-inline\''); | ||
}); | ||
it('should contains \'unsafe-eval\'', function(){ | ||
csp.EVAL.should.be.equal('\'unsafe-eval\''); | ||
}); | ||
it('should contains \'none\'', function(){ | ||
csp.NONE.should.be.equal('\'none\''); | ||
}); | ||
t.is(actual, 'script-src myhost.com additional.host.com;'); | ||
}); | ||
test('Extend | duplicating', t => { | ||
const actual = csp({ | ||
policies: { | ||
'script-src': [ 'myhost.com' ] | ||
}, | ||
extend: { | ||
'script-src': [ 'myhost.com' ] | ||
} | ||
}); | ||
t.is(actual, 'script-src myhost.com;'); | ||
}); | ||
test('Extend | new policy', t => { | ||
const actual = csp({ | ||
policies: { | ||
'script-src': [ 'myhost.com' ] | ||
}, | ||
extend: { | ||
'style-src': [ 'newhost.com' ] | ||
} | ||
}); | ||
t.is(actual, 'script-src myhost.com; style-src newhost.com;'); | ||
}); | ||
test('Nonce', t => { | ||
const actual = csp.nonce('vg3eer#E4gEbw34gwq3fgqGQWBWQh'); | ||
const expected = "'nonce-vg3eer#E4gEbw34gwq3fgqGQWBWQh'" | ||
t.is(actual, expected); | ||
}); | ||
test('Constants', t => { | ||
t.is(csp.SELF, "'self'"); | ||
t.is(csp.INLINE, "'unsafe-inline'"); | ||
t.is(csp.EVAL, "'unsafe-eval'"); | ||
t.is(csp.NONE, "'none'"); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
98789
1
7
250
0
62
1
1