-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds vulnerability badges from Snyk.io, closes #1642 - [X] Vulnerability badge for GitHub repos - [x] Vulnerability badge for npm package
- Loading branch information
1 parent
fc41b57
commit 2fe61d2
Showing
6 changed files
with
365 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
'use strict' | ||
|
||
const Joi = require('joi') | ||
const BaseSvgScrapingService = require('../base-svg-scraping') | ||
|
||
const schema = Joi.object({ | ||
message: Joi.alternatives() | ||
.try([/^\d*$/, Joi.equal('unknown')]) | ||
.required(), | ||
}).required() | ||
|
||
module.exports = class SnykVulnerabilityBase extends BaseSvgScrapingService { | ||
static render({ vulnerabilities }) { | ||
let color = 'red' | ||
if (vulnerabilities === '0') { | ||
color = 'brightgreen' | ||
} | ||
return { | ||
message: vulnerabilities, | ||
color, | ||
} | ||
} | ||
|
||
async fetch({ url, qs, errorMessages }) { | ||
const { message: vulnerabilities } = await this._requestSvg({ | ||
url, | ||
schema, | ||
options: { | ||
qs, | ||
}, | ||
errorMessages, | ||
}) | ||
|
||
return { vulnerabilities } | ||
} | ||
|
||
static get category() { | ||
return 'quality' | ||
} | ||
|
||
static get defaultBadgeData() { | ||
return { | ||
label: 'vulnerabilities', | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
'use strict' | ||
|
||
const SynkVulnerabilityBase = require('./snyk-vulnerability-base') | ||
|
||
module.exports = class SnykVulnerabilityGitHub extends SynkVulnerabilityBase { | ||
static get route() { | ||
return { | ||
base: 'snyk/vulnerabilities/github', | ||
format: '([^/]+)/([^/]+)(?:/(.+))?', | ||
capture: ['user', 'repo', 'manifestFilePath'], | ||
} | ||
} | ||
|
||
static get examples() { | ||
return [ | ||
{ | ||
title: 'Snyk Vulnerabilities for GitHub Repo', | ||
pattern: ':user/:repo', | ||
namedParams: { | ||
user: 'badges', | ||
repo: 'shields', | ||
}, | ||
staticExample: this.render({ vulnerabilities: '0' }), | ||
}, | ||
{ | ||
title: 'Snyk Vulnerabilities for GitHub Repo (Specific Manifest)', | ||
pattern: ':user/:repo/:manifestFilePath', | ||
namedParams: { | ||
user: 'badges', | ||
repo: 'shields', | ||
manifestFilePath: 'gh-badges/package.json', | ||
}, | ||
staticExample: this.render({ vulnerabilities: '0' }), | ||
documentation: ` | ||
<p> | ||
Provide the path to your target manifest file relative to the base of your repository. | ||
Snyk does not support using a specific branch for this, so do not include "blob" nor a branch name. | ||
</p> | ||
`, | ||
}, | ||
] | ||
} | ||
|
||
async handle({ user, repo, manifestFilePath }) { | ||
const url = `https://snyk.io/test/github/${user}/${repo}/badge.svg` | ||
const qs = { targetFile: manifestFilePath } | ||
const { vulnerabilities } = await this.fetch({ | ||
url, | ||
qs, | ||
errorMessages: { | ||
404: 'repo or manifest not found', | ||
}, | ||
}) | ||
return this.constructor.render({ vulnerabilities }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
'use strict' | ||
|
||
const Joi = require('joi') | ||
const t = (module.exports = require('../create-service-tester')()) | ||
const { colorScheme } = require('../test-helpers') | ||
const { | ||
twoVulnerabilitiesSvg, | ||
zeroVulnerabilitiesSvg, | ||
} = require('./snyk-test-helpers') | ||
|
||
t.create('live: valid repo') | ||
.get('/badges/shields.json') | ||
.expectJSONTypes( | ||
Joi.object().keys({ | ||
name: 'vulnerabilities', | ||
value: Joi.number().required(), | ||
}) | ||
) | ||
|
||
t.create('live: non existent repo') | ||
.get('/badges/not-real.json') | ||
.timeout(10000) | ||
.expectJSON({ name: 'vulnerabilities', value: 'repo or manifest not found' }) | ||
|
||
t.create('live: valid target manifest path') | ||
.get('/badges/shields/gh-badges/package.json.json') | ||
.expectJSONTypes( | ||
Joi.object().keys({ | ||
name: 'vulnerabilities', | ||
value: Joi.number().required(), | ||
}) | ||
) | ||
|
||
t.create('live: invalid target manifest path') | ||
.get('/badges/shields/gh-badges/requirements.txt.json') | ||
.expectJSON({ name: 'vulnerabilities', value: 'repo or manifest not found' }) | ||
|
||
t.create('repo has no vulnerabilities') | ||
.get('/badges/shields.json?style=_shields_test') | ||
.intercept(nock => | ||
nock('https://snyk.io/test/github/badges/shields') | ||
.get('/badge.svg') | ||
.reply(200, zeroVulnerabilitiesSvg) | ||
) | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: '0', | ||
colorB: colorScheme.brightgreen, | ||
}) | ||
|
||
t.create('repo has vulnerabilities') | ||
.get('/badges/shields.json?style=_shields_test') | ||
.intercept(nock => | ||
nock('https://snyk.io/test/github/badges/shields') | ||
.get('/badge.svg') | ||
.reply(200, twoVulnerabilitiesSvg) | ||
) | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: '2', | ||
colorB: colorScheme.red, | ||
}) | ||
|
||
t.create('target manifest file has no vulnerabilities') | ||
.get('/badges/shields/gh-badges/package.json.json?style=_shields_test') | ||
.intercept(nock => | ||
nock('https://snyk.io/test/github/badges/shields') | ||
.get('/badge.svg') | ||
.query({ | ||
targetFile: 'gh-badges/package.json', | ||
}) | ||
.reply(200, zeroVulnerabilitiesSvg) | ||
) | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: '0', | ||
colorB: colorScheme.brightgreen, | ||
}) | ||
|
||
t.create('target manifest file has vulnerabilities') | ||
.get('/badges/shields/gh-badges/package.json.json?style=_shields_test') | ||
.intercept(nock => | ||
nock('https://snyk.io/test/github/badges/shields') | ||
.get('/badge.svg') | ||
.query({ | ||
targetFile: 'gh-badges/package.json', | ||
}) | ||
.reply(200, twoVulnerabilitiesSvg) | ||
) | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: '2', | ||
colorB: colorScheme.red, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
'use strict' | ||
|
||
const { NotFound } = require('../errors') | ||
const SynkVulnerabilityBase = require('./snyk-vulnerability-base') | ||
|
||
module.exports = class SnykVulnerabilityNpm extends SynkVulnerabilityBase { | ||
static get route() { | ||
return { | ||
base: 'snyk/vulnerabilities/npm', | ||
pattern: ':packageName(.+)', | ||
} | ||
} | ||
|
||
static get examples() { | ||
return [ | ||
{ | ||
title: 'Snyk Vulnerabilities for npm package', | ||
pattern: ':packageName', | ||
namedParams: { | ||
packageName: 'mocha', | ||
}, | ||
staticExample: this.render({ vulnerabilities: '0' }), | ||
}, | ||
{ | ||
title: 'Snyk Vulnerabilities for npm package version', | ||
pattern: ':packageName', | ||
namedParams: { | ||
packageName: '[email protected]', | ||
}, | ||
staticExample: this.render({ vulnerabilities: '1' }), | ||
}, | ||
{ | ||
title: 'Snyk Vulnerabilities for npm scoped package', | ||
pattern: ':packageName', | ||
namedParams: { | ||
packageName: '@babel/core', | ||
}, | ||
staticExample: this.render({ vulnerabilities: '0' }), | ||
}, | ||
] | ||
} | ||
|
||
async handle({ packageName }) { | ||
const url = `https://snyk.io/test/npm/${packageName}/badge.svg` | ||
|
||
try { | ||
const { vulnerabilities } = await this.fetch({ | ||
url, | ||
// Snyk returns an HTTP 200 with an HTML page when the specified | ||
// npm package is not found that contains the text 404. | ||
// Including this in case Snyk starts returning a 404 response code instead. | ||
errorMessages: { | ||
404: 'npm package is invalid or does not exist', | ||
}, | ||
}) | ||
return this.constructor.render({ vulnerabilities }) | ||
} catch (e) { | ||
// If the package is invalid/nonexistent Snyk will return an HTML page | ||
// which will result in an InvalidResponse error being thrown by the valueFromSvgBadge() | ||
// function. Catching it here to switch to a more contextualized error message. | ||
throw new NotFound({ | ||
prettyMessage: 'npm package is invalid or does not exist', | ||
}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
'use strict' | ||
|
||
const Joi = require('joi') | ||
const t = (module.exports = require('../create-service-tester')()) | ||
const { colorScheme } = require('../test-helpers') | ||
const { | ||
twoVulnerabilitiesSvg, | ||
zeroVulnerabilitiesSvg, | ||
} = require('./snyk-test-helpers') | ||
|
||
t.create('live: valid package latest version') | ||
.get('/mocha.json') | ||
.timeout(7500) | ||
.expectJSONTypes( | ||
Joi.object().keys({ | ||
name: 'vulnerabilities', | ||
value: Joi.number().required(), | ||
}) | ||
) | ||
|
||
t.create('live: valid scoped package latest version') | ||
.get('/@babel/core.json') | ||
.timeout(7500) | ||
.expectJSONTypes( | ||
Joi.object().keys({ | ||
name: 'vulnerabilities', | ||
value: Joi.number().required(), | ||
}) | ||
) | ||
|
||
t.create('live: non existent package') | ||
.get('/mochaabcdef.json') | ||
.timeout(7500) | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: 'npm package is invalid or does not exist', | ||
}) | ||
|
||
t.create('live: valid package specific version') | ||
.get('/[email protected]?style=_shields_test') | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: '1', | ||
colorB: colorScheme.red, | ||
}) | ||
|
||
t.create('live: non existent package version') | ||
.get('/[email protected]') | ||
.timeout(7500) | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: 'npm package is invalid or does not exist', | ||
}) | ||
|
||
t.create('package has no vulnerabilities') | ||
.get('/mocha.json?style=_shields_test') | ||
.intercept(nock => | ||
nock('https://snyk.io/test/npm/mocha') | ||
.get('/badge.svg') | ||
.reply(200, zeroVulnerabilitiesSvg) | ||
) | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: '0', | ||
colorB: colorScheme.brightgreen, | ||
}) | ||
|
||
t.create('package has vulnerabilities') | ||
.get('/mocha.json?style=_shields_test') | ||
.intercept(nock => | ||
nock('https://snyk.io/test/npm/mocha') | ||
.get('/badge.svg') | ||
.reply(200, twoVulnerabilitiesSvg) | ||
) | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: '2', | ||
colorB: colorScheme.red, | ||
}) | ||
|
||
t.create('package not found') | ||
.get('/[email protected]?style=_shields_test') | ||
.intercept(nock => | ||
nock('https://snyk.io/test/npm/[email protected]') | ||
.get('/badge.svg') | ||
.reply(200, '<html>foo</html>') | ||
) | ||
.expectJSON({ | ||
name: 'vulnerabilities', | ||
value: 'npm package is invalid or does not exist', | ||
colorB: colorScheme.red, | ||
}) |