Skip to content

Commit

Permalink
Add [Snyk] badges (#2566)
Browse files Browse the repository at this point in the history
Adds vulnerability badges from Snyk.io, closes #1642 

- [X] Vulnerability badge for GitHub repos
- [x] Vulnerability badge for npm package
  • Loading branch information
calebcartwright authored and paulmelnikow committed Dec 20, 2018
1 parent fc41b57 commit 2fe61d2
Show file tree
Hide file tree
Showing 6 changed files with 365 additions and 0 deletions.
11 changes: 11 additions & 0 deletions services/snyk/snyk-test-helpers.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions services/snyk/snyk-vulnerability-base.js
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',
}
}
}
56 changes: 56 additions & 0 deletions services/snyk/snyk-vulnerability-github.service.js
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 })
}
}
94 changes: 94 additions & 0 deletions services/snyk/snyk-vulnerability-github.tester.js
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,
})
66 changes: 66 additions & 0 deletions services/snyk/snyk-vulnerability-npm.service.js
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',
})
}
}
}
92 changes: 92 additions & 0 deletions services/snyk/snyk-vulnerability-npm.tester.js
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,
})

0 comments on commit 2fe61d2

Please sign in to comment.