├── .gitignore
├── config
└── config.json
├── .npmignore
├── .travis.yml
├── .editorconfig
├── test
├── exclusion.json
├── inclusion.json
├── list_item.js
├── license.js
├── bin.js
└── audit.js
├── lib
├── license.js
├── list_item.js
└── audit.js
├── index.js
├── package.json
├── LICENSE.txt
├── docs
├── flowchart.dot
└── flowchart.svg
├── bin
└── dep-audit
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | coverage
3 | node_modules
4 |
--------------------------------------------------------------------------------
/config/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclusions": {},
3 | "inclusions": {},
4 | "spdx": "(MIT OR ISC)"
5 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .editorconfig
3 | .travix.yml
4 | config
5 | coverage
6 | docs
7 | test
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - '0.10'
5 | - '0.12'
6 | - '4'
7 | - '6'
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | trim_trailing_whitespace = true
10 |
11 |
--------------------------------------------------------------------------------
/test/exclusion.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclusions": {
3 | "aws-sign2": [
4 | {
5 | "version_range": "0.6.0",
6 | "audit_trail": "test",
7 | "desc": "fizzzzz"
8 | }
9 | ]
10 | },
11 | "inclusions": {},
12 | "spdx": "(MIT OR ISC OR BSD-3-Clause OR BSD-2-Clause OR WTFPL OR Apache-2.0 OR CC-BY-3.0 OR Unlicense OR MPL-2.0)"
13 | }
--------------------------------------------------------------------------------
/lib/license.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2016, Yahoo Inc.
3 | Code licensed under the MIT License.
4 | See LICENSE.txt
5 | */
6 | var checker = require('license-checker')
7 | var Promise = require('bluebird')
8 |
9 | module.exports = function getModules (opts) {
10 | return new Promise(function (resolve, reject) {
11 | checker.init(opts, function (err, json) {
12 | err ? reject(err) : resolve(json)
13 | })
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/test/inclusion.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclusions": {},
3 | "inclusions": {
4 | "tweetnacl": [
5 | {
6 | "version_range": "0.13.3",
7 | "audit_trail": "foo",
8 | "desc": "bar"
9 | },
10 | {
11 | "version_range": "0.14.x",
12 | "audit_trail": "foo",
13 | "desc": "has \"SEE LICENSE IN COPYING.txt\""
14 | }
15 | ]
16 | },
17 | "spdx": "(MIT OR ISC OR BSD-4-Clause OR BSD-3-Clause OR BSD-2-Clause OR WTFPL OR Apache-2.0 OR CC-BY-3.0 OR Unlicense OR MPL-2.0)"
18 | }
19 |
--------------------------------------------------------------------------------
/lib/list_item.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2016, Yahoo Inc.
3 | Code licensed under the MIT License.
4 | See LICENSE.txt
5 | */
6 | var semver = require('semver')
7 |
8 | module.exports = function getListItem (exceptions) {
9 | return function (pkg) {
10 | var components = pkg.split('@')
11 | var name = components[0]
12 | var ver = components[1]
13 | var match = null
14 |
15 | if (exceptions[name]) {
16 | exceptions[name].some(function (item) {
17 | if (semver.satisfies(ver, item.version_range)) {
18 | match = item
19 | return true
20 | }
21 | })
22 | }
23 | return match
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2016, Yahoo Inc.
3 | Code licensed under the MIT License.
4 | See LICENSE.txt
5 | */
6 | var getLicenses = require('./lib/license')
7 | var audit = require('./lib/audit')
8 |
9 | // The second argument to the callback is an object with the `pass` and `fail`
10 | // keys. Each of these is an object. For each key package@version the value
11 | // is the reason why the package passes or fails the checks. If the reason is
12 | // a string it's the license of the package@version, otherwise the reason is
13 | // the exception object.
14 | module.exports = function (opts, callback) {
15 | getLicenses(opts.checker)
16 | .then(function (json) {
17 | if (typeof opts.hook === 'function') {
18 | return opts.hook(json)
19 | }
20 | return json
21 | })
22 | .then(function (json) {
23 | var report = audit(opts.fix, json, opts.spdx, opts.include, opts.exclude)
24 | callback(null, report)
25 | })
26 | .catch(callback)
27 | }
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dep-audit",
3 | "version": "1.0.1",
4 | "description": "Audits licenses of npm packages installed as dependencies",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "standard && mocha",
8 | "coverage": "istanbul cover _mocha",
9 | "doc": "dot -Tsvg docs/flowchart.dot > docs/flowchart.svg"
10 | },
11 | "bin": {
12 | "dep-audit": "./bin/dep-audit"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/yahoo/dep-audit"
17 | },
18 | "keywords": [
19 | "license"
20 | ],
21 | "author": "jungsoo",
22 | "license": "MIT",
23 | "dependencies": {
24 | "bluebird": "^3.4.1",
25 | "license-checker": "^8.0.2",
26 | "request": "^2.72.0",
27 | "semver": "^5.1.1",
28 | "spdx-correct": "^1.0.2",
29 | "spdx-satisfies": "^0.1.3",
30 | "validate-npm-package-license": "^3.0.1",
31 | "yargs": "^4.7.1"
32 | },
33 | "devDependencies": {
34 | "istanbul": "^0.4.3",
35 | "mocha": "^2.5.3",
36 | "mockery": "^1.7.0",
37 | "standard": "^7.1.2"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright 2016 Yahoo, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/test/list_item.js:
--------------------------------------------------------------------------------
1 | /* globals it, describe, before, after */
2 | var assert = require('assert')
3 | var mockery = require('mockery')
4 |
5 | describe('exceptions module', function () {
6 | var getItem
7 | before(function () {
8 | var semverMock = {satisfies: function (version, accepted) {
9 | return version === accepted
10 | }}
11 |
12 | mockery.enable({
13 | useCleanCache: true,
14 | warnOnReplace: false,
15 | warnOnUnregistered: false
16 | })
17 | mockery.registerMock('semver', semverMock)
18 | var exceptionList = {
19 | 'pp': [{version_range: '2.9.0'}],
20 | 'x': [{version_range: '1.0.0'}]
21 | }
22 | getItem = require('../lib/list_item.js')(exceptionList)
23 | })
24 |
25 | it('Returns list item if name is on exception list and version is ok', function () {
26 | assert.deepEqual(getItem('x@1.0.0'), {version_range: '1.0.0'})
27 | })
28 |
29 | it('Returns null if name is not on exception list', function () {
30 | assert.equal(getItem('y@1.0.0'), null)
31 | })
32 |
33 | it('Returns null if version is not in accepted range', function () {
34 | assert.equal(getItem('x@1.0.1'), null)
35 | })
36 |
37 | after(function () {
38 | mockery.disable()
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/test/license.js:
--------------------------------------------------------------------------------
1 | /* globals it, describe, before, after */
2 | var assert = require('assert')
3 | var mockery = require('mockery')
4 |
5 | describe('license module', function () {
6 | var getLicenses
7 | before(function () {
8 | var checkerMock = {
9 | init: function (opts, callback) {
10 | if (opts.err) {
11 | callback(new Error('Could not get licenses', null))
12 | }
13 | callback(null, 'success')
14 | }
15 | }
16 | mockery.enable({
17 | useCleanCache: true,
18 | warnOnReplace: false,
19 | warnOnUnregistered: false
20 | })
21 | mockery.registerMock('license-checker', checkerMock)
22 | getLicenses = require('../lib/license.js')
23 | })
24 |
25 | it('Returns resolved promise on success', function () {
26 | var opt = {}
27 | return getLicenses(opt)
28 | .then(function (data) {
29 | assert.equal(data, 'success')
30 | })
31 | })
32 |
33 | it('Rejects promise on error', function () {
34 | var opt = {err: true}
35 | return getLicenses(opt)
36 | .then(function (data) {
37 | assert.fail('No error thrown')
38 | })
39 | .catch(function (e) {
40 | assert.equal(e.message, 'Could not get licenses')
41 | })
42 | })
43 |
44 | after(function () {
45 | mockery.disable()
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/docs/flowchart.dot:
--------------------------------------------------------------------------------
1 | digraph {
2 | subgraph cluster_logic {
3 | label="Logic";
4 | node[shape="box", style="rounded"]
5 | start; good; bad;
6 | node[shape="diamond", style=""]
7 | spdx_valid; inclusion_list; exclusion_list; hook;
8 | }
9 |
10 | start -> hook;
11 | hook -> bad[label="no"];
12 | hook -> spdx_valid[label="yes"];
13 | spdx_valid -> exclusion_list[label="yes"];
14 | spdx_valid -> inclusion_list[label="no"];
15 | exclusion_list -> bad[label="yes"];
16 | exclusion_list -> good[label="no"];
17 | inclusion_list -> good[label="yes"];
18 | inclusion_list -> bad[label="no"];
19 |
20 | spdx_valid[label="Is 'valid'\nSPDX license?"];
21 | hook[label="Passes\nuser-supplied\nhook?"];
22 | inclusion_list[label="Is in the\ninclusion list?"]
23 | exclusion_list[label="Is in the\nexclusion list?"]
24 |
25 | subgraph cluster_data {
26 | label="Data";
27 | list_item[
28 | label=<
29 | | list_item |
30 | | name |
31 | | audit_trail |
32 | | desc |
33 | | version_range |
34 |
>
35 | shape="none"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/bin.js:
--------------------------------------------------------------------------------
1 | /* globals it, describe, before */
2 | var path = require('path')
3 | var assert = require('assert')
4 | var exec = require('child_process').exec
5 | var bin = path.resolve(__dirname, '..', 'bin', 'dep-audit')
6 |
7 | function config (p) {
8 | return path.resolve(__dirname, p)
9 | }
10 |
11 | describe('If license does not match', function () {
12 | var code
13 | before(function (done) {
14 | this.timeout(10000)
15 | exec(bin + ' --config ' + config('../config/config.json'),
16 | {cwd: process.cwd()},
17 | function (error) {
18 | code = error.code
19 | done()
20 | })
21 | })
22 |
23 | it('Should exit 1', function () {
24 | assert.equal(code, 1)
25 | })
26 | })
27 |
28 | describe('There is a dependency on inclusion list', function () {
29 | var code
30 | before(function (done) {
31 | this.timeout(10000)
32 | exec(bin + ' --config ' + config('inclusion.json') + ' --fix --guess',
33 | {cwd: process.cwd()},
34 | function (error) {
35 | code = error
36 | done()
37 | })
38 | })
39 |
40 | it('Should exit 0', function () {
41 | assert.equal(code, null)
42 | })
43 | })
44 |
45 | describe('There is a dependency on exclusion list', function () {
46 | var err
47 | before(function (done) {
48 | this.timeout(10000)
49 | exec(bin + ' --config ' + config('exclusion.json') + ' --fix --guess',
50 | {cwd: process.cwd()},
51 | function (error) {
52 | err = error
53 | done()
54 | })
55 | })
56 |
57 | it('Should exit 1', function () {
58 | assert.equal(err.code, 1)
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/lib/audit.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2016, Yahoo Inc.
3 | Code licensed under the MIT License.
4 | See LICENSE.txt
5 | */
6 | var licenseSatisfies = require('spdx-satisfies')
7 | var correct = require('spdx-correct')
8 | var valid = require('validate-npm-package-license')
9 | var getItem = require('./list_item.js')
10 | var json = require(process.cwd() + '/package.json')
11 |
12 | function satisfies (license, allowed) {
13 | try {
14 | return licenseSatisfies(license, allowed)
15 | } catch (e) { // catch lexical error if license not recognized
16 | return false
17 | }
18 | }
19 |
20 | function spdxFix (spdx) {
21 | var licenses = spdx.split(' ')
22 | var fixed = licenses.map(function (val, ind) {
23 | if (val.charAt(0) === '(') {
24 | return '(' + (correct(val.slice(1)) || val.slice(1))
25 | } else if (val.charAt(val.length - 1) === ')') {
26 | return (correct(val.slice(0, -1)) || val.slice(0, -1)) + ')'
27 | } else {
28 | return correct(val) || val
29 | }
30 | })
31 | return fixed.join(' ')
32 | }
33 |
34 | module.exports = function audit (fix, modules, allowed, inclusions, exclusions) {
35 | if (valid(allowed).spdx !== true) {
36 | throw new Error('Allowed licenses ' + allowed + ' is not a valid spdx expression')
37 | }
38 |
39 | var getInclusion = getItem(inclusions)
40 | var getExclusion = getItem(exclusions)
41 | var report = { pass: {}, fail: {} }
42 |
43 | Object.keys(modules).forEach(function (nameVersion) {
44 | if (json.name + '@' + json.version === nameVersion) {
45 | return
46 | }
47 | var licenses = modules[nameVersion].licenses
48 | if (Array.isArray(licenses)) {
49 | licenses = '(' + licenses.join(' OR ') + ')'
50 | }
51 | licenses = licenses.replace(/[*]/g, '') // in case we are guessing
52 | licenses = fix ? spdxFix(licenses) : licenses
53 |
54 | var exception
55 | exception = getExclusion(nameVersion)
56 | if (exception) {
57 | report.fail[nameVersion] = exception
58 | return
59 | }
60 | exception = getInclusion(nameVersion)
61 | if (exception) {
62 | report.pass[nameVersion] = exception
63 | return
64 | }
65 | if (satisfies(licenses, allowed)) {
66 | report.pass[nameVersion] = licenses
67 | } else {
68 | report.fail[nameVersion] = licenses
69 | }
70 | })
71 | return report
72 | }
73 |
--------------------------------------------------------------------------------
/bin/dep-audit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /*
3 | Copyright 2016, Yahoo Inc.
4 | Code licensed under the MIT License.
5 | See LICENSE.txt
6 | */
7 |
8 | // support early versions of node 6
9 | if (process.stdout._handle && process.stdout._handle.setBlocking) process.stdout._handle.setBlocking(true)
10 | if (process.stderr._handle && process.stderr._handle.setBlocking) process.stderr._handle.setBlocking(true)
11 |
12 | var path = require('path')
13 | var audit = require('../index')
14 | var request = require('request')
15 | var argv = require('yargs')
16 | .usage('Usage:\n$0 --config [options]' +
17 | '\n$0 --config-url [options]')
18 | .config()
19 | .describe({'config-url': 'URL to JSON config file',
20 | 'hook': 'Path to hook to execute on modules before auditing',
21 | 'fix': 'Attempt to fix incorrect licenses',
22 | 'guess': 'Attempt to guess licenses from files other than package.json'})
23 | .string(['config', 'config-url', 'hook'])
24 | .boolean(['fix', 'guess', 'version'])
25 | .version()
26 | .help()
27 | .argv
28 |
29 | if (argv['config-url']) {
30 | request.get({
31 | uri: argv['config-url'],
32 | json: true
33 | }, function (err, res, body) {
34 | if (err) {
35 | console.error('Could not fetch config file')
36 | process.exit(2)
37 | } else if (res.statusCode === 200) {
38 | checkDependencies(body)
39 | }
40 | })
41 | } else if (argv.config) {
42 | checkDependencies(require(path.resolve(process.cwd(), argv.config)))
43 | }
44 |
45 | function checkDependencies (body) {
46 | var opts = {
47 | checker:
48 | {start: process.cwd(),
49 | production: true,
50 | unknown: !argv.guess},
51 | fix: argv.fix,
52 | spdx: (body || argv).spdx,
53 | include: (body || argv).inclusions || {},
54 | exclude: (body || argv).exclusions || {}
55 | }
56 | if (argv.hook) {
57 | opts.hook = require(path.resolve(process.cwd(), argv.hook))
58 | }
59 |
60 | audit(opts, function (error, report) {
61 | if (error) {
62 | console.error(error)
63 | process.exit(1)
64 | }
65 |
66 | if (Object.keys(report.fail).length) {
67 | console.error('The following packages violate the policy:')
68 | Object.keys(report.fail).forEach(function (nameVersion) {
69 | var reason = report.fail[nameVersion]
70 | if (typeof reason === 'string') {
71 | console.error(' ' + nameVersion + ' license', JSON.stringify(reason), 'not allowed by policy')
72 | } else {
73 | console.error(' ' + nameVersion + ' ' + reason.desc, '(' + JSON.stringify(reason.audit_trail) + ')')
74 | }
75 | })
76 | process.exit(1)
77 | }
78 |
79 | console.log('All modules are allowed!')
80 | })
81 | }
82 |
--------------------------------------------------------------------------------
/test/audit.js:
--------------------------------------------------------------------------------
1 | /* globals it, describe, before, after */
2 | var assert = require('assert')
3 | var mockery = require('mockery')
4 |
5 | describe('audit module', function () {
6 | var audit, bl, wl
7 | before(function () {
8 | bl = {name: 'x', version_range: '1.0.0',
9 | audit_trail: '1/1/2000', desc: 'blacklisted'}
10 | wl = {name: 'y', version_range: '2.0.0',
11 | audit_trail: '2/2/2000', desc: 'whitelisted'}
12 | var exceptionMock = function (config) {
13 | return function (name) {
14 | var item = null
15 | var split = name.split('@')
16 | if (split[0] === config.name &&
17 | split[1] === config.version_range) {
18 | item = config
19 | }
20 | return item
21 | }
22 | }
23 | var satisfiesMock = function (license, allowed) {
24 | if (license === 'err') {
25 | throw new Error()
26 | }
27 | return license === allowed
28 | }
29 | var fixMock = function (spdx) {
30 | if (spdx === 'OR' || spdx === 'unknown') {
31 | return null
32 | }
33 | return 'fix_' + spdx
34 | }
35 | var validMock = function (license) {
36 | if (license === 'invalid') {
37 | return {spdx: false}
38 | }
39 | return {spdx: true}
40 | }
41 | mockery.enable({
42 | useCleanCache: true,
43 | warnOnReplace: false,
44 | warnOnUnregistered: false
45 | })
46 | mockery.registerMock('spdx-satisfies', satisfiesMock)
47 | mockery.registerMock('spdx-correct', fixMock)
48 | mockery.registerMock('validate-npm-package-license', validMock)
49 | mockery.registerMock('./list_item.js', exceptionMock)
50 | audit = require('../lib/audit')
51 | })
52 |
53 | it('reports packages not in inclusion list', function () {
54 | var modules = {'z@1.0.1': {licenses: 'foo'}}
55 | var res = audit(false, modules, 'bar', wl, bl)
56 | assert.equal(Object.keys(res.pass).length, 0, 'no passing modules')
57 | assert.deepEqual(res.fail, {
58 | 'z@1.0.1': 'foo'
59 | }, 'one failing module')
60 | })
61 |
62 | it('reports packages in exclusion list', function () {
63 | var modules = {'x@1.0.0': {licenses: 'foo'}}
64 | var res = audit(false, modules, 'foo', wl, bl)
65 | assert.equal(Object.keys(res.pass).length, 0, 'no passing modules')
66 | assert.deepEqual(res.fail, {
67 | 'x@1.0.0': {
68 | name: 'x',
69 | version_range: '1.0.0',
70 | audit_trail: '1/1/2000',
71 | desc: 'blacklisted'
72 | }
73 | }, 'one failing module')
74 | })
75 |
76 | it('reports all passing modules', function () {
77 | var modules = {'z@1.0.0': {licenses: 'foo'}, 'y@2.0.0': {licenses: 'bar'}}
78 | var res = audit(false, modules, 'foo', wl, bl)
79 | assert.deepEqual(res, {
80 | pass: {
81 | 'y@2.0.0': {
82 | name: 'y',
83 | version_range: '2.0.0',
84 | audit_trail: '2/2/2000',
85 | desc: 'whitelisted'
86 | },
87 | 'z@1.0.0': 'foo'
88 | },
89 | fail: {}
90 | })
91 | })
92 |
93 | it('Removes asterisks from licenses when checking', function () {
94 | var modules = {'z@5.0.0': {licenses: '(foo* OR bar*)'}}
95 | var res = audit(false, modules, '(foo OR bar)', wl, bl)
96 | assert.deepEqual(res.pass, {
97 | 'z@5.0.0': '(foo OR bar)'
98 | })
99 | })
100 |
101 | it('Fixes licenses in spdx expression', function () {
102 | var modules = {'m@5.0.0': {licenses: '(foo OR bar OR test)'}}
103 | var res = audit(true, modules, '(fix_foo OR fix_bar OR fix_test)', wl, bl)
104 | assert.deepEqual(res.pass, {
105 | 'm@5.0.0': '(fix_foo OR fix_bar OR fix_test)'
106 | })
107 | })
108 |
109 | it('Fixer returns original value if it cannot fix license', function () {
110 | var modules = {'z@2.3.0': {licenses: '(unknown OR unknown)'}}
111 | var res = audit(true, modules, '(unknown OR unknown)', wl, bl)
112 | assert.deepEqual(res.pass, {
113 | 'z@2.3.0': '(unknown OR unknown)'
114 | })
115 | })
116 |
117 | it('If license cannot be read, does not satisfy', function () {
118 | var modules = {z: {licenses: 'err'}}
119 | var res = audit(false, modules, 'mit', wl, bl)
120 | assert.deepEqual(res.fail, {
121 | 'z': 'err'
122 | })
123 | })
124 |
125 | it('If allowed licenses are invalid, throws error', function () {
126 | try {
127 | var modules = {z: {licenses: 'foo'}}
128 | audit(false, modules, 'invalid', wl, bl)
129 | } catch (e) {
130 | assert.equal(e.message, 'Allowed licenses invalid is not a valid spdx expression')
131 | }
132 | })
133 |
134 | it('If license field is array, create spdx', function () {
135 | var modules = {'x@2.1.1': {licenses: ['MIT', 'ISC']}}
136 | var res = audit(false, modules, '(MIT OR ISC)', wl, bl)
137 | assert.deepEqual(res.pass, {
138 | 'x@2.1.1': '(MIT OR ISC)'
139 | })
140 | })
141 |
142 | after(function () {
143 | mockery.deregisterAll()
144 | mockery.disable()
145 | })
146 | })
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | NPM Dependency Audit Tool [](http://standardjs.com/)
2 | ======================
3 |
4 | ```
5 | npm install -g dep-audit
6 | ```
7 |
8 | Given a path to a configuration file, this module will check that each module
9 | in the node_modules tree of the current directory satisfies the requirements
10 | established by the configuration file.
11 |
12 | ```
13 | dep-audit --config config/config.json
14 | ```
15 |
16 | Config file should be formatted like so:
17 |
18 | ```
19 | {
20 | "exclusions":{
21 | "name":[
22 | {
23 | "version_range":"1.0.0-2.1.4", // semver range that is not allowed
24 | "audit_trail":"Added by admin @ 2016-1-1", // who added it and when
25 | "desc":"Bad module" // why module isn't allowed
26 | }
27 | ]
28 | },
29 | "inclusions":{
30 | "name":[
31 | {
32 | "version_range":"1.0.0-2.1.4", // semver range that is allowed
33 | "audit_trail":"Added by admin @ 2016-1-1", // who added it and when
34 | "desc":"Bad module" // why module is allowed
35 | }
36 | ]
37 | },
38 | "spdx":"(MIT OR ISC)" // spdx expression indicating which licenses are ok
39 | }
40 | ```
41 | If a module is not in `exclusions` or `inclusions`, it will be allowed if
42 | its license satisfies the SPDX expression in `spdx`.
43 | Modules in `exclusions` will not be allowed even if their license
44 | satisfies the spdx expression.
45 | Modules in `inclusions` will be allowed even if their license
46 | does not satisfy the spdx expression.
47 |
48 | If an unacceptable module that does not satisfy the requirements in the config file
49 | is found, dep-audit will log the module as well as its audit trail and description.
50 | The process will then exit with exit code 1.
51 |
52 | Alternatively, the user can provide a URL to fetch a config file from.
53 |
54 | ```
55 | dep-audit --config-url http://....
56 | ```
57 |
58 | Hooks
59 | -----
60 | A hook can be provided that will be executed before each module is audited.
61 | In order to supply a hook, pass the file path of the module to the hook option.
62 | ```
63 | --hook /path/to/hook/
64 | ```
65 | The file must export a function that takes an object representing the
66 | node_modules tree as an argument and returns an object representing the
67 | modules from the node_modules tree that should be audited.
68 | ```
69 | module.exports = function (json) {
70 | return json
71 | }
72 | ```
73 | The node_modules object will be formatted like so using
74 | a separate object for each module.
75 | ```
76 | {
77 | "name@version": {
78 | "licenses": "ISC",
79 | "repository": "url to repo",
80 | "licenseFile": "/path/to/license/file"
81 | }
82 | }
83 | ```
84 |
85 | Options
86 | -------
87 | * `--config [path]` Path to fetch inclusion list, exclusion list, and spdx expression
88 | * `--config-url [url]` URL to fetch inclusion list, exclusion list, and spdx expression
89 | * `--allowed [list]` Audit modules in node_modules tree using list of licenses
90 | * `--hook [path]` Path to hook to execute before modules are audited
91 | * `--fix` Attempt to fix incorrect licenses (implemented using [spdx-correct](https://www.npmjs.com/package/spdx-correct))
92 | * `--guess` Attempt to guess licenses from files other than package.json (implemented using [license-checker](https://www.npmjs.com/package/license-checker))
93 | * `--version` Display the current version
94 | * `--help` Get help
95 |
96 | Using dep-audit programmatically
97 | ------------------------------------
98 | If dep-audit is installed locally, it can be used as a library rather than
99 | a command line tool.
100 | ```
101 | npm install dep-audit
102 | ```
103 |
104 | Then, just require the module in your project and you can audit your dependencies programmatically.
105 | ```
106 | var audit = require('dep-audit')
107 | var opts = {
108 | "hook": function (json) {
109 | console.log(json)
110 | return json
111 | },
112 | "checker":
113 | {
114 | "start": "/path/to/project/",
115 | "production": true,
116 | // If true, will only audit production dependencies.
117 | // If false, will also audit dev dependencies,
118 | "unknown": false
119 | // If true, will only check package.json for license.
120 | // If false, will guess license from other files
121 | },
122 | "fix": true, // If true, will fix malformed licenses,
123 | "spdx": "MIT", // spdx expression indicating which licenses are allowed
124 | "include": {
125 | "name":[
126 | {
127 | "version_range":"1.0.0-2.1.4", // semver range that is allowed
128 | "audit_trail":"Added by admin @ 2016-1-1", // who added it and when
129 | "desc":"Bad module" // why module is allowed
130 | }
131 | ]
132 | },
133 | "exclude": {
134 | "name":[
135 | {
136 | "version_range":"1.0.0-2.1.4", // semver range that is not allowed
137 | "audit_trail":"Added by admin @ 2016-1-1", // who added it and when
138 | "desc":"Bad module" // why module isn't allowed
139 | }
140 | ]
141 | }
142 | }
143 |
144 | audit (opts, function (error, report) {
145 | if (error) {
146 | throw error
147 | }
148 | Object.keys(report.fail).forEach(function (nameVersion) {
149 | console.log('FAILED', nameVersion, 'because', report.fail[nameVersion])
150 | })
151 | })
152 | ```
153 | `opts.include` and `opts.exclude` should be formatted like the `inclusions` and `exclusions`
154 | fields of a config file used in the command line tool.
155 | That is, `opts.include` and `opts.exclude` should be objects where the keys are module names
156 | and the values are lists containing objects with `version_range`, `audit_trail`, and `desc` fields.
157 |
158 | License
159 | -------
160 |
161 | See LICENSE.txt
162 |
--------------------------------------------------------------------------------
/docs/flowchart.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
128 |
--------------------------------------------------------------------------------