├── .github ├── snyk-logo.png ├── pie-my-vulns-screenshot.png ├── ISSUE_TEMPLATE │ ├── 3-help.md │ ├── 2-feature-request.md │ └── 1-bug-report.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── pie-my-vulns-logo.svg ├── Dockerfile ├── .prettierrc.js ├── jest.e2e.js ├── __tests__ ├── __e2e__ │ ├── project3 │ │ ├── package.json │ │ └── package-lock.json │ ├── project1 │ │ ├── package.json │ │ └── package-lock.json │ ├── project2 │ │ └── package.json │ └── cli.e2e.js ├── Reporters │ └── DependencyTypeReporter.test.js └── Audit.test.js ├── jest.default.js ├── jsdoc.json ├── codefresh.yml ├── src ├── Parsers │ ├── SeverityParser.js │ ├── DependencyTypeParser.js │ └── RemediationTypeParser.js ├── Reporters │ ├── DependencyTypeReporter.js │ ├── SeverityReporter.js │ └── RemediationTypeReporter.js └── Audit.js ├── .dockerignore ├── CONTRIBUTING.md ├── .gitignore ├── SECURITY.md ├── README.md ├── bin └── pie-my-vulns.js ├── CODE_OF_CONDUCT.md ├── .circleci └── config.yml ├── package.json └── LICENSE /.github/snyk-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lirantal/pie-my-vulns/HEAD/.github/snyk-logo.png -------------------------------------------------------------------------------- /.github/pie-my-vulns-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lirantal/pie-my-vulns/HEAD/.github/pie-my-vulns-screenshot.png -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.16.2-alpine3.9 2 | WORKDIR /app 3 | COPY . /app 4 | RUN npm ci --production 5 | USER node 6 | ENTRYPOINT node ./bin/pie-my-vulns.js --directory=/tmp/tested-app 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-help.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '⁉️ Need help?' 3 | about: Please describe the problem. 4 | --- 5 | 6 | 9 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | singleQuote: true, 5 | semi: false, 6 | trailingComma: 'none', 7 | useTabs: false, 8 | bracketSpacing: true 9 | } 10 | -------------------------------------------------------------------------------- /jest.e2e.js: -------------------------------------------------------------------------------- 1 | const jestConfig = require('./jest.default') 2 | jestConfig.testMatch = ['**/__e2e__/*.e2e.js'] 3 | jestConfig.collectCoverage = false 4 | jestConfig.coverageThreshold = null 5 | jestConfig.testTimeout = 30000 6 | module.exports = jestConfig 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | - **Library Version**: 8 | - **OS**: 9 | - **Node.js Version**: 10 | 11 | 12 | -------------------------------------------------------------------------------- /__tests__/__e2e__/project3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project3", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "react": "^16.12.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jest.default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | verbose: true, 4 | notify: true, 5 | collectCoverage: true, 6 | coverageThreshold: { 7 | global: { 8 | branches: 80, 9 | functions: 80, 10 | lines: 80, 11 | statements: 80 12 | } 13 | }, 14 | testPathIgnorePatterns: ['/__tests__/.*/__fixtures__/.*'], 15 | collectCoverageFrom: ['index.js', 'src/**/*.{js,ts}'], 16 | testMatch: ['**/*.test.js'] 17 | } 18 | -------------------------------------------------------------------------------- /__tests__/__e2e__/project1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "helmet": "^2.3.0" 14 | }, 15 | "devDependencies": { 16 | "timespan": "^2.3.0", 17 | "uglify-js": "^2.4.24" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /__tests__/__e2e__/project2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project1", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "helmet": "^2.3.0" 14 | }, 15 | "devDependencies": { 16 | "timespan": "^2.3.0", 17 | "uglify-js": "^2.4.24" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": ["jsdoc", "closure"] 5 | }, 6 | "source": { 7 | "include": ["./index.js"], 8 | "exclude": ["./node_modules", "./.nyc_output", "./coverage", "./out", "./tests"], 9 | "includePattern": ".+\\.js(doc|x)?$", 10 | "excludePattern": "(^|\\/|\\\\)_" 11 | }, 12 | "plugins": [], 13 | "templates": {}, 14 | "opts": { 15 | "destination": "./out", 16 | "recurse": true, 17 | "private": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature request" 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | Please describe the problem you are trying to solve. 14 | 15 | **Describe the solution you'd like** 16 | Please describe the desired behavior. 17 | 18 | **Describe alternatives you've considered** 19 | Please describe alternative solutions or features you have considered. 20 | -------------------------------------------------------------------------------- /codefresh.yml: -------------------------------------------------------------------------------- 1 | version: '1.0' 2 | steps: 3 | main_clone: 4 | type: git-clone 5 | repo: lirantal/pie-my-vulns 6 | BuildingDockerImage: 7 | title: Building Docker Image 8 | type: build 9 | registry: dockerhub 10 | image_name: lirantal/pie-my-vulns 11 | working_directory: ./ 12 | dockerfile: Dockerfile 13 | tag: '${{CF_BRANCH_TAG_NORMALIZED}}' 14 | PushToRegistry: 15 | title: Pushing to Docker Registry 16 | type: push 17 | registry: dockerhub 18 | candidate: '${{BuildingDockerImage}}' 19 | tags: 20 | - master 21 | - latest 22 | when: 23 | branch: 24 | only: 25 | - master 26 | stages: [] 27 | -------------------------------------------------------------------------------- /src/Parsers/SeverityParser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class VulnerabilitySeverityParser { 4 | constructor(vulnsData) { 5 | this.vulnsData = vulnsData 6 | this.vulnsMap = { 7 | high: 0, 8 | medium: 0, 9 | low: 0 10 | } 11 | } 12 | 13 | parse() { 14 | if (!this.vulnsData.vulnerabilities) { 15 | return this.vulnsMap 16 | } 17 | 18 | this.vulnsData.vulnerabilities.map(vulnItem => { 19 | this.vulnsMap[vulnItem.severity] += 1 20 | }) 21 | 22 | return this.vulnsMap 23 | } 24 | 25 | getHighSeverityCount() { 26 | return this.vulnsMap['high'] 27 | } 28 | 29 | getMediumSeverityCount() { 30 | return this.vulnsMap['medium'] 31 | } 32 | 33 | getLowSeverityCount() { 34 | return this.vulnsMap['low'] 35 | } 36 | } 37 | 38 | module.exports = VulnerabilitySeverityParser 39 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Directory for instrumented libs generated by jscoverage/JSCover 9 | lib-cov 10 | 11 | # Coverage directory used by tools like istanbul 12 | coverage 13 | 14 | # Dependency directories 15 | node_modules/ 16 | jspm_packages/ 17 | 18 | # Optional npm cache directory 19 | .npm 20 | 21 | # Optional eslint cache 22 | .eslintcache 23 | 24 | # Optional REPL history 25 | .node_repl_history 26 | 27 | # Output of 'npm pack' 28 | *.tgz 29 | 30 | # Yarn Integrity file 31 | .yarn-integrity 32 | 33 | # dotenv environment variables file 34 | .env 35 | .env.test 36 | 37 | # Tests 38 | __tests__ 39 | jest* 40 | 41 | # Open source licence 42 | LICENCE 43 | 44 | # MD files 45 | *.md 46 | 47 | # Documentation and formatting 48 | .prettierrc.js 49 | jsdoc.json 50 | 51 | # Source control and CI/CD 52 | .circleci/ 53 | .github/ 54 | .gitignore -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to pie-my-vulns. 6 | These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 7 | 8 | ## Code of Conduct 9 | 10 | This project and everyone participating in it is governed by a [Code of Conduct](./CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. 11 | 12 | ## How to contribute to pie-my-vulns 13 | 14 | 15 | 16 | ### Tests 17 | 18 | Make sure you the code you're adding has decent test coverage. 19 | 20 | Running project tests and coverage: 21 | 22 | ```bash 23 | npm run test 24 | ``` 25 | 26 | ### Commit Guidelines 27 | 28 | The project uses the commitizen tool for standardizing changelog style commit and a git pre-commit hook to enforce them. 29 | -------------------------------------------------------------------------------- /src/Parsers/DependencyTypeParser.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-prototype-builtins */ 2 | 'use strict' 3 | 4 | class DependencyTypeParser { 5 | constructor(vulnsData) { 6 | this.vulnsData = vulnsData 7 | this.vulnsMap = { 8 | prodDependency: 0, 9 | devDependency: 0 10 | } 11 | } 12 | 13 | parse() { 14 | if (!this.vulnsData.vulnerabilities) { 15 | return this.vulnsMap 16 | } 17 | 18 | const vulnItemScarce = this.vulnsData.vulnerabilities[0] 19 | if (vulnItemScarce.hasOwnProperty('parentDepType') !== true) { 20 | return false 21 | } 22 | 23 | this.vulnsData.vulnerabilities.map(vulnItem => { 24 | if (vulnItem.parentDepType === 'prod') { 25 | this.vulnsMap.prodDependency += 1 26 | } else { 27 | this.vulnsMap.devDependency += 1 28 | } 29 | }) 30 | } 31 | 32 | getProdDependencyCount() { 33 | return this.vulnsMap['prodDependency'] 34 | } 35 | 36 | getDevDependencyCount() { 37 | return this.vulnsMap['devDependency'] 38 | } 39 | } 40 | 41 | module.exports = DependencyTypeParser 42 | -------------------------------------------------------------------------------- /src/Parsers/RemediationTypeParser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class RemediationTypeParser { 4 | constructor(vulnsData) { 5 | this.vulnsData = vulnsData 6 | this.vulnsMap = { 7 | isPatchable: 0, 8 | isUpgradable: 0, 9 | none: 0 10 | } 11 | } 12 | 13 | parse() { 14 | if (!this.vulnsData.vulnerabilities) { 15 | return this.vulnsMap 16 | } 17 | 18 | this.vulnsData.vulnerabilities.map(vulnItem => { 19 | if (vulnItem.isUpgradable === true) { 20 | this.vulnsMap['isUpgradable'] += 1 21 | return true 22 | } 23 | 24 | if (vulnItem.isPatchable === true) { 25 | this.vulnsMap['isPatchable'] += 1 26 | return true 27 | } 28 | 29 | this.vulnsMap['none'] += 1 30 | return true 31 | }) 32 | 33 | return this.vulnsMap 34 | } 35 | 36 | getUpgradableCount() { 37 | return this.vulnsMap['isUpgradable'] 38 | } 39 | 40 | getPatchableCount() { 41 | return this.vulnsMap['isPatchable'] 42 | } 43 | 44 | getNoRemediationCount() { 45 | return this.vulnsMap['none'] 46 | } 47 | } 48 | 49 | module.exports = RemediationTypeParser 50 | -------------------------------------------------------------------------------- /src/Reporters/DependencyTypeReporter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Pie = require('cli-pie') 4 | const DependencyTypeParser = require('../Parsers/DependencyTypeParser') 5 | const DEFAULT_PIE_SIZE = 4 6 | 7 | class DependencyTypeReporter { 8 | constructor({ data, pieSize, colorFul }) { 9 | this.options = { 10 | pieSize: pieSize || DEFAULT_PIE_SIZE, 11 | colorFul: !!colorFul 12 | } 13 | this.data = data 14 | } 15 | 16 | getTitle() { 17 | return `Vulnerabilities by dependency source:` 18 | } 19 | 20 | getResult() { 21 | const depTypeParser = new DependencyTypeParser(this.data) 22 | const res = depTypeParser.parse() 23 | if (res === false) { 24 | return false 25 | } 26 | 27 | var pieChart = new Pie( 28 | this.options.pieSize, 29 | [ 30 | { 31 | label: 'Production Dependencies', 32 | value: depTypeParser.getProdDependencyCount(), 33 | color: [0, 255, 0] 34 | }, 35 | { 36 | label: 'Development Dependencies', 37 | value: depTypeParser.getDevDependencyCount(), 38 | color: [0, 0, 255] 39 | } 40 | ], 41 | { 42 | legend: true, 43 | flat: true, 44 | no_ansi: !this.options.colorFul 45 | } 46 | ) 47 | 48 | return pieChart.toString() 49 | } 50 | } 51 | 52 | module.exports = DependencyTypeReporter 53 | -------------------------------------------------------------------------------- /src/Reporters/SeverityReporter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Pie = require('cli-pie') 4 | const VulnerabilitySeverityParser = require('../Parsers/SeverityParser') 5 | const DEFAULT_PIE_SIZE = 4 6 | 7 | class SeverityReporter { 8 | constructor({ data, pieSize, colorFul }) { 9 | this.options = { 10 | pieSize: pieSize || DEFAULT_PIE_SIZE, 11 | colorFul: !!colorFul 12 | } 13 | this.data = data 14 | } 15 | 16 | getTitle() { 17 | return `Vulnerabilities by severity:` 18 | } 19 | 20 | getResult() { 21 | const vulnSeverityParser = new VulnerabilitySeverityParser(this.data) 22 | vulnSeverityParser.parse() 23 | 24 | var pieChart = new Pie( 25 | this.options.pieSize, 26 | [ 27 | { 28 | label: 'High severity', 29 | value: vulnSeverityParser.getHighSeverityCount(), 30 | color: [255, 0, 255] 31 | }, 32 | { 33 | label: 'Medium severity', 34 | value: vulnSeverityParser.getMediumSeverityCount(), 35 | color: [179, 26, 107] 36 | }, 37 | { 38 | label: 'Low severity', 39 | value: vulnSeverityParser.getLowSeverityCount(), 40 | color: [89, 87, 117] 41 | } 42 | ], 43 | { 44 | legend: true, 45 | flat: true, 46 | no_ansi: !this.options.colorFul 47 | } 48 | ) 49 | 50 | return pieChart.toString() 51 | } 52 | } 53 | 54 | module.exports = SeverityReporter 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a bug report 4 | --- 5 | 6 | 7 | 8 | ## Expected Behavior 9 | 10 | 11 | 12 | 13 | ## Current Behavior 14 | 15 | 16 | 17 | 18 | ## Possible Solution 19 | 20 | 21 | 22 | 23 | ## Steps to Reproduce (for bugs) 24 | 25 | 26 | 27 | 28 | 1. 29 | 2. 30 | 3. 31 | 4. 32 | 33 | ## Context 34 | 35 | 36 | 37 | 38 | ## Your Environment 39 | 40 | 41 | 42 | - Library Version used: 43 | - Node.js version (e.g. Node.js 5.4): 44 | - Operating System and version (desktop or mobile): 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | .dynamodb/ 81 | -------------------------------------------------------------------------------- /src/Reporters/RemediationTypeReporter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Pie = require('cli-pie') 4 | const RemediationTypeParser = require('../Parsers/RemediationTypeParser') 5 | const DEFAULT_PIE_SIZE = 4 6 | 7 | class RemediationTypeReporter { 8 | constructor({ data, pieSize, colorFul }) { 9 | this.options = { 10 | pieSize: pieSize || DEFAULT_PIE_SIZE, 11 | colorFul: !!colorFul 12 | } 13 | this.data = data 14 | } 15 | 16 | getTitle() { 17 | return `Vulnerabilities by remediation action:` 18 | } 19 | 20 | getResult() { 21 | const vulnSeverityParser = new RemediationTypeParser(this.data) 22 | vulnSeverityParser.parse() 23 | 24 | var pieChart = new Pie( 25 | this.options.pieSize, 26 | [ 27 | { 28 | label: 'Upgradable vulnerabilities', 29 | value: vulnSeverityParser.getUpgradableCount(), 30 | color: [0, 255, 0] 31 | }, 32 | { 33 | label: 'Patchable vulnerabilities', 34 | value: vulnSeverityParser.getPatchableCount(), 35 | color: [0, 0, 255] 36 | }, 37 | { 38 | label: 'No remediation available', 39 | value: vulnSeverityParser.getNoRemediationCount(), 40 | color: [255, 0, 50] 41 | } 42 | ], 43 | { 44 | legend: true, 45 | flat: true, 46 | no_ansi: !this.options.colorFul 47 | } 48 | ) 49 | 50 | return pieChart.toString() 51 | } 52 | } 53 | 54 | module.exports = RemediationTypeReporter 55 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Responsible disclosure security policy 4 | 5 | A responsible disclosure policy helps protect users of the project from publicly disclosed security vulnerabilities without a fix by employing a process where vulnerabilities are first triaged in a private manner, and only publicly disclosed after a reasonable time period that allows patching the vulnerability and provides an upgrade path for users. 6 | 7 | When contacting us directly via email, we will do our best efforts to respond in a reasonable time to resolve the issue. When contacting a security program their disclosure policy will provide details on time-frame, processes and paid bounties. 8 | 9 | We kindly ask you to refrain from malicious acts that put our users, the project, or any of the project’s team members at risk. 10 | 11 | ## Reporting a security issue 12 | 13 | We consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present. 14 | 15 | If you discover a security vulnerability, please use one of the following means of communications to report it to us: 16 | 17 | - Report the security issue to the Node.js Security WG through the [HackerOne program](https://hackerone.com/nodejs-ecosystem) for ecosystem modules on npm, or to [Snyk Security Team](https://snyk.io/vulnerability-disclosure). They will help triage the security issue and work with all involved parties to remediate and release a fix. 18 | 19 | Note that time-frame and processes are subject to each program’s own policy. 20 | 21 | - Report the security issue to the project maintainers directly. 22 | 23 | Your efforts to responsibly disclose your findings are sincerely appreciated and will be taken into account to acknowledge your contributions. 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Types of changes 8 | 9 | 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 14 | 15 | ## Related Issue 16 | 17 | 18 | 19 | 20 | 21 | 22 | ## Motivation and Context 23 | 24 | 25 | 26 | ## How Has This Been Tested? 27 | 28 | 29 | 30 | 31 | 32 | 33 | ## Screenshots (if appropriate): 34 | 35 | ## Checklist: 36 | 37 | 38 | 39 | 40 | - [ ] I have updated the documentation (if required). 41 | - [ ] I have read the **CONTRIBUTING** document. 42 | - [ ] I have added tests to cover my changes. 43 | - [ ] All new and existing tests passed. 44 | - [ ] I added a picture of a cute animal cause it's fun 45 | -------------------------------------------------------------------------------- /__tests__/__e2e__/project3/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project3", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "js-tokens": { 8 | "version": "4.0.0", 9 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 10 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 11 | }, 12 | "loose-envify": { 13 | "version": "1.4.0", 14 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 15 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 16 | "requires": { 17 | "js-tokens": "^3.0.0 || ^4.0.0" 18 | } 19 | }, 20 | "object-assign": { 21 | "version": "4.1.1", 22 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 23 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 24 | }, 25 | "prop-types": { 26 | "version": "15.7.2", 27 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", 28 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", 29 | "requires": { 30 | "loose-envify": "^1.4.0", 31 | "object-assign": "^4.1.1", 32 | "react-is": "^16.8.1" 33 | } 34 | }, 35 | "react": { 36 | "version": "16.12.0", 37 | "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", 38 | "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", 39 | "requires": { 40 | "loose-envify": "^1.1.0", 41 | "object-assign": "^4.1.1", 42 | "prop-types": "^15.6.2" 43 | } 44 | }, 45 | "react-is": { 46 | "version": "16.12.0", 47 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", 48 | "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /__tests__/Reporters/DependencyTypeReporter.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable security/detect-object-injection */ 2 | const DepTypeReporter = require('../../src/Reporters/DependencyTypeReporter') 3 | const RemediationTypeReporter = require('../../src/Reporters/RemediationTypeReporter') 4 | const SevReporter = require('../../src/Reporters/SeverityReporter') 5 | 6 | const reporterFunctions = [ 7 | ['DepTypeReporter', DepTypeReporter], 8 | ['RemediationTypeReporter', RemediationTypeReporter], 9 | ['SevReporter', SevReporter] 10 | ] 11 | 12 | const data = require('../__fixtures__/output.json') 13 | 14 | describe('Reporters', () => { 15 | describe('Reporter instantiation', () => { 16 | test.each(reporterFunctions)( 17 | '%s: constructor correctly sets data', 18 | (reporterName, Reporter) => { 19 | const reporter = new Reporter({ 20 | data, 21 | pieSize: 1, 22 | colorFul: false 23 | }) 24 | 25 | expect(reporter.options.pieSize).toBe(1) 26 | expect(reporter.options.colorFul).toBe(false) 27 | } 28 | ) 29 | }) 30 | 31 | describe('Reporter titles', () => { 32 | const expectedReporterTitle = { 33 | DepTypeReporter: 'Vulnerabilities by dependency source:', 34 | RemediationTypeReporter: 'Vulnerabilities by remediation action:', 35 | SevReporter: 'Vulnerabilities by severity:' 36 | } 37 | 38 | test.each(reporterFunctions)('%s: correctly returns a title', (reporterName, Reporter) => { 39 | const reporter = new Reporter({ 40 | data 41 | }) 42 | 43 | expect(reporter.getTitle()).toBe(expectedReporterTitle[reporterName]) 44 | }) 45 | }) 46 | 47 | describe('Reporter results', () => { 48 | const expectedStringInText = { 49 | DepTypeReporter: [ 50 | /Production Dependencies \(14\.75%\)/, 51 | /Development Dependencies \(85\.25%\)/ 52 | ], 53 | RemediationTypeReporter: [ 54 | /Upgradable vulnerabilities \(53\.92%\)/, 55 | /Patchable vulnerabilities \(9\.22%\)/, 56 | /No remediation available \(36\.87%\)/ 57 | ], 58 | SevReporter: [ 59 | /High severity \(43\.32%\)/, 60 | /Medium severity \(41\.01%\)/, 61 | /Low severity \(15\.67%\)/ 62 | ] 63 | } 64 | 65 | test.each(reporterFunctions)('%s: returns a piechart string', (reporterName, Reporter) => { 66 | const reporter = new Reporter({ 67 | data 68 | }) 69 | 70 | const pieChartString = reporter.getResult() 71 | 72 | expectedStringInText[reporterName].forEach(regexPattern => { 73 | expect(pieChartString).toMatch(regexPattern) 74 | }) 75 | }) 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | pie-my-vulns 3 |

4 | 5 |

6 | Visualize your project security vulnerabilities as a pie chart in the terminal 7 |

8 | 9 |

10 | npm version 11 | license 12 | downloads 13 | build 14 | codecov 15 | Known Vulnerabilities 16 | Responsible Disclosure Policy 17 |

18 | 19 |

20 | Screenshot of npm module called pie-my-vulns that draws pie charts in the terminal reflecting security vulnerabilities found in JavaScript and Node.js projects based on Snyk vulnerability database 21 | 22 |

23 |

Many thanks to for supporting open source security

24 | 25 |

26 | 27 | # About 28 | 29 | Visualize your project security vulnerabilities as a pie chart in the terminal 30 | 31 | # Usage 32 | 33 | ## Command line 34 | 35 | Using Node.js's npx command to run a one-off scan inside a project's directory: 36 | 37 | ```bash 38 | npx pie-my-vulns 39 | ``` 40 | 41 | To scan a specific project directory use the `--directory` option, for example: 42 | 43 | ```bash 44 | npx pie-my-vulns --directory=path/to/project/dir 45 | ``` 46 | 47 | To pipe existing Snyk json (obtained via `snyk test --json`) through stdin 48 | 49 | ```bash 50 | cat snyk.json | npx pie-my-vulns 51 | ``` 52 | 53 | ## Docker container 54 | 55 | Using docker to run a one-off scan inside a docker container. 56 | Pass the directory to scan to the `source` environment variable: 57 | 58 | ``` 59 | docker run --mount type=bind,source=,target=/tmp/tested-app pie-my-vulns 60 | ``` 61 | 62 | # Install 63 | 64 | You can install globally via: 65 | 66 | ```bash 67 | npm install -g pie-my-vulns 68 | ``` 69 | 70 | # Contributing 71 | 72 | Please consult [CONTRIBUTING](./CONTRIBUTING.md) for guidelines on contributing to this project. 73 | 74 | # Author 75 | 76 | **pie-my-vulns** © [Liran Tal](https://github.com/lirantal), Released under the [Apache-2.0](./LICENSE) License. 77 | -------------------------------------------------------------------------------- /bin/pie-my-vulns.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-process-exit */ 3 | 'use strict' 4 | const parseArgs = require('minimist') 5 | const fs = require('fs') 6 | const debug = require('debug')('pie-my-vulns') 7 | const Audit = require('../src/Audit') 8 | const SeverityReporter = require('../src/Reporters/SeverityReporter') 9 | const RemediationTypeReporter = require('../src/Reporters/RemediationTypeReporter') 10 | const DependencyTypeReporter = require('../src/Reporters/DependencyTypeReporter') 11 | 12 | const EXIT_CODE_ERROR = 1 13 | const EXIT_CODE_VULNS = 2 14 | const EXIT_CODE_VULNS_NONE = 0 15 | const STDIN_FILE_DESCRIPTOR = 0 16 | const reportsList = [SeverityReporter, DependencyTypeReporter, RemediationTypeReporter] 17 | 18 | async function main() { 19 | let vulnerabilitiesResult = '' 20 | 21 | try { 22 | // Let's check if there's any input being piped from stdin 23 | if (fs.fstatSync(STDIN_FILE_DESCRIPTOR).isFIFO()) { 24 | vulnerabilitiesResult = await readFromStdin() 25 | } else { 26 | const argv = parseArgs(process.argv.slice(2)) 27 | const { directory } = argv 28 | const audit = new Audit() 29 | vulnerabilitiesResult = await audit.test({ directory }) 30 | } 31 | } catch (error) { 32 | printError(error) 33 | } 34 | 35 | console.log() 36 | 37 | if (!isVulnerabilitiesDetected(vulnerabilitiesResult)) { 38 | console.log('0 vulnerabilities found') 39 | process.exit(EXIT_CODE_VULNS_NONE) 40 | } 41 | 42 | reportsList.forEach(Reporter => { 43 | const reporter = new Reporter({ 44 | data: vulnerabilitiesResult, 45 | colorFul: true 46 | }) 47 | 48 | const stdoutText = reporter.getResult() 49 | if (stdoutText !== false) { 50 | console.log(reporter.getTitle()) 51 | console.log(stdoutText) 52 | console.log() 53 | } 54 | }) 55 | 56 | console.log('Summary:') 57 | console.log( 58 | ` - [${vulnerabilitiesResult.vulnerabilities.length}] Total number of vulnerabilities found` 59 | ) 60 | console.log( 61 | ` - [${vulnerabilitiesResult.dependencyCount}] Total number of dependencies scanned\n` 62 | ) 63 | 64 | process.exit(EXIT_CODE_VULNS) 65 | } 66 | 67 | async function readFromStdin() { 68 | return new Promise(resolve => { 69 | let data = '' 70 | const stdin = process.stdin 71 | stdin.on('readable', function() { 72 | const chunk = this.read() 73 | if (chunk !== null) { 74 | data += chunk 75 | } 76 | }) 77 | stdin.on('end', function() { 78 | return resolve(JSON.parse(data)) 79 | }) 80 | }) 81 | } 82 | 83 | function printError(error) { 84 | const githubIssueURL = 'https://github.com/lirantal/pie-my-vulns/issues' 85 | 86 | debug(error) 87 | console.error() 88 | console.error(`Unexpected failure: ${error.message}`) 89 | console.error() 90 | console.error(`To enable debug information invoke the CLI with a DEBUG=pie* prefix.`) 91 | console.error() 92 | console.error(`Please open an issue at: ${githubIssueURL}`) 93 | 94 | process.exit(EXIT_CODE_ERROR) 95 | } 96 | 97 | function isVulnerabilitiesDetected(vulnsData) { 98 | return vulnsData && vulnsData.vulnerabilities && vulnsData.vulnerabilities.length > 0 99 | } 100 | 101 | main().catch(error => { 102 | printError(error) 103 | }) 104 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at liran.tal@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /__tests__/__e2e__/cli.e2e.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable security/detect-child-process */ 2 | const os = require('os') 3 | const path = require('path') 4 | const util = require('util') 5 | const childProcess = require('child_process') 6 | const spawnAsync = util.promisify(childProcess.execFile) 7 | const exec = childProcess.execSync 8 | 9 | const cliBinPath = path.join(__dirname, '../../bin/pie-my-vulns.js') 10 | 11 | describe('End-to-End CLI', () => { 12 | beforeAll(() => { 13 | let cmdForToken = `npx snyk config set "api=$SNYK_TEST_TOKEN"` 14 | if (os.platform() === 'win32') { 15 | cmdForToken = `npx snyk config set "api=%SNYK_TEST_TOKEN%"` 16 | } 17 | exec(cmdForToken) 18 | }) 19 | 20 | test('CLI should return error code 2 when vulnerabilities are found', async () => { 21 | expect.assertions(1) 22 | 23 | try { 24 | await spawnAsync('node', [cliBinPath], { 25 | cwd: path.join(__dirname, 'project1') 26 | }) 27 | } catch (err) { 28 | expect(err.code).toBe(2) // means that vulnerabilities were found 29 | } 30 | }) 31 | 32 | test('CLI should show vulnerabilities breakdown numbers and their titles', async () => { 33 | expect.hasAssertions() 34 | 35 | try { 36 | await spawnAsync('node', [cliBinPath], { 37 | cwd: path.join(__dirname, 'project1') 38 | }) 39 | } catch (err) { 40 | expect(err.stdout).toContain('Medium severity (20.00%)') 41 | expect(err.stdout).toContain('High severity (0.00%)') 42 | expect(err.stdout).toContain('Low severity (80.00%)') 43 | expect(err.stdout).toContain('Patchable vulnerabilities (0.00%)') 44 | expect(err.stdout).toContain('No remediation available (0.00%)') 45 | expect(err.stdout).toContain('Total number of vulnerabilities found') 46 | expect(err.stdout).toContain('Total number of dependencies scanned') 47 | expect(err.stdout).toContain('Vulnerabilities by severity:') 48 | expect(err.stdout).toContain('Vulnerabilities by remediation action:') 49 | } 50 | }) 51 | 52 | test('CLI should return error code 1 when there is an issue', async () => { 53 | expect.assertions(2) 54 | 55 | try { 56 | await spawnAsync('node', [cliBinPath], { 57 | cwd: path.join(__dirname, 'project2') 58 | }) 59 | } catch (err) { 60 | expect(err.code).toBe(1) 61 | expect(err.stderr).toContain('Unexpected failure: missing node_modules folders') 62 | } 63 | }) 64 | 65 | test('CLI should return error code 0 when no vulnerabilities are found', async () => { 66 | const { stdout, err } = await spawnAsync('node', [cliBinPath], { 67 | cwd: path.join(__dirname, 'project3') 68 | }) 69 | expect(err).toBe(undefined) 70 | expect(stdout).toContain('0 vulnerabilities found') 71 | }) 72 | 73 | test('CLI should accept path to project directory from command argument', async () => { 74 | const project3Dir = path.join(__dirname, 'project3') 75 | const { stdout, err } = await spawnAsync('node', [cliBinPath, '--directory', project3Dir], { 76 | cwd: path.join(__dirname, 'project1') 77 | }) 78 | 79 | expect(err).toBe(undefined) 80 | expect(stdout).toContain('0 vulnerabilities found') 81 | }) 82 | 83 | if (os.platform() === 'linux') { 84 | test('CLI should be able to read Snyk JSON from stdin', async () => { 85 | try { 86 | await exec(`cat snyk.json | node ${cliBinPath}`, { 87 | cwd: __dirname 88 | }) 89 | } catch (err) { 90 | expect(err.status).toBe(2) // means that vulnerabilities were found 91 | } 92 | }) 93 | } 94 | }) 95 | -------------------------------------------------------------------------------- /src/Audit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable security/detect-child-process */ 2 | 'use strict' 3 | const Ora = require('ora') 4 | 5 | const path = require('path') 6 | const Util = require('util') 7 | const ChildProcess = require('child_process') 8 | 9 | const nodeCliCommand = 'node' 10 | const auditCliCommand = path.join(__dirname, '../node_modules/snyk/dist/cli/index.js') 11 | const auditCliArgs = [auditCliCommand, 'test', '--json'] 12 | const ERROR_VULNS_FOUND = 1 13 | const ERROR_UNAUTHENTICATED = 2 14 | const JSON_BUFFER_SIZE = 50 * 1024 * 1024 15 | 16 | const shellEnvVariables = Object.assign({}, process.env, { 17 | SNYK_UTM_MEDIUM: 'cli', 18 | SNYK_UTM_SOURCE: 'cli', 19 | SNYK_UTM_CAMPAIGN: 'pie-my-vulns' 20 | }) 21 | 22 | class Audit { 23 | async authenticate() { 24 | return new Promise((resolve, reject) => { 25 | const process = ChildProcess.execFile(nodeCliCommand, [auditCliCommand, 'auth'], { 26 | env: shellEnvVariables 27 | }) 28 | process.stdout.on('data', chunk => { 29 | const httpsLinkMatch = chunk.match(/https:\/\/.*/g) 30 | if (httpsLinkMatch && httpsLinkMatch.length > 0) { 31 | const httpsLink = httpsLinkMatch[0].trim() 32 | console.log() 33 | console.log(`or you can hit this link:`) 34 | console.log(` ${httpsLink}`) 35 | } 36 | }) 37 | 38 | process.on('close', () => { 39 | return resolve() 40 | }) 41 | 42 | process.on('error', error => { 43 | return reject(new Error(error)) 44 | }) 45 | }) 46 | } 47 | 48 | async test({ directory = '' } = {}, firstScan = true) { 49 | const testSpinner = new Ora({}) 50 | 51 | // Start the spinner only for the first invocation 52 | if (firstScan) { 53 | console.log() 54 | testSpinner.start('Scanning project dependencies') 55 | } 56 | 57 | const ExecFile = Util.promisify(ChildProcess.execFile) 58 | const args = [...auditCliArgs, ...(directory ? [directory] : [])] 59 | let testResults = [] 60 | try { 61 | // allow for 50MB of buffer for a large JSON output 62 | await ExecFile(nodeCliCommand, args, { 63 | maxBuffer: JSON_BUFFER_SIZE, 64 | env: shellEnvVariables 65 | }) 66 | } catch (error) { 67 | const errorMessage = error.stdout 68 | 69 | // error: can't detect package manifest 70 | if (errorMessage && errorMessage.indexOf('Could not detect supported target files') !== -1) { 71 | testSpinner.fail('Scan failed') 72 | throw new Error(`can't detect package manifest files\ntry running in the project's rootdir`) 73 | } 74 | 75 | if (errorMessage && errorMessage.indexOf("we can't test without dependencies") !== -1) { 76 | testSpinner.fail('Scan failed') 77 | throw new Error(`missing node_modules folders\ninstall dependencies and try again`) 78 | } 79 | 80 | if (error.code === ERROR_VULNS_FOUND) { 81 | // we are authenticated as a user for Snyk 82 | // but vulnerabilities have been found 83 | testResults = JSON.parse(error.stdout) 84 | } else if (error.code === ERROR_UNAUTHENTICATED) { 85 | // user is not authenticated and we need 86 | // to trigger the auth process 87 | testSpinner.stop() 88 | 89 | console.log(`Seems like you're not authenticated to Snyk,`) 90 | console.log(`so redirecting you now and after login I'll show scan results here`) 91 | await this.authenticate() 92 | return this.test({ directory }, false) 93 | } else { 94 | throw error 95 | } 96 | } 97 | 98 | testSpinner.succeed('Scan completed successfully') 99 | return testResults 100 | } 101 | } 102 | 103 | module.exports = Audit 104 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | win: circleci/windows@2 5 | 6 | jobs: 7 | build: 8 | working_directory: ~/project 9 | docker: 10 | - image: circleci/node:12 11 | steps: 12 | - checkout 13 | - restore_cache: 14 | key: dependency-cache-{{ checksum "package-lock.json" }} 15 | - run: 16 | name: install 17 | command: npm install --ignore-engines 18 | - save_cache: 19 | key: dependency-cache-{{ checksum "package-lock.json" }} 20 | paths: 21 | - ./node_modules 22 | - persist_to_workspace: 23 | root: ~/project 24 | paths: 25 | - ./node_modules 26 | 27 | lint: 28 | working_directory: ~/project 29 | docker: 30 | - image: circleci/node:12 31 | steps: 32 | - checkout 33 | - attach_workspace: 34 | at: ./ 35 | - run: 36 | name: lint 37 | command: npm run lint 38 | 39 | test_linux: 40 | working_directory: ~/project 41 | docker: 42 | - image: circleci/node:12 43 | steps: 44 | - checkout 45 | - attach_workspace: 46 | at: ./ 47 | - run: 48 | name: test 49 | command: npm run test 50 | - run: 51 | name: test_e2e 52 | command: npm run test:e2e 53 | - run: 54 | name: coverage 55 | command: npx codecov 56 | 57 | test_windows: 58 | executor: win/default 59 | working_directory: ~/project 60 | steps: 61 | - checkout 62 | - run: 63 | name: install 64 | command: npm install --ignore-engines 65 | - run: 66 | name: test 67 | command: npm run test 68 | - run: 69 | name: test_e2e 70 | command: npm run test:e2e 71 | 72 | test-e2e-linux: 73 | working_directory: ~/project 74 | docker: 75 | - image: circleci/node:12 76 | - image: verdaccio/verdaccio:4 77 | steps: 78 | - checkout 79 | - attach_workspace: 80 | at: ./ 81 | - run: 82 | name: npm-verbosity 83 | command: npm config set loglevel verbose 84 | - run: 85 | name: npm-set-registry 86 | command: npm set registry http://0.0.0.0:4873 87 | - run: 88 | name: create-registry-user 89 | command: | 90 | curl -X PUT http://0.0.0.0:4873/-/user/org.couchdb.user:liran -H "Content-Type: application/json" -d '{"name":"liran","password":"liran","email":"liran@example.com","type":"user"}' | node -pe 'JSON.parse(fs.readFileSync(0)).token' | xargs npm config set _authToken 91 | - run: 92 | name: npm-publish 93 | command: npm publish --verbose 94 | - run: 95 | name: npx-pie-my-vulns 96 | command: | 97 | cd __tests__/__e2e__/project1 98 | npm install --ignore-engines 99 | npx snyk config set "api=${SNYK_TEST_TOKEN}" 100 | set +e 101 | npx pie-my-vulns 102 | [[ $? = 2 ]] && exit 0 || exit 1 103 | 104 | release: 105 | working_directory: ~/project 106 | docker: 107 | - image: circleci/node:12 108 | steps: 109 | - checkout 110 | - attach_workspace: 111 | at: ./ 112 | - run: 113 | name: release 114 | command: npm run semantic-release 115 | 116 | workflows: 117 | version: 2.1 118 | project: 119 | jobs: 120 | - build 121 | - lint: 122 | requires: 123 | - build 124 | - test_linux: 125 | requires: 126 | - lint 127 | - test_windows: 128 | requires: 129 | - lint 130 | - test-e2e-linux: 131 | requires: 132 | - test_linux 133 | - test_windows 134 | - release: 135 | filters: 136 | branches: 137 | only: 138 | - master 139 | requires: 140 | - test-e2e-linux 141 | -------------------------------------------------------------------------------- /__tests__/Audit.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable security/detect-child-process */ 2 | const EventEmitter = require('events') 3 | const { execFile } = require('child_process') 4 | jest.mock('child_process') 5 | 6 | const Audit = require('../src/Audit') 7 | 8 | class ChildProcessStdoutMock extends EventEmitter {} 9 | class ChildProcessSuccessMock extends EventEmitter { 10 | constructor() { 11 | super() 12 | this.self = this 13 | } 14 | 15 | get stdout() { 16 | const stdout = new ChildProcessStdoutMock() 17 | 18 | setImmediate(() => { 19 | stdout.emit( 20 | 'data', 21 | ` 22 | Now redirecting you to our auth page, go ahead and log in, 23 | and once the auth is complete, return to this prompt and you'll 24 | be ready to start using snyk. 25 | 26 | If you can't wait use this url: 27 | https://snyk.io/login?token=98723-28763-28g830-f2iu873b 28 | ` 29 | ) 30 | }, 0) 31 | 32 | setImmediate(() => { 33 | this.self.emit('close') 34 | }, 0) 35 | 36 | return stdout 37 | } 38 | } 39 | 40 | describe('Audit', () => { 41 | beforeEach(() => { 42 | jest.resetAllMocks() 43 | }) 44 | 45 | test('authenticate() should invoke auditing with auth argument', async () => { 46 | jest.spyOn(global.console, 'log') 47 | 48 | execFile.mockImplementation(() => { 49 | const process = new ChildProcessSuccessMock() 50 | return process 51 | }) 52 | 53 | const audit = new Audit() 54 | await audit.authenticate() 55 | 56 | expect(execFile).toHaveBeenCalled() 57 | expect(execFile).toHaveBeenCalledWith('node', [expect.anything(), 'auth'], { 58 | env: expect.any(Object) 59 | }) 60 | expect(console.log.mock.calls).toEqual([ 61 | [], 62 | ['or you can hit this link:'], 63 | [' https://snyk.io/login?token=98723-28763-28g830-f2iu873b'] 64 | ]) 65 | }) 66 | 67 | test('when test() returns vulns found exit code they should be returned', async () => { 68 | execFile.mockImplementation( 69 | jest.fn((file, args, options, callback) => { 70 | const err = new Error() 71 | err.code = 1 72 | err.stdout = JSON.stringify({ vulnsCount: 15 }) 73 | return callback(err) 74 | }) 75 | ) 76 | 77 | const audit = new Audit() 78 | const data = await audit.test() 79 | 80 | expect(data).toEqual({ 81 | vulnsCount: 15 82 | }) 83 | 84 | expect(execFile).toHaveBeenCalled() 85 | }) 86 | 87 | test('when test() not being authenticated should run the authenticate flow', async () => { 88 | execFile.mockImplementationOnce( 89 | jest.fn((file, args, options, callback) => { 90 | const err = new Error() 91 | err.code = 2 92 | err.stdout = JSON.stringify({ vulnsCount: 15 }) 93 | return callback(err) 94 | }) 95 | ) 96 | 97 | execFile.mockImplementationOnce( 98 | jest.fn((file, args, options, callback) => { 99 | const err = new Error() 100 | err.code = 1 101 | err.stdout = JSON.stringify({ vulnsCount: 15 }) 102 | return callback(err) 103 | }) 104 | ) 105 | 106 | const audit = new Audit() 107 | 108 | const spy = jest.spyOn(audit, 'authenticate') 109 | audit.authenticate.mockResolvedValue(true) 110 | 111 | await audit.test() 112 | 113 | expect(audit.authenticate).toHaveBeenCalled() 114 | spy.mockRestore() 115 | }) 116 | 117 | test('when test() throws an error about supported target files', async () => { 118 | expect.assertions(2) 119 | execFile.mockImplementation( 120 | jest.fn((file, args, options, callback) => { 121 | const err = new Error() 122 | err.code = 1 123 | err.stdout = 'Could not detect supported target files' 124 | return callback(err) 125 | }) 126 | ) 127 | 128 | const audit = new Audit() 129 | try { 130 | await audit.test() 131 | } catch (err) { 132 | expect(err.message).toContain( 133 | `can't detect package manifest files\ntry running in the project's rootdir` 134 | ) 135 | } 136 | 137 | expect(execFile).toHaveBeenCalled() 138 | }) 139 | 140 | test('when test() throws an error about deps not being available', async () => { 141 | expect.assertions(2) 142 | execFile.mockImplementation( 143 | jest.fn((file, args, options, callback) => { 144 | const err = new Error() 145 | err.code = 1 146 | err.stdout = "we can't test without dependencies" 147 | return callback(err) 148 | }) 149 | ) 150 | 151 | const audit = new Audit() 152 | try { 153 | await audit.test() 154 | } catch (err) { 155 | expect(err.message).toContain( 156 | `missing node_modules folders\ninstall dependencies and try again` 157 | ) 158 | } 159 | 160 | expect(execFile).toHaveBeenCalled() 161 | }) 162 | }) 163 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pie-my-vulns", 3 | "version": "0.0.0-development", 4 | "description": "Visualize your project security vulnerabilities as a pie chart in the terminal", 5 | "bin": { 6 | "pie-my-vulns": "./bin/pie-my-vulns.js" 7 | }, 8 | "engines": { 9 | "node": ">=8.0.0" 10 | }, 11 | "scripts": { 12 | "lint": "eslint . && npm run lint:lockfile", 13 | "lint:lockfile": "lockfile-lint --path package-lock.json --type npm --validate-https --allowed-hosts npm yarn", 14 | "lint:fix": "eslint . --fix", 15 | "format": "prettier --config .prettierrc.js --write '**/*.js'", 16 | "test": "jest -c jest.default.js", 17 | "test:e2e": "jest -c jest.e2e.js", 18 | "test:watch": "jest --watch", 19 | "coverage:view": "open-cli coverage/lcov-report/index.html", 20 | "semantic-release": "semantic-release" 21 | }, 22 | "author": { 23 | "name": "Liran Tal", 24 | "email": "liran.tal@gmail.com", 25 | "url": "https://github.com/lirantal" 26 | }, 27 | "license": "Apache-2.0", 28 | "keywords": [ 29 | "pie", 30 | "chart", 31 | "vulnerabilities", 32 | "security", 33 | "terminal", 34 | "console", 35 | "visualize" 36 | ], 37 | "homepage": "https://github.com/lirantal/pie-my-vulns", 38 | "bugs": { 39 | "url": "https://github.com/lirantal/pie-my-vulns/issues" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/lirantal/pie-my-vulns.git" 44 | }, 45 | "dependencies": { 46 | "cli-pie": "^2.4.2", 47 | "minimist": "1.2.5", 48 | "debug": "^4.3.1", 49 | 50 | "snyk": "^1.685.0", 51 | "ora": "5.4.1" 52 | }, 53 | "devDependencies": { 54 | "@commitlint/cli": "^7.2.1", 55 | "@commitlint/config-conventional": "^7.1.2", 56 | "@semantic-release/changelog": "^3.0.4", 57 | "@semantic-release/commit-analyzer": "^6.2.0", 58 | "@semantic-release/git": "^7.0.16", 59 | "@semantic-release/github": "^5.4.2", 60 | "@semantic-release/npm": "^5.1.13", 61 | "@semantic-release/release-notes-generator": "^7.2.1", 62 | "babel-eslint": "^10.0.1", 63 | "babel-plugin-syntax-async-functions": "^6.13.0", 64 | "babel-plugin-transform-regenerator": "^6.26.0", 65 | "babel-preset-env": "^1.6.1", 66 | "cz-conventional-changelog": "^1.2.0", 67 | "eslint": "^6.0.1", 68 | "eslint-config-standard": "^13.0.1", 69 | "eslint-plugin-import": "^2.18.0", 70 | "eslint-plugin-jest": "^22.7.2", 71 | "eslint-plugin-node": "^9.1.0", 72 | "eslint-plugin-promise": "^4.2.1", 73 | "eslint-plugin-security": "^1.4.0", 74 | "eslint-plugin-standard": "^4.0.0", 75 | "husky": "^3.0.0", 76 | "jest": "^24.8.0", 77 | "lint-staged": "^9.2.0", 78 | "lockfile-lint": "^2.0.1", 79 | "open-cli": "^5.0.0", 80 | "prettier": "^1.18.2", 81 | "semantic-release": "^15.13.19" 82 | }, 83 | "husky": { 84 | "hooks": { 85 | "commit-msg": "commitlint --env HUSKY_GIT_PARAMS", 86 | "pre-commit": "lint-staged", 87 | "post-merge": "npm install", 88 | "pre-push": "npm run lint && npm run test" 89 | } 90 | }, 91 | "lint-staged": { 92 | "**/*.js": [ 93 | "npm run format", 94 | "git add" 95 | ] 96 | }, 97 | "commitlint": { 98 | "extends": [ 99 | "@commitlint/config-conventional" 100 | ] 101 | }, 102 | "standard": { 103 | "env": [ 104 | "jest" 105 | ], 106 | "parser": "babel-eslint", 107 | "ignore": [ 108 | "**/out/" 109 | ] 110 | }, 111 | "eslintIgnore": [ 112 | "coverage/**" 113 | ], 114 | "eslintConfig": { 115 | "env": { 116 | "node": true, 117 | "es6": true, 118 | "jest": true 119 | }, 120 | "plugins": [ 121 | "import", 122 | "standard", 123 | "node", 124 | "security", 125 | "jest" 126 | ], 127 | "extends": [ 128 | "standard", 129 | "plugin:node/recommended" 130 | ], 131 | "rules": { 132 | "no-process-exit": "warn", 133 | "jest/no-disabled-tests": "error", 134 | "jest/no-focused-tests": "error", 135 | "jest/no-identical-title": "error", 136 | "node/no-unsupported-features": "off", 137 | "node/no-unpublished-require": "off", 138 | "security/detect-non-literal-fs-filename": "error", 139 | "security/detect-unsafe-regex": "error", 140 | "security/detect-buffer-noassert": "error", 141 | "security/detect-child-process": "error", 142 | "security/detect-disable-mustache-escape": "error", 143 | "security/detect-eval-with-expression": "error", 144 | "security/detect-no-csrf-before-method-override": "error", 145 | "security/detect-non-literal-regexp": "error", 146 | "security/detect-object-injection": "warn", 147 | "security/detect-possible-timing-attacks": "error", 148 | "security/detect-pseudoRandomBytes": "error", 149 | "space-before-function-paren": "off", 150 | "object-curly-spacing": "off" 151 | }, 152 | "parserOptions": { 153 | "ecmaVersion": 8, 154 | "ecmaFeatures": { 155 | "impliedStrict": true 156 | } 157 | } 158 | }, 159 | "release": { 160 | "branch": "master", 161 | "analyzeCommits": { 162 | "preset": "angular", 163 | "releaseRules": [ 164 | { 165 | "type": "docs", 166 | "release": "patch" 167 | }, 168 | { 169 | "type": "refactor", 170 | "release": "patch" 171 | }, 172 | { 173 | "type": "style", 174 | "release": "patch" 175 | } 176 | ] 177 | } 178 | }, 179 | "plugins": [ 180 | "@semantic-release/commit-analyzer", 181 | "@semantic-release/release-notes-generator", 182 | [ 183 | "@semantic-release/changelog", 184 | { 185 | "changelogFile": "CHANGELOG.md" 186 | } 187 | ], 188 | "@semantic-release/npm", 189 | [ 190 | "@semantic-release/git", 191 | { 192 | "assets": [ 193 | "CHANGELOG.md" 194 | ] 195 | } 196 | ], 197 | "@semantic-release/github" 198 | ] 199 | } 200 | -------------------------------------------------------------------------------- /__tests__/__e2e__/project1/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project1", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "amdefine": { 8 | "version": "1.0.1", 9 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 10 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", 11 | "dev": true 12 | }, 13 | "async": { 14 | "version": "0.2.10", 15 | "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", 16 | "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", 17 | "dev": true 18 | }, 19 | "camelcase": { 20 | "version": "1.2.1", 21 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", 22 | "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", 23 | "dev": true 24 | }, 25 | "camelize": { 26 | "version": "1.0.0", 27 | "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", 28 | "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" 29 | }, 30 | "connect": { 31 | "version": "3.4.1", 32 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.4.1.tgz", 33 | "integrity": "sha1-ohNh0/QJnvdhzabcSpc7seuwo00=", 34 | "requires": { 35 | "debug": "~2.2.0", 36 | "finalhandler": "0.4.1", 37 | "parseurl": "~1.3.1", 38 | "utils-merge": "1.0.0" 39 | } 40 | }, 41 | "content-security-policy-builder": { 42 | "version": "1.0.0", 43 | "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-1.0.0.tgz", 44 | "integrity": "sha1-Ef1AxcwpimxyWjX5rPcegqtdMkM=", 45 | "requires": { 46 | "dashify": "^0.2.0" 47 | } 48 | }, 49 | "core-util-is": { 50 | "version": "1.0.2", 51 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 52 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 53 | }, 54 | "dashify": { 55 | "version": "0.2.2", 56 | "resolved": "https://registry.npmjs.org/dashify/-/dashify-0.2.2.tgz", 57 | "integrity": "sha1-agdBWgHJH69KMuONnfunH2HLIP4=" 58 | }, 59 | "debug": { 60 | "version": "2.2.0", 61 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 62 | "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", 63 | "requires": { 64 | "ms": "0.7.1" 65 | } 66 | }, 67 | "decamelize": { 68 | "version": "1.2.0", 69 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 70 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 71 | "dev": true 72 | }, 73 | "depd": { 74 | "version": "1.1.0", 75 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 76 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 77 | }, 78 | "dns-prefetch-control": { 79 | "version": "0.1.0", 80 | "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz", 81 | "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI=" 82 | }, 83 | "dont-sniff-mimetype": { 84 | "version": "1.0.0", 85 | "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz", 86 | "integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g=" 87 | }, 88 | "ee-first": { 89 | "version": "1.1.1", 90 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 91 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 92 | }, 93 | "escape-html": { 94 | "version": "1.0.3", 95 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 96 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 97 | }, 98 | "finalhandler": { 99 | "version": "0.4.1", 100 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.1.tgz", 101 | "integrity": "sha1-haF8bFmpRxfSYtYSMNSw6+PUoU0=", 102 | "requires": { 103 | "debug": "~2.2.0", 104 | "escape-html": "~1.0.3", 105 | "on-finished": "~2.3.0", 106 | "unpipe": "~1.0.0" 107 | } 108 | }, 109 | "frameguard": { 110 | "version": "2.0.0", 111 | "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-2.0.0.tgz", 112 | "integrity": "sha1-MMLBSeXjUF+eFW+bxJGkOEIOSH4=" 113 | }, 114 | "helmet": { 115 | "version": "2.3.0", 116 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-2.3.0.tgz", 117 | "integrity": "sha1-1lXIW1Wwo79yKkwsZuSLeLQWG5E=", 118 | "requires": { 119 | "connect": "3.4.1", 120 | "dns-prefetch-control": "0.1.0", 121 | "dont-sniff-mimetype": "1.0.0", 122 | "frameguard": "2.0.0", 123 | "helmet-csp": "1.2.2", 124 | "hide-powered-by": "1.0.0", 125 | "hpkp": "1.2.0", 126 | "hsts": "1.0.0", 127 | "ienoopen": "1.0.0", 128 | "nocache": "1.0.1", 129 | "referrer-policy": "1.0.0", 130 | "x-xss-protection": "1.0.0" 131 | } 132 | }, 133 | "helmet-csp": { 134 | "version": "1.2.2", 135 | "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-1.2.2.tgz", 136 | "integrity": "sha1-CFwDB9V/yWzZczfxcKuL/qmeXfc=", 137 | "requires": { 138 | "camelize": "1.0.0", 139 | "content-security-policy-builder": "1.0.0", 140 | "lodash.reduce": "4.5.0", 141 | "platform": "1.3.1" 142 | } 143 | }, 144 | "hide-powered-by": { 145 | "version": "1.0.0", 146 | "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.0.0.tgz", 147 | "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=" 148 | }, 149 | "hpkp": { 150 | "version": "1.2.0", 151 | "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-1.2.0.tgz", 152 | "integrity": "sha1-g/LLOLJs/yHa8m4v9LVxJpId7GU=" 153 | }, 154 | "hsts": { 155 | "version": "1.0.0", 156 | "resolved": "https://registry.npmjs.org/hsts/-/hsts-1.0.0.tgz", 157 | "integrity": "sha1-mOEDnverpVQFe2sOMlhMCxFDpBQ=", 158 | "requires": { 159 | "core-util-is": "1.0.2" 160 | } 161 | }, 162 | "ienoopen": { 163 | "version": "1.0.0", 164 | "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.0.0.tgz", 165 | "integrity": "sha1-NGpCj0dKrI9QzzeE6i0PFvYr2ms=" 166 | }, 167 | "lodash.reduce": { 168 | "version": "4.5.0", 169 | "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.5.0.tgz", 170 | "integrity": "sha1-r30uxiEGJEHnfVv0CKHgce+GaRw=" 171 | }, 172 | "ms": { 173 | "version": "0.7.1", 174 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", 175 | "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" 176 | }, 177 | "nocache": { 178 | "version": "1.0.1", 179 | "resolved": "https://registry.npmjs.org/nocache/-/nocache-1.0.1.tgz", 180 | "integrity": "sha1-aVyfc2kmp1VPc2X6JeCHlBBlvTY=", 181 | "requires": { 182 | "depd": "1.1.0" 183 | } 184 | }, 185 | "on-finished": { 186 | "version": "2.3.0", 187 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 188 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 189 | "requires": { 190 | "ee-first": "1.1.1" 191 | } 192 | }, 193 | "parseurl": { 194 | "version": "1.3.3", 195 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 196 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 197 | }, 198 | "platform": { 199 | "version": "1.3.1", 200 | "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.1.tgz", 201 | "integrity": "sha1-SSIQiSM1vTExwKCN2i2T7DVD5CM=" 202 | }, 203 | "referrer-policy": { 204 | "version": "1.0.0", 205 | "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.0.0.tgz", 206 | "integrity": "sha1-9g7tyS+UKwGmEYEh7JMtZuj9fhQ=" 207 | }, 208 | "source-map": { 209 | "version": "0.1.34", 210 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz", 211 | "integrity": "sha1-p8/omux7FoLDsZjQrPtH19CQVms=", 212 | "dev": true, 213 | "requires": { 214 | "amdefine": ">=0.0.4" 215 | } 216 | }, 217 | "timespan": { 218 | "version": "2.3.0", 219 | "resolved": "https://registry.npmjs.org/timespan/-/timespan-2.3.0.tgz", 220 | "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=", 221 | "dev": true 222 | }, 223 | "uglify-js": { 224 | "version": "2.4.24", 225 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz", 226 | "integrity": "sha1-+tV1XB4Vd2WLsG/5q25UjJW+vW4=", 227 | "dev": true, 228 | "requires": { 229 | "async": "~0.2.6", 230 | "source-map": "0.1.34", 231 | "uglify-to-browserify": "~1.0.0", 232 | "yargs": "~3.5.4" 233 | } 234 | }, 235 | "uglify-to-browserify": { 236 | "version": "1.0.2", 237 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", 238 | "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", 239 | "dev": true 240 | }, 241 | "unpipe": { 242 | "version": "1.0.0", 243 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 244 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 245 | }, 246 | "utils-merge": { 247 | "version": "1.0.0", 248 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 249 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 250 | }, 251 | "window-size": { 252 | "version": "0.1.0", 253 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", 254 | "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", 255 | "dev": true 256 | }, 257 | "wordwrap": { 258 | "version": "0.0.2", 259 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", 260 | "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", 261 | "dev": true 262 | }, 263 | "x-xss-protection": { 264 | "version": "1.0.0", 265 | "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.0.0.tgz", 266 | "integrity": "sha1-iYr7k4abJGYc+cUvnujbjtB2Tdk=" 267 | }, 268 | "yargs": { 269 | "version": "3.5.4", 270 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz", 271 | "integrity": "sha1-2K/49mXpTDS9JZvevRv68N3TU2E=", 272 | "dev": true, 273 | "requires": { 274 | "camelcase": "^1.0.2", 275 | "decamelize": "^1.0.0", 276 | "window-size": "0.1.0", 277 | "wordwrap": "0.0.2" 278 | } 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2019 Liran Tal . 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | https://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. -------------------------------------------------------------------------------- /.github/pie-my-vulns-logo.svg: -------------------------------------------------------------------------------- 1 | pie chart --------------------------------------------------------------------------------