Skip to content

Commit

Permalink
feat(scan): scan a website for javascript vulnerabilities (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
lirantal authored Oct 6, 2019
1 parent ef4846d commit 4d9ea75
Show file tree
Hide file tree
Showing 14 changed files with 854 additions and 58 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
language: node_js
node_js:
- '8'
- '10'
- '12'
before_script:
Expand Down
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,21 @@ finds publicly known security vulnerabilities in a website's frontend JavaScript

# Install

Using `npx` to run a one-off scan of a website:
You can install globally via:

```bash
npx is-website-vulnerable example.com
npm install -g is-website-vulnerable
```

# Usage
or scan as a one-off call with npx.

```js
// @TODO
const {} = require('is-website-vulnerable')
```
# Usage

# Example
Using `npx` to run a one-off scan of a website:

<!-- TODO -->
```bash
npx is-website-vulnerable https://example.com
```

# Contributing

Expand Down
43 changes: 43 additions & 0 deletions __tests__/Audit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
jest.mock('lighthouse')
jest.mock('chrome-launcher', () => {
return {
launch() {
return {
port: '1234',
kill() {
return null
}
}
}
}
})

const lighthouse = require('lighthouse')
const { Audit } = require('../index')

describe('Audit', () => {
test('Instantiation of audit with no settings should default to reasonable scan', async () => {
const audit = new Audit()
expect(audit.settings).toEqual({
onlyAudits: ['no-vulnerable-libraries', 'js-libraries']
})
})

test('Instantiation of audit with custom settings should be allowed', async () => {
const audit = new Audit({
onlyAudits: ['no-vulnerable-libraries']
})
expect(audit.settings).toEqual({
onlyAudits: ['no-vulnerable-libraries']
})
})

test('a URL scan should result in calling lighthouse with that url', async () => {
const url = 'https://abc.com'

const audit = new Audit()
await audit.scanUrl(url)

expect(lighthouse.mock.calls[0][0]).toBe(url)
})
})
88 changes: 88 additions & 0 deletions __tests__/RenderConsole.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const { RenderConsole } = require('../index')
const chalk = require('chalk')
describe('RenderConsole', () => {
describe('Severity formatters and charts', () => {
test('severity color formatting should return expected hex color', () => {
const renderer = new RenderConsole({})

const severityColorHex = renderer.formatSeverityColor('Medium')
expect(severityColorHex).toEqual('#df8620')
})

test('severity color formatting should return low by default when unexpected color provided', () => {
const renderer = new RenderConsole({})
const severityColorHex = renderer.formatSeverityColor('asasdas')
expect(severityColorHex).toEqual('#595775')
})

test('severity chart formatter should return correctly for low severity', () => {
const renderer = new RenderConsole({})
const severity = 'Low'
const severityColorHex = renderer.formatSeverityChart(severity)
expect(severityColorHex).toEqual(`${chalk.hex('#595775').bold('■')}■■`)
})

test('severity chart formatter should return correctly for high severity', () => {
const renderer = new RenderConsole({})
const severity = 'High'
const severityColorHex = renderer.formatSeverityChart(severity)
expect(severityColorHex).toEqual(`■■${chalk.hex('##b31a6b').bold('■')}`)
})
})

describe('Vulnerability formatters', () => {
test('No vulnerability information should render an empty string', () => {
const renderer = new RenderConsole({})
const vulnInfo = renderer.formatVulnerability()
expect(vulnInfo).toEqual('')
})

test('A vulnerability information should not return an empty layout', () => {
const results = require('./fixtures/one-medium-vulnerability.json')
const renderer = new RenderConsole({})

const vulns = results.lhr.audits['no-vulnerable-libraries'].details.items

const vulnInfo = renderer.formatVulnerability(vulns[0])
expect(vulnInfo).not.toBe('')
})

test('A vulnerability information should render required items in layout', () => {
const results = require('./fixtures/one-medium-vulnerability.json')
const renderer = new RenderConsole({})

const vulns = results.lhr.audits['no-vulnerable-libraries'].details.items

const vulnInfo = renderer.formatVulnerability(vulns[0])
expect(vulnInfo.match('jQuery@1.11.2')[0]).toEqual('jQuery@1.11.2')
expect(vulnInfo.match(/2 .*vulnerabilities/)[0]).toBeTruthy()
expect(vulnInfo.match('https://snyk.io/vuln/npm:jquery')[0]).toEqual(
'https://snyk.io/vuln/npm:jquery'
)
})
})
describe('Vulnerability renderer', () => {
test('printed output should have relevant data points', () => {
const results = require('./fixtures/multiple-vulnerabilities.json')
const renderer = new RenderConsole(results)

const vulnInfo = renderer.format()
// expect(vulnInfo).toEqual('')
expect(vulnInfo.match(/14.* Total vulnerabilities/)).toBeTruthy()
expect(vulnInfo.match(/23423.* execution time/)).toBeTruthy()
})

test('printed output should also support no vulnerabilities existing', () => {
const results = require('./fixtures/no-vulns.json')
const renderer = new RenderConsole(results)

const vulnInfo = renderer.format()
expect(
vulnInfo.match(
/No JavaScript libraries detected with publicly known security vulnerabilities/
)
).toBeTruthy()
expect(vulnInfo.match(/0.* Total vulnerabilities/)).toBeTruthy()
})
})
})
3 changes: 0 additions & 3 deletions __tests__/app.test.js

This file was deleted.

86 changes: 86 additions & 0 deletions __tests__/fixtures/multiple-vulnerabilities.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"lhr": {
"finalUrl": "https://www.example.com",
"audits": {
"js-libraries": {
"id": "js-libraries",
"title": "Detected JavaScript libraries",
"description": "All front-end JavaScript libraries detected on the page. [Learn more](https://web.dev/js-libraries).",
"score": 1,
"scoreDisplayMode": "binary",
"numericValue": null,
"displayValue": null,
"explanation": null,
"errorMessage": null,
"warnings": null,
"details": {
"type": "table",
"headings": [
{ "key": "name", "itemType": "text", "text": "Name" },
{ "key": "version", "itemType": "text", "text": "Version" }
],
"items": [
{ "name": "jQuery", "version": "2.1.4", "npm": "jquery" },
{ "name": "jQuery UI", "version": "1.11.4", "npm": "jquery-ui" },
{ "name": "Leaflet", "version": "0.7.3", "npm": "leaflet" },
{ "name": "AngularJS", "version": "1.2.32", "npm": "angular" }
],
"summary": {}
}
},
"no-vulnerable-libraries": {
"id": "no-vulnerable-libraries",
"title": "Includes front-end JavaScript libraries with known security vulnerabilities",
"description": "Some third-party scripts may contain known security vulnerabilities that are easily identified and exploited by attackers. [Learn more](https://web.dev/no-vulnerable-libraries).",
"score": 0,
"scoreDisplayMode": "binary",
"numericValue": null,
"displayValue": "14 vulnerabilities detected",
"explanation": null,
"errorMessage": null,
"warnings": null,
"details": {
"type": "table",
"headings": [
{ "key": "detectedLib", "itemType": "link", "text": "Library Version" },
{ "key": "vulnCount", "itemType": "text", "text": "Vulnerability Count" },
{ "key": "highestSeverity", "itemType": "text", "text": "Highest Severity" }
],
"items": [
{
"highestSeverity": "Medium",
"vulnCount": 2,
"detectedLib": {
"text": "jQuery@2.1.4",
"url": "https://snyk.io/vuln/npm:jquery?lh=2.1.4&utm_source=lighthouse&utm_medium=ref&utm_campaign=audit",
"type": "link"
}
},
{
"highestSeverity": "High",
"vulnCount": 1,
"detectedLib": {
"text": "jQuery UI@1.11.4",
"url": "https://snyk.io/vuln/npm:jquery-ui?lh=1.11.4&utm_source=lighthouse&utm_medium=ref&utm_campaign=audit",
"type": "link"
}
},
{
"highestSeverity": "High",
"vulnCount": 11,
"detectedLib": {
"text": "AngularJS@1.2.32",
"url": "https://snyk.io/vuln/npm:angular?lh=1.2.32&utm_source=lighthouse&utm_medium=ref&utm_campaign=audit",
"type": "link"
}
}
],
"summary": {}
}
}
},
"timing": {
"total": "23423"
}
}
}
23 changes: 23 additions & 0 deletions __tests__/fixtures/no-vulns.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"lhr": {
"finalUrl": "https://www.example.com",
"audits": {
"no-vulnerable-libraries": {
"id": "no-vulnerable-libraries",
"title": "Includes front-end JavaScript libraries with known security vulnerabilities",
"description": "Some third-party scripts may contain known security vulnerabilities that are easily identified and exploited by attackers. [Learn more](https://web.dev/no-vulnerable-libraries).",
"score": 0,
"scoreDisplayMode": "binary",
"numericValue": null,
"displayValue": "2 vulnerabilities detected",
"explanation": null,
"errorMessage": null,
"warnings": null,
"details": null
}
},
"timing": {
"total": "23423"
}
}
}
42 changes: 42 additions & 0 deletions __tests__/fixtures/one-medium-vulnerability.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"lhr": {
"finalUrl": "https://www.example.com",
"audits": {
"no-vulnerable-libraries": {
"id": "no-vulnerable-libraries",
"title": "Includes front-end JavaScript libraries with known security vulnerabilities",
"description": "Some third-party scripts may contain known security vulnerabilities that are easily identified and exploited by attackers. [Learn more](https://web.dev/no-vulnerable-libraries).",
"score": 0,
"scoreDisplayMode": "binary",
"numericValue": null,
"displayValue": "2 vulnerabilities detected",
"explanation": null,
"errorMessage": null,
"warnings": null,
"details": {
"type": "table",
"headings": [
{ "key": "detectedLib", "itemType": "link", "text": "Library Version" },
{ "key": "vulnCount", "itemType": "text", "text": "Vulnerability Count" },
{ "key": "highestSeverity", "itemType": "text", "text": "Highest Severity" }
],
"items": [
{
"highestSeverity": "Medium",
"vulnCount": 2,
"detectedLib": {
"text": "jQuery@1.11.2",
"url": "https://snyk.io/vuln/npm:jquery?lh=1.11.2&utm_source=lighthouse&utm_medium=ref&utm_campaign=audit",
"type": "link"
}
}
],
"summary": {}
}
}
},
"timing": {
"total": "23423"
}
}
}
34 changes: 34 additions & 0 deletions bin/is-website-vulnerable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable no-process-exit */
'use strict'

const { Audit, RenderConsole } = require('../index')

const url = process.argv[2]
if (!url) {
console.error('')
console.error('error: please provide a URL of a website to scan')
console.error('')
console.error('Usage:')
console.error(' is-website-vulnerable https://www.google.com')
console.error('')
process.exit(1)
}

const audit = new Audit()
audit
.scanUrl(url)
.then(results => {
const renderer = new RenderConsole(results)
renderer.print()
})
.catch(error => {
console.error('')
console.log(error)

console.error('')
console.error('Usage:')
console.error(' is-website-vulnerable https://www.example.com')
console.error('')

process.exit(1)
})
8 changes: 7 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
'use strict'

module.exports = {}
const Audit = require('./src/Audit')
const RenderConsole = require('./src/RenderConsole')

module.exports = {
Audit,
RenderConsole
}
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
"security",
"vulnerabilities",
"website",
"scan"
"scan",
"lighthouse",
"audit"
],
"homepage": "https://github.com/lirantal/is-website-vulnerable",
"bugs": {
Expand All @@ -36,7 +38,10 @@
"type": "git",
"url": "https://github.com/lirantal/is-website-vulnerable.git"
},
"dependencies": {},
"dependencies": {
"chalk": "^2.4.2",
"lighthouse": "^5.5.0"
},
"devDependencies": {
"@commitlint/cli": "^7.2.1",
"@commitlint/config-conventional": "^7.1.2",
Expand Down
Loading

0 comments on commit 4d9ea75

Please sign in to comment.