├── .eslintrc ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── Contributor-Agreement.md ├── LICENSE ├── README.md ├── appveyor.yml ├── demo ├── index.js ├── justrequireagent.js └── static │ └── hello.txt ├── lib ├── ast.js ├── config.js ├── debugger-wrapper.js ├── index.js ├── module-utils.js ├── resources │ ├── build-date.repo │ └── functions.repo.json ├── snapshot │ ├── index.js │ └── reader.js ├── state.js ├── system-info.js └── transmitter.js ├── package-lock.json ├── package.json └── test ├── config.test.js ├── debugger.test.js ├── disable.test.js ├── dump.js ├── e2e.test.js ├── failures.test.js ├── fixtures ├── function-declarations │ ├── build-date.repo │ ├── functions.repo.json │ └── node_modules │ │ ├── class-member │ │ ├── index.js │ │ └── package.json │ │ ├── inner-function │ │ └── package.json │ │ ├── lodash-cc-style │ │ └── package.json │ │ ├── multiple-declarations-in-exports │ │ ├── index.js │ │ └── package.json │ │ ├── multiple-one-liners │ │ ├── index.js │ │ └── package.json │ │ ├── one-liner-declaration-in-exports │ │ ├── index.js │ │ └── package.json │ │ └── one-liner │ │ ├── index.js │ │ └── package.json ├── handlebars │ └── lib │ │ └── handlebars │ │ └── utils.js ├── snapshots │ └── bundled-snapshot.json ├── st │ ├── node_modules │ │ └── st.js │ ├── script.json │ ├── vulnerable_methods.json │ ├── vulnerable_methods_invalid.json │ └── vulnerable_methods_new.json └── uglify-js │ └── lib │ └── parse.js ├── function-declaration-types.test.js ├── method-detection.test.js ├── shutdown.test.js ├── snapshot.test.js └── transmitter.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2018 4 | }, 5 | "env": { 6 | "node": true, 7 | "es6": true 8 | }, 9 | "rules": { 10 | "array-bracket-spacing": [2, "never"], 11 | "block-scoped-var": 2, 12 | "brace-style": 2, 13 | "camelcase": 1, 14 | "comma-dangle": ["error", "always-multiline"], 15 | "computed-property-spacing": [2, "never"], 16 | "curly": 2, 17 | "eol-last": 2, 18 | "eqeqeq": [2, "smart"], 19 | "guard-for-in": 2, 20 | "indent": [ 21 | 2, 22 | 2, 23 | { 24 | "SwitchCase": 1 25 | } 26 | ], 27 | "max-depth": [1, 3], 28 | "max-len": [1, 120], 29 | "max-statements": [1, 50], 30 | "new-cap": 0, 31 | "no-caller": 2, 32 | "no-else-return": 2, 33 | "no-extend-native": 2, 34 | "no-mixed-spaces-and-tabs": 2, 35 | "no-trailing-spaces": 2, 36 | "no-undef": 2, 37 | "no-unused-vars": 1, 38 | "no-use-before-define": [2, "nofunc"], 39 | "object-curly-spacing": [2, "never"], 40 | "quotes": [2, "single", "avoid-escape"], 41 | "semi": [2, "always"], 42 | "keyword-spacing": [2, {"before": true, "after": true}], 43 | "space-before-function-paren": [ 44 | 2, 45 | { 46 | "anonymous": "ignore", 47 | "named": "never" 48 | } 49 | ], 50 | "space-unary-ops": 2 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @snyk/runtime 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Commit messages 4 | 5 | Commit messages must follow the [Angular-style](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format) commit format (but excluding the scope). 6 | 7 | i.e: 8 | 9 | ```text 10 | fix: minified scripts being removed 11 | 12 | Also includes tests 13 | ``` 14 | 15 | This will allow for the automatic changelog to generate correctly. 16 | 17 | ### Commit types 18 | 19 | Must be one of the following: 20 | 21 | * **feat**: A new feature 22 | * **fix**: A bug fix 23 | * **docs**: Documentation only changes 24 | * **test**: Adding missing tests 25 | * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation 26 | * **refactor**: A code change that neither fixes a bug nor adds a feature 27 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 28 | * **perf**: A code change that improves performance 29 | 30 | To release a major you need to add `BREAKING CHANGE: ` to the start of the body and the detail of the breaking change. 31 | 32 | ## Code standards 33 | 34 | Ensure that your code adheres to the included `.eslintrc` config by running `npm run lint`. 35 | 36 | ## Sending pull requests 37 | 38 | - add tests for newly added code (and try to mirror directory and file structure if possible) 39 | - spell check 40 | - PRs will not be code reviewed unless all tests are passing (run `npm test`) 41 | 42 | *Important:* when fixing a bug, please commit a **failing test** first demonstrate the current code is failing. Once that commit is in place, then commit the bug fix, so that we can test *before* and *after*. 43 | 44 | Remember that you're developing for multiple platforms and versions of node, so if the tests pass on your Mac or Linux or Windows machine, it *may* not pass elsewhere. 45 | 46 | ## Contributor Agreement 47 | 48 | A pull-request will only be considered for merging into the upstream codebase after you have signed our [contributor agreement](https://github.com/snyk/nodejs-runtime-agent/blob/master/Contributor-Agreement.md), assigning us the rights to the contributed code and granting you a license to use it in return. If you submit a pull request, you will be prompted to review and sign the agreement with one click (we use [CLA assistant](https://cla-assistant.io/)). 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - `node -v`: 2 | - `npm -v`: 3 | - `snyk -v`: 4 | - Command run: 5 | 6 | ### Expected behaviour 7 | 8 | 9 | ### Actual behaviour 10 | 11 | 12 | ### Steps to reproduce 13 | 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - [ ] Ready for review 2 | - [ ] Follows CONTRIBUTING rules 3 | - [ ] Any changes involving paths support all path separators 4 | - [ ] Reviewed by Snyk internal team 5 | 6 | #### What does this PR do? 7 | 8 | 9 | #### Where should the reviewer start? 10 | 11 | 12 | #### How should this be manually tested? 13 | 14 | 15 | #### Any background context you want to provide? 16 | 17 | 18 | #### What are the relevant tickets? 19 | 20 | 21 | #### Screenshots 22 | 23 | 24 | #### Additional questions 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | !test/fixtures/function-declarations/node_modules/ 3 | .npmignore 4 | .vscode/ 5 | utils/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | sudo: false 3 | notifications: 4 | email: false 5 | slack: 6 | on_failure: always 7 | on_success: never 8 | on_pull_requests: false 9 | rooms: 10 | secure: mWzYwXXkaPJ/t24kG9gz6hYyRGoGX+zsjKDa+IzjhY4NeMJyzgHyN3x1vWwSXhfG1jQuBTFyT5RTWN8Jfo2Za/XDKYfMXXP5gCMtNkeWdflYWaUC7sAZepRANUB3gzkCTvkc/DOY/FI07xCcLMJoZ7jGiADNakTaWvzdExJUBs6NuhGdIUmOT+chbip02yN7mSPeqyU7/vFqpCxxEoPeNzaNshLufbZUEWgmLm0bm5Uxdo7Yi5rL0/nA7oqXUzzWTtgQEu0mOG4Oqu7oXAki2rLISw8enZwt5/fUbxGgK1J3UB86vgnDrxbTAhuNUuddaSxUuDsg1+3xyRzU74cyKUWnWqL10Tyy9KgDR0A+48w2v8DH/pOvnvfXA+FL0zLtDJ9jPuSK0dFbceRYmolEGMDF53Q/s2W+waC13Bi3nHRQJKYmT+bOnoLABLpfm5fbV/2br4LVTQwiP80HJ+19Vy4lriF55zu1yjESUBzdvvX1Dhp5E3AXZZv6xB0v4gyZZeOoIv6BxqinLauZiS3nM7O9vu1QnFvbo4HH0Df651fyy1kOU5UAAD+CNRgpZ8GMc+EegvnLbS3nzbNOPlkACmhxMNcpEvD6MomcB4UV2dPWAYIQSfrRV5h+iZlNSqCkA2pl3p6TTZhLMvoSxziRzUZZx7GQe/cnUGN6GbzyyRk= 11 | language: node_js 12 | node_js: 13 | - '8' 14 | - '10' 15 | cache: 16 | directories: 17 | - node_modules 18 | install: 19 | - npm install 20 | script: 21 | - curl $SNAPSHOT_URL > lib/resources/functions.bundle.json 22 | - node -e "console.log(new Date().toUTCString())" > lib/resources/build-date.bundle 23 | - npm test 24 | jobs: 25 | include: 26 | - stage: npm release 27 | if: branch = master AND type != pull_request 28 | node_js: '10' 29 | script: 30 | - curl $SNAPSHOT_URL > lib/resources/functions.bundle.json 31 | - node -e "console.log(new Date().toUTCString())" > lib/resources/build-date.bundle 32 | - npx semantic-release 33 | branches: 34 | only: 35 | - master 36 | -------------------------------------------------------------------------------- /Contributor-Agreement.md: -------------------------------------------------------------------------------- 1 | # Snyk CLI tool contributor agreement 2 | 3 | This Snyk CLI tool Agreement (this **"Agreement"**) applies to any Contribution you make to any Work. 4 | 5 | This is a binding legal agreement on you and any organization you represent. If you are signing this Agreement on behalf of your employer or other organization, you represent and warrant that you have the authority to agree to this Agreement on behalf of the organization. 6 | 7 | ## 1. Definitions 8 | 9 | **"Contribution"** means any original work, including any modification of or addition to an existing work, that you submit to Snyk CLI tool repo in any manner for inclusion in any Work. 10 | 11 | **"Snyk", "we"** and **"us"** means Snyk Ltd. 12 | 13 | **"Work"** means any project, work or materials owned or managed by Snyk Ltd. 14 | 15 | **"You"** and **"your"** means you and any organization on whose behalf you are entering this Agreement. 16 | 17 | ## 2. Copyright Assignment, License and Waiver 18 | 19 | **(a) Assignment.** By submitting a Contribution, you assign to Snyk all right, title and interest in any copright you have in the Contribution, and you waive any rights, including any moral rights, database rights, etc., that may affect your ownership of the copyright in the Contribution. 20 | 21 | **(b) License to Snyk.** If your assignment in Section 2(a) is ineffective for any reason, you grant to us and to any recipient of any Work distributed by use, a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sublicensable licence to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Contributions and any derivative work created based on a Contribution. If your license grant is ineffective for any reason, you irrevocably waive and covenant to not assert any claim you may have against us, our successors in interest, and any of our direct or indirect licensees and customers, arising out of our, our successors in interest's, or any of our direct or indirect licensees' or customers' use, reproduction, preparation of derivative works, public display, public performance, sublicense, and distribution of a Contribution. You also agree that we may publicly use your name and the name of any organization on whose behalf you're entering into this Agreement in connection with publicizing the Work. 22 | 23 | **(c) License to you.** We grant to you a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sublicensable license to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute a Contribution and any derivative works you create based on a Contribution. 24 | 25 | ## 3. Patent License 26 | You grant to us and to any recipient of any Work distributed by us, a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sublicensable patent license to make, have made, use, sell, offer to sell, import, and otherwise transfer the Contribution in whole or in part, along or included in any Work under any patent you own, or license from a third party, that is necessarily infringed by the Contribution or by combination of the Contribution with any Work. 27 | 28 | ## 4. Your Representation and Warranties. 29 | By submitting a Contribution, you represent and warrant that: (a) each Contribution you submit is an original work and you can legally grant the rights set out in this Agreement; (b) the Contribution does not, and any exercise of the rights granted by you will not, infringe any third party's intellectual property or other right; and (c) you are not aware of any claims, suits, or actions pertaining to the Contribution. You will notify us immediately if you become aware or have reason to believe that any of your representations and warranties is or becomes inaccurate. 30 | 31 | ##5. Intellectual Property 32 | Except for the assignment and licenses set forth in this Agreement, this Agreement does not transfer any right, title or interest in any intellectual property right of either party to the other. If you choose to provide us with suggestions, ideas for improvement, recommendations or other feedback, on any Work we may use your feedback without any restriction or payment. 33 | 34 | ## Miscellaneous 35 | English law governs this Agreement, excluding any applicable conflict of laws rules or principles, and the parties agree to the exclusive jurisdiction of the courts in England, UK. This Agreement does not create a partnership, agency relationship, or joint venture between the parties. We may assign this Agreement without notice or restriction. If any provision of this Agreement is unenforcable, that provision will be modified to render it enforceable to the extent possible to effect the parties' intention and the remaining provisions will not be affected. The parties may amend this Agreement only in a written amendment signed by both parties. This Agreement comprises the parties' entire agreement relating to the subject matter of this Agreement. 36 | 37 | **Agreed and accepted on my behalf and on behalf of my organization** 38 | 39 | Our contributor agreement is based on the [mongoDB contributor agreement] (https://www.mongodb.com/legal/contributor-agreement). 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is a proprietary product of Snyk Ltd, and is 2 | protected under copyright laws and international copyright treaties. 3 | Any use of the software is subject to and governed by the license 4 | conditions below: 5 | 6 | 1. You may run, execute or otherwise actively use this software 7 | (whether as a whole or portions of it) only if you have entered into 8 | a separate software-as-a-service (“SaaS”) agreement with Snyk. 9 | 2. You may re-publish and re-distribute this software but only within 10 | the same code repository it is found in and only for internal business purposes. 11 | 3. All other uses, reproductions, copies, distributions or 12 | publications of this software are strictly prohibited. 13 | 4. You may not use this software to breach the security of the 14 | Snyk SaaS platform, to circumvent, manipulate, impair or 15 | disrupt its operation, or perform any benchmark or penetration 16 | testing of the Snyk SaaS platform. 17 | 5. You may not use this software for any activity that constitutes, 18 | or encourages conduct that would constitute, a criminal offence, 19 | give rise to civil liability or otherwise violate any applicable 20 | law. 21 | Any use of this software is further subject to and governed by the 22 | following limitations: 23 | This software is provided to you 'as is'. Snyk does not guarantee, makes no representation, and provides no warranty about this software, including on quality, fitness for a particular purpose or non-infringement. 24 | 25 | To the maximum extent permitted by applicable law, Snyk and its directors, agents or employees will not be liable for any direct, indirect, incidental, consequential, special, statutory or punitive damages, losses arising from, or in connection, with your use of, or reliance upon, this software. 26 | 27 | These terms and conditions are subject to English law and any disputes shall be settled in the courts of England and Wales. 28 | 29 | © 2018 Snyk Ltd. Version November 2018 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snyk Node.js runtime agent 2 | [![Known Vulnerabilities](https://snyk.io/test/github/snyk/nodejs-runtime-agent/badge.svg?style=flat-square)](https://snyk.io/test/github/snyk/nodejs-runtime-agent) 3 | 4 | Use this package as a library in your application to monitor your dependencies and to learn how the vulnerable functions of the dependencies are invoked in your deployments. 5 | 6 | # Quick start 7 | ```js 8 | require('@snyk/nodejs-runtime-agent')({ projectId: }); 9 | ``` 10 | 11 | # Supported Node.js versions 12 | 13 | The Node.js Runtime Agent is tested on Node 8 and Node 10. 14 | Other versions are unsupported. 15 | 16 | # How to 17 | ```js 18 | require('@snyk/nodejs-runtime-agent')(config); 19 | ``` 20 | 21 | The `config` object supports the following options: 22 | 23 | | Key | Type | Default value | Purpose | 24 | |--------------------|-----------|------------------------------------------|-------------------------------------------------------------------------| 25 | | `projectId` | `String` | | The Snyk project ID that matches your application. | 26 | | `enable` | `Boolean` | `true` | Set to `false` to disable the agent. | 27 | 28 | Advanced `config` options: 29 | 30 | | Key | Type | Default value | Purpose | 31 | |----------------------|-----------|-------------------------------------------------------------|--------------------------------------------------------------------------------------------| 32 | | `beaconIntervalMs` | `Number` | `60000` | Report frequency in milliseconds. | 33 | | `snapshotIntervalMs` | `Number` | `3600000` | Snapshot retrieval frequency in milliseconds. | 34 | | `flushOnExit` | `Boolean` | `true` | Set to `false` to prevent the agent from flushing its data before exiting. `true` is useful especially for short-lived environments. | 35 | 36 | # Demo 37 | 38 | There is a 39 | [self-contained demo named node-woof](https://github.com/snyk/node-woof#node-woof), 40 | which you can clone and run. It will guide you through the setup of the project on 41 | your machine. 42 | 43 | # Development 44 | `npm start` brings up an http server that invokes a vulnerable function 45 | on startup and for every request. 46 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # https://www.appveyor.com/docs/appveyor-yml 2 | 3 | # to disable automatic builds 4 | build: off 5 | branches: 6 | only: 7 | - master 8 | 9 | init: 10 | - git config --global core.autocrlf true 11 | 12 | shallow_clone: true 13 | 14 | cache: 15 | - node_modules -> package.json 16 | 17 | environment: 18 | matrix: 19 | - nodejs_version: "8" 20 | - nodejs_version: "10" 21 | 22 | matrix: 23 | fast_finish: true 24 | 25 | install: 26 | - ps: Install-Product node $env:nodejs_version 27 | - node --version 28 | - npm --version 29 | - npm install 30 | 31 | test_script: 32 | - npm test 33 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | // load the agent from the local project and start it 2 | // env vars provide the configuration with default values as a fallback 3 | require('../lib')({ 4 | baseUrl: process.env.SNYK_HOMEBASE_ORIGIN || 'http://localhost:8000', 5 | projectId: process.env.SNYK_PROJECT_ID || 'A3B8ADA9-B726-41E9-BC6B-5169F7F89A0C', 6 | beaconIntervalMs: process.env.SNYK_BEACON_INTERVAL_MS || 10000, 7 | snapshotIntervalMs: process.env.SNYK_SNAPSHOT_INTERVAL_MS || 60 * 60 * 1000, 8 | enable: !process.env.SNYK_RUNTIME_AGENT_DISABLE, 9 | }); 10 | 11 | // start running some non-vulnerable function in the background 12 | // tests may hook into it to make it look vulnerable 13 | if (process.env.SNYK_TRIGGER_EXTRA_VULN) { 14 | setInterval(() => { 15 | try { 16 | st.Mount.prototype.getUrl('whatever'); 17 | } catch (err) {} 18 | }, 250).unref(); 19 | } 20 | 21 | // create a server with a known vulnerability 22 | const http = require('http'); 23 | const st = require('st'); 24 | const ENV_PORT = process.env.PORT; 25 | const PORT = ENV_PORT !== undefined ? ENV_PORT : 3000; 26 | 27 | const server = http.createServer( 28 | st({ 29 | path: __dirname + '/static', 30 | url: '/', 31 | cors: true 32 | }) 33 | ); 34 | 35 | server.listen(PORT, () => console.log( 36 | `Demo server started, hit http://localhost:${server.address().port}/hello.txt to try it`)); 37 | 38 | module.exports = server; 39 | -------------------------------------------------------------------------------- /demo/justrequireagent.js: -------------------------------------------------------------------------------- 1 | const flushOnExit = process.env.flushBeforeExit ? process.env.flushBeforeExit === 'yes' : true; 2 | const port = process.env.runtimeAgentPort || 9000; 3 | 4 | // load the agent 5 | require('../lib')({ 6 | url: `http://localhost:${port}/api/v1/beacon`, 7 | projectId: 'hurr durr', 8 | flushOnExit, 9 | }); 10 | 11 | // do some.. stuff 12 | console.log('henlo!'); 13 | let i = 0; 14 | while (i < 100) { 15 | i += 1; 16 | } 17 | 18 | // and.. we're done! 19 | console.log('ok bye'); 20 | -------------------------------------------------------------------------------- /demo/static/hello.txt: -------------------------------------------------------------------------------- 1 | Hello there! 2 | 3 | You've just triggered a vulnerable method in `st`, congratulations! 4 | 5 | This event is being recorded and will be sent to the homebase service shortly. 6 | Refresh this page to trigger the event once again. 7 | -------------------------------------------------------------------------------- /lib/ast.js: -------------------------------------------------------------------------------- 1 | const acorn = require('acorn'); 2 | 3 | function findAllVulnerableFunctionsInScript(scriptContent, vulnerableFunctionNames) { 4 | const declaredFunctions = {}; 5 | const parser = new acorn.Parser( 6 | {locations: true, sourceType: 'module'}, scriptContent); 7 | parser.strict = false; 8 | const parsedScript = parser.parse(); 9 | const body = parsedScript.body; 10 | body.forEach(function (node) { 11 | inspectNode(node, 12 | [], 13 | (nameParts, loc) => { 14 | const mangled = nameParts.join('.'); 15 | if (vulnerableFunctionNames.includes(mangled)) { 16 | declaredFunctions[mangled] = loc; 17 | } 18 | }); 19 | }); 20 | 21 | return declaredFunctions; 22 | } 23 | 24 | function inspectNode(node, path, cb, expectingAnonymousDeclaration) { 25 | if (!node) { 26 | return; 27 | } 28 | switch (node.type) { 29 | case 'FunctionDeclaration': { 30 | const loc = node.body.loc; 31 | const newPath = path.concat(unpackName(node.id)); 32 | cb(newPath, loc); 33 | inspectNode(node.body, newPath, cb); 34 | break; 35 | } 36 | case 'ArrowFunctionExpression': { 37 | if (expectingAnonymousDeclaration) { 38 | cb(path, node.loc); 39 | } 40 | inspectNode(node.body, path, cb); 41 | break; 42 | } 43 | case 'FunctionExpression': { 44 | let newPath = path; 45 | const name = unpackName(node.id); 46 | 47 | if (expectingAnonymousDeclaration) { 48 | // if we're in a context where anonymous makes sense, 49 | // we discard the function name, to avoid duplication 50 | cb(path, node.body.loc); 51 | } else if (0 !== name.length) { 52 | newPath = path.concat(name); 53 | cb(newPath, node.body.loc); 54 | } 55 | 56 | inspectNode(node.body, newPath, cb); 57 | break; 58 | } 59 | case 'IfStatement': 60 | inspectNode(node.test, path, cb); 61 | inspectNode(node.consequent, path, cb); 62 | inspectNode(node.alternate, path, cb); 63 | break; 64 | case 'ExpressionStatement': 65 | inspectNode(node.expression, path, cb); 66 | break; 67 | case 'ArrayExpression': 68 | for (const el of node.elements) { 69 | inspectNode(el, path, cb); 70 | } 71 | break; 72 | case 'CallExpression': 73 | inspectNode(node.callee, path, cb); 74 | node.arguments.forEach((arg) => inspectNode(arg, path, cb)); 75 | break; 76 | case 'VariableDeclaration': 77 | node.declarations.forEach((decl) => { 78 | let newPath; 79 | if (decl.init && decl.init.id && decl.init.id.name) { 80 | newPath = path.concat(decl.init.id.name); 81 | } else if (decl.id && decl.id.name) { 82 | newPath = path.concat(decl.id.name); 83 | } 84 | 85 | inspectNode(decl.init, newPath, cb, true); 86 | }); 87 | break; 88 | case 'ExportNamedDeclaration': 89 | inspectNode(node.declaration, path, cb); 90 | break; 91 | case 'ExportDefaultDeclaration': 92 | inspectNode(node.declaration, path.concat('module.exports'), cb, true); 93 | break; 94 | case 'AssignmentExpression': { 95 | inspectNode(node.left, path, cb); 96 | inspectNode(node.right, path.concat(unpackName(node.left)), cb, true); 97 | break; 98 | } 99 | case 'LogicalExpression': 100 | inspectNode(node.left, path, cb); 101 | inspectNode(node.right, path, cb); 102 | break; 103 | case 'UnaryExpression': 104 | inspectNode(node.argument, path, cb); 105 | break; 106 | case 'ObjectExpression': 107 | for (const prop of node.properties) { 108 | inspectNode(prop, path, cb); 109 | } 110 | break; 111 | case 'BlockStatement': 112 | for (const statement of node.body) { 113 | inspectNode(statement, path, cb); 114 | } 115 | break; 116 | case 'Property': 117 | const key = unpackName(node.key); 118 | if (0 === key.length) { 119 | // e.g. { ["concatenation" + "here"]: 5 } 120 | return; 121 | } 122 | inspectNode(node.value, path.concat(key), cb, true); 123 | break; 124 | case 'ClassDeclaration': { 125 | const name = node.id; 126 | if (name.type !== 'Identifier') { 127 | return; 128 | } 129 | const body = node.body; 130 | if (body.type !== 'ClassBody') { 131 | return; 132 | } 133 | body.body.forEach(child => { 134 | inspectNode(child, path.concat(`${name.name}.prototype`), cb, true); 135 | }); 136 | break; 137 | } 138 | case 'MethodDefinition': { 139 | const key = node.key; 140 | if (key.type !== 'Identifier') { 141 | return; 142 | } 143 | inspectNode(node.value, path.concat(`${key.name}`), cb, true); 144 | break; 145 | } 146 | case 'MemberExpression': 147 | inspectNode(node.object, path, cb); 148 | break; 149 | case 'ReturnStatement': 150 | inspectNode(node.argument, path, cb); 151 | break; 152 | case 'Identifier': 153 | break; 154 | case 'EmptyStatement': 155 | break; 156 | } 157 | } 158 | 159 | function unpackName(node) { 160 | if (!node) { 161 | return []; 162 | } 163 | 164 | const name = []; 165 | switch (node.type) { 166 | case 'MemberExpression': { 167 | pushAll(name, unpackName(node.object)); 168 | pushAll(name, unpackName(node.property)); 169 | break; 170 | } 171 | case 'Identifier': 172 | name.push(node.name); 173 | break; 174 | case 'Literal': 175 | name.push(node.value); 176 | break; 177 | } 178 | 179 | return name; 180 | } 181 | 182 | function pushAll(array, values) { 183 | array.push.apply(array, values); 184 | } 185 | 186 | module.exports = {findAllVulnerableFunctionsInScript}; 187 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const debug = require('debug')('snyk:nodejs-runtime-agent:config'); 3 | const uuidv4 = require('uuid/v4'); 4 | 5 | module.exports = { 6 | initConfig, 7 | }; 8 | 9 | function initConfig(startingConfig) { 10 | debug('Starting with config', startingConfig); 11 | validateStartingConfig(startingConfig); 12 | const {baseUrl = 'https://homebase.snyk.io'} = startingConfig; 13 | const config = {}; 14 | 15 | config['enable'] = true; 16 | config['flushOnExit'] = true; 17 | config['agentId'] = uuidv4(); 18 | config['beaconIntervalMs'] = 60 * 1000; 19 | config['snapshotIntervalMs'] = 60 * 60 * 1000; 20 | config['beaconUrl'] = `${baseUrl}/api/v1/beacon`; 21 | config['snapshotUrl'] = `${baseUrl}/api/v2/snapshot/${startingConfig.projectId}/node`; 22 | config['allowUnknownCA'] = false; 23 | 24 | config['functionPaths'] = { 25 | repo: { 26 | snapshot: path.join(__dirname, './resources/functions.repo.json'), 27 | date: path.join(__dirname, './resources/build-date.repo'), 28 | }, 29 | bundle: { 30 | snapshot: path.join(__dirname, './resources/functions.bundle.json'), 31 | date: path.join(__dirname, './resources/build-date.bundle'), 32 | }, 33 | }; 34 | 35 | if ('url' in startingConfig) { 36 | config['beaconUrl'] = startingConfig['url']; 37 | } 38 | 39 | const overrideables = [ 40 | 'snapshotUrl', 'snapshotIntervalMs', 'beaconIntervalMs', 41 | 'enable', 'flushOnExit', 'projectId', 'functionPaths', 42 | 'allowUnknownCA', 43 | ]; 44 | for (const key of overrideables) { 45 | if (key in startingConfig) { 46 | config[key] = startingConfig[key]; 47 | } 48 | } 49 | 50 | for (let key in config) { 51 | if (key !== 'initConfig') { 52 | this[key] = config[key]; 53 | } 54 | } 55 | 56 | debug('config after applying defaults', config); 57 | } 58 | 59 | function validateStartingConfig(startingConfig) { 60 | if (!startingConfig) { 61 | throw new Error('No config provided, disabling'); 62 | } 63 | if (!startingConfig.projectId) { 64 | throw new Error('No projectId defined in configuration'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/debugger-wrapper.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('snyk:nodejs-runtime-agent:inspector'); 2 | const inspector = require('inspector'); 3 | 4 | const state = require('./state'); 5 | const snapshot = require('./snapshot'); 6 | const moduleUtils = require('./module-utils'); 7 | 8 | let session; 9 | const breakpointsMap = {}; 10 | const suspendedBreakpointIds = []; 11 | const scriptUrlToInstrumentedFunctions = {}; 12 | 13 | function init() { 14 | if (!session) { 15 | session = new inspector.Session(); 16 | } 17 | try { 18 | session.connect(); 19 | } catch (error) { 20 | throw new Error('Debug session is already connected'); 21 | } 22 | 23 | session.on('Debugger.scriptParsed', (script) => { 24 | // dealing only with 3rd party modules. 25 | if (moduleUtils.isNative(script.params.url)) { 26 | return; 27 | } 28 | 29 | scriptUrlToInstrumentedFunctions[script.params.url] = {}; 30 | instrumentScript(script.params.url); 31 | }); 32 | session.on('Debugger.paused', (message) => { 33 | try { 34 | const pauseContext = message.params; 35 | handleDebuggerPausedEvent(pauseContext); 36 | } catch (error) { 37 | debug(`Error handling debugger paused event: ${JSON.stringify(message)}, ${error}`); 38 | } 39 | }); 40 | session.post('Debugger.enable'); 41 | session.post('Debugger.setBreakpointsActive', {active: true}); 42 | } 43 | 44 | function refreshInstrumentation() { 45 | Object.keys(scriptUrlToInstrumentedFunctions).forEach((scriptUrl) => { 46 | instrumentScript(scriptUrl); 47 | }); 48 | } 49 | 50 | function instrumentScript(scriptUrl) { 51 | const moduleInfo = moduleUtils.getModuleInfo(scriptUrl); 52 | state.addPackage(moduleInfo.name, moduleInfo.version); 53 | const functionsToInstrument = snapshot.getVulnerableFunctionsLocations(moduleInfo); 54 | 55 | Object.keys(functionsToInstrument).forEach((functionName) => { 56 | const functionLocation = functionsToInstrument[functionName]; 57 | if (!(functionName in scriptUrlToInstrumentedFunctions[scriptUrl])) { 58 | setBreakpointOnFunction(scriptUrl, moduleInfo, functionName, functionLocation); 59 | state.addFilter(moduleInfo.name, moduleInfo.scriptRelativePath, functionName); 60 | } 61 | }); 62 | 63 | Object.keys(scriptUrlToInstrumentedFunctions[scriptUrl]).forEach((functionName) => { 64 | if (!(functionName in functionsToInstrument)) { 65 | removeBreakpoint(scriptUrlToInstrumentedFunctions[scriptUrl][functionName]); 66 | delete scriptUrlToInstrumentedFunctions[scriptUrl][functionName]; 67 | state.removeFilter(moduleInfo.name, moduleInfo.scriptRelativePath, functionName); 68 | } 69 | }); 70 | } 71 | 72 | function setBreakpointOnFunction(scriptUrl, moduleInfo, functionName, functionLocation) { 73 | const breakpointParameters = { 74 | lineNumber: functionLocation.start.line - 1, // lines are 0-based in the inspector but 1-based in acorn 75 | columnNumber: functionLocation.start.column - 1, // same for columns 76 | url: scriptUrl, 77 | }; 78 | session.post('Debugger.setBreakpointByUrl', breakpointParameters, (error,response) => { 79 | if (error) { 80 | const errorEvent = {functionName, moduleInfo, error, message: 'Failed setting a breakpoint'}; 81 | debug(`Failed setting a breakpoint on method ${functionName} in module ${moduleInfo.name}:`); 82 | debug(error); 83 | state.addEvent({error: errorEvent}); 84 | return; 85 | } 86 | 87 | scriptUrlToInstrumentedFunctions[scriptUrl][functionName] = response.breakpointId; 88 | breakpointsMap[response.breakpointId] = {functionName, moduleInfo, scriptUrl, functionLocation}; 89 | debug(`Successfully set a breakpoint on method ${functionName} in module ${moduleInfo.name}`); 90 | }); 91 | } 92 | 93 | function resumeSnoozedBreakpoints() { 94 | suspendedBreakpointIds.forEach((breakpointId) => { 95 | debug(`resuming breakpoint ${breakpointId}`); 96 | const bpData = breakpointsMap[breakpointId]; 97 | setBreakpointOnFunction(bpData.scriptUrl, bpData.moduleInfo, bpData.functionName, bpData.functionLocation); 98 | }); 99 | suspendedBreakpointIds.length = 0; 100 | } 101 | 102 | function removeBreakpoint(breakpointId) { 103 | debug(`removing breakpoint ${breakpointId}`); 104 | session.post('Debugger.removeBreakpoint', {breakpointId}); 105 | } 106 | 107 | function snoozeBreakpoint(breakpointId) { 108 | removeBreakpoint(breakpointId); 109 | suspendedBreakpointIds.push(breakpointId); 110 | } 111 | 112 | function handleDebuggerPausedEvent(pauseContext) { 113 | if (ignorePause(pauseContext)) { 114 | return; 115 | } 116 | 117 | const breakpointId = pauseContext.hitBreakpoints[0]; 118 | const bpData = breakpointsMap[breakpointId]; 119 | snoozeBreakpoint(breakpointId); 120 | 121 | const methodEntry = { 122 | source: 'nodejs-runtime-agent', 123 | coordinates: [`node:${bpData.moduleInfo.name}:${bpData.moduleInfo.version}`], 124 | methodName: `${bpData.moduleInfo.name}.${bpData.functionName}`, 125 | filterName: null, // TODO 126 | sourceUri:`file://${bpData.moduleInfo.baseDir}/${bpData.moduleInfo.scriptRelativePath}`, 127 | sourceCrc32c: null, // TODO - probably in the scriptParsed context 128 | breakpointId, // TODO: needed? 129 | }; 130 | 131 | state.addEvent({methodEntry}); 132 | } 133 | 134 | function ignorePause(pauseContext) { 135 | if (pauseContext.reason !== 'other') { 136 | debug(`ignoring debugger pause due to reason being ${pauseContext.reason}.`); 137 | return true; 138 | } 139 | 140 | if (!pauseContext.hitBreakpoints || pauseContext.hitBreakpoints.length === 0) { 141 | debug('ignoring debugger pause due to no breakpoints being present.'); 142 | return true; 143 | } 144 | 145 | return false; 146 | } 147 | 148 | module.exports = { 149 | init, 150 | ignorePause, 151 | refreshInstrumentation, 152 | resumeSnoozedBreakpoints, 153 | scriptUrlToInstrumentedFunctions, 154 | }; 155 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('snyk:nodejs-runtime-agent'); 2 | 3 | const config = require('./config'); 4 | const snapshot = require('./snapshot'); 5 | const transmitter = require('./transmitter'); 6 | const debuggerWrapper = require('./debugger-wrapper'); 7 | 8 | const intervals = []; 9 | 10 | function start(startingConfig) { 11 | try { 12 | debug('If you have any issues during this beta, please contact runtime@snyk.io'); 13 | config.initConfig(startingConfig); 14 | 15 | if (!config.enable) { 16 | debug('Runtime agent is disabled'); 17 | return; 18 | } 19 | 20 | if (config.flushOnExit) { 21 | setFlushOnExit(); 22 | } 23 | 24 | snapshot.init(); 25 | debuggerWrapper.init(); 26 | 27 | const beaconInterval = setInterval(() => { 28 | try { 29 | debuggerWrapper.resumeSnoozedBreakpoints(); 30 | transmitter.handlePeriodicTasks(); 31 | } catch (error) { 32 | try { 33 | stopIntervals(); 34 | console.log('Error in Snyk runtime agent, please contact runtime@snyk.io', error); 35 | } catch (err) {} 36 | } 37 | }, config.beaconIntervalMs).unref(); 38 | intervals.push(beaconInterval); 39 | 40 | const snapshotInterval = setInterval(() => { 41 | try { 42 | snapshot.refresh() 43 | .then(() => { 44 | try { 45 | debuggerWrapper.refreshInstrumentation(); 46 | } catch (err) { 47 | debug(`failed re-instrumenting the agent ${err}, possibly due to bad snapshot.`); 48 | debug('will retry with the next snapshot'); 49 | } 50 | }) 51 | .catch((err) => { 52 | debug(`failed retrieving new snapshot ${err}, will retry again later`); 53 | }); 54 | } catch (error) { 55 | try { 56 | stopIntervals(); 57 | console.log('Error in Snyk runtime agent, please contact runtime@snyk.io', error); 58 | } catch (err) {} 59 | } 60 | }, config.snapshotIntervalMs).unref(); 61 | intervals.push(snapshotInterval); 62 | } catch (error) { 63 | // using console.log here as this is a one-time message 64 | // and will be used as a lead to enable debug mode 65 | console.log('Error while starting Snyk runtime agent, please contact runtime@snyk.io', error); 66 | }; 67 | } 68 | 69 | function setFlushOnExit() { 70 | let flushedOnce = false; 71 | function handleBeforeExit() { 72 | if (flushedOnce) { 73 | return; 74 | } 75 | 76 | debug('flushing last beacons before exiting'); 77 | transmitter.handlePeriodicTasks() 78 | .then(() => { 79 | flushedOnce = true; 80 | }) 81 | .catch(() => { 82 | flushedOnce = true; 83 | }); 84 | } 85 | 86 | process.on('beforeExit', handleBeforeExit); 87 | } 88 | 89 | function stopIntervals() { 90 | intervals.forEach((currentInterval) => { 91 | clearInterval(currentInterval); 92 | }); 93 | } 94 | 95 | module.exports = start; 96 | -------------------------------------------------------------------------------- /lib/module-utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | function isNative(scriptPath) { 5 | const normalizedScriptPath = normalizeScriptPath(scriptPath); 6 | return !normalizedScriptPath.includes('node_modules'); 7 | } 8 | 9 | function getModuleInfo(scriptPath) { 10 | const normalizedscriptPath = normalizeScriptPath(scriptPath); 11 | const segments = normalizedscriptPath.split(path.sep); 12 | const index = segments.lastIndexOf('node_modules'); 13 | const scoped = segments[index + 1][0] === '@'; 14 | const offset = scoped ? 3 : 2; 15 | const baseDir = segments.slice(0, index + offset).join(path.sep); 16 | const packageJsonStr = fs.readFileSync(path.join(baseDir, 'package.json')); 17 | const packageJson = JSON.parse(packageJsonStr); 18 | const moduleInfo = { 19 | version: packageJson.version, 20 | name: packageJson.name, 21 | baseDir: baseDir, 22 | scriptRelativePath: segments.slice(index + offset).join(path.sep), 23 | scriptPath: normalizedscriptPath, 24 | }; 25 | return moduleInfo; 26 | } 27 | 28 | function normalizeScriptPath(scriptPath) { 29 | let normalizedScriptPath = scriptPath; 30 | 31 | // Remove file prefix which was added in Node v10.12 32 | if (scriptPath.startsWith('file://') && path.sep === '/') { 33 | normalizedScriptPath = scriptPath.substring('file://'.length); 34 | } else if (scriptPath.startsWith('file:///') && path.sep === '\\') { 35 | normalizedScriptPath = scriptPath.substring('file:///'.length).replace(/\//g, '\\'); 36 | } 37 | return normalizedScriptPath; 38 | } 39 | 40 | function normaliseSeparator(pathToNormalise) { 41 | if (path.sep === '\\') { 42 | return pathToNormalise.replace(/\//g, '\\'); 43 | } 44 | return pathToNormalise; 45 | } 46 | 47 | function denormaliseSeparator(pathToDenormalise) { 48 | if (path.sep === '\\') { 49 | return pathToDenormalise.replace(/\\/g, '/'); 50 | } 51 | return pathToDenormalise; 52 | } 53 | 54 | module.exports = {isNative, getModuleInfo, normaliseSeparator, denormaliseSeparator}; 55 | -------------------------------------------------------------------------------- /lib/resources/build-date.repo: -------------------------------------------------------------------------------- 1 | Thu, 22 May 2019 09:53:29 GMT 2 | -------------------------------------------------------------------------------- /lib/resources/functions.repo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionId": { 4 | "className": null, 5 | "filePath": "lib/handlebars/helpers/lookup.js", 6 | "functionName": "module.exports" 7 | }, 8 | "packageName": "handlebars", 9 | "version": [ 10 | ">3.0.6 <4.1.2" 11 | ], 12 | "vulnId": "SNYK-JS-HANDLEBARS-174183" 13 | }, 14 | { 15 | "functionId": { 16 | "className": null, 17 | "filePath": "lib/base64.js", 18 | "functionName": "base64.decode" 19 | }, 20 | "packageName": "utile", 21 | "version": [ 22 | ">0.0.6 <=0.3.0" 23 | ], 24 | "vulnId": "npm:utile:20180614" 25 | }, 26 | { 27 | "functionId": { 28 | "className": null, 29 | "filePath": "lib/base64.js", 30 | "functionName": "base64.encode" 31 | }, 32 | "packageName": "utile", 33 | "version": [ 34 | ">0.0.6 <=0.3.0" 35 | ], 36 | "vulnId": "npm:utile:20180614" 37 | }, 38 | { 39 | "functionId": { 40 | "className": null, 41 | "filePath": "lib/js-yaml/loader.js", 42 | "functionName": "loadAll.storeMappingPair" 43 | }, 44 | "packageName": "js-yaml", 45 | "version": [ 46 | ">1.0.3 <=2.1.3" 47 | ], 48 | "vulnId": "SNYK-JS-JSYAML-174129" 49 | }, 50 | { 51 | "functionId": { 52 | "className": null, 53 | "filePath": "lib/js-yaml/loader.js", 54 | "functionName": "storeMappingPair" 55 | }, 56 | "packageName": "js-yaml", 57 | "version": [ 58 | ">2.1.3 <3.13.1" 59 | ], 60 | "vulnId": "SNYK-JS-JSYAML-174129" 61 | }, 62 | { 63 | "functionId": { 64 | "className": null, 65 | "filePath": "lib/hoek.js", 66 | "functionName": "exports.merge" 67 | }, 68 | "packageName": "hoek", 69 | "version": [ 70 | "<0.0.19" 71 | ], 72 | "vulnId": "npm:hoek:20180212" 73 | }, 74 | { 75 | "functionId": { 76 | "className": null, 77 | "filePath": "lib/index.js", 78 | "functionName": "exports.merge" 79 | }, 80 | "packageName": "hoek", 81 | "version": [ 82 | ">=5.0.0 <5.0.3" 83 | ], 84 | "vulnId": "npm:hoek:20180212" 85 | }, 86 | { 87 | "functionId": { 88 | "className": null, 89 | "filePath": "lib/index.js", 90 | "functionName": "exports.merge" 91 | }, 92 | "packageName": "hoek", 93 | "version": [ 94 | ">0.0.18 <4.2.1" 95 | ], 96 | "vulnId": "npm:hoek:20180212" 97 | }, 98 | { 99 | "functionId": { 100 | "className": null, 101 | "filePath": "core.js", 102 | "functionName": "isSafe" 103 | }, 104 | "packageName": "useragent", 105 | "version": [ 106 | ">=2.2.0 <2.2.1" 107 | ], 108 | "vulnId": "SNYK-JS-USERAGENT-174737" 109 | }, 110 | { 111 | "functionId": { 112 | "className": null, 113 | "filePath": "index.js", 114 | "functionName": "isSafe" 115 | }, 116 | "packageName": "useragent", 117 | "version": [ 118 | ">=2.1.13 <2.2.0", 119 | ">=2.2.1" 120 | ], 121 | "vulnId": "SNYK-JS-USERAGENT-174737" 122 | }, 123 | { 124 | "functionId": { 125 | "className": null, 126 | "filePath": "lib/writer.js", 127 | "functionName": "Writer.prototype._stat.statCb" 128 | }, 129 | "packageName": "fstream", 130 | "version": [ 131 | ">0.0.0 <1.0.12" 132 | ], 133 | "vulnId": "SNYK-JS-FSTREAM-174725" 134 | }, 135 | { 136 | "functionId": { 137 | "className": null, 138 | "filePath": "lib/parser_inline.js", 139 | "functionName": "validateLink" 140 | }, 141 | "packageName": "remarkable", 142 | "version": [ 143 | ">0.1.0" 144 | ], 145 | "vulnId": "SNYK-JS-REMARKABLE-174641" 146 | }, 147 | { 148 | "functionId": { 149 | "className": null, 150 | "filePath": "lib/eslint.js", 151 | "functionName": "module.exports.api.report" 152 | }, 153 | "packageName": "eslint", 154 | "version": [ 155 | ">=1.4.0 <4.0.0-beta.0" 156 | ], 157 | "vulnId": "npm:eslint:20180222" 158 | }, 159 | { 160 | "functionId": { 161 | "className": null, 162 | "filePath": "lib/linter.js", 163 | "functionName": "Linter.prototype.report" 164 | }, 165 | "packageName": "eslint", 166 | "version": [ 167 | ">=4.0.0-beta.0 <4.5.0" 168 | ], 169 | "vulnId": "npm:eslint:20180222" 170 | }, 171 | { 172 | "functionId": { 173 | "className": null, 174 | "filePath": "lib/report-translator.js", 175 | "functionName": "normalizeMessagePlaceholders" 176 | }, 177 | "packageName": "eslint", 178 | "version": [ 179 | ">=4.6.0 <4.15.0" 180 | ], 181 | "vulnId": "npm:eslint:20180222" 182 | }, 183 | { 184 | "functionId": { 185 | "className": null, 186 | "filePath": "lib/util/interpolate.js", 187 | "functionName": "module.exports" 188 | }, 189 | "packageName": "eslint", 190 | "version": [ 191 | ">=4.15.0 <4.18.2" 192 | ], 193 | "vulnId": "npm:eslint:20180222" 194 | }, 195 | { 196 | "functionId": { 197 | "className": null, 198 | "filePath": "lib/index.js", 199 | "functionName": "exports.unset" 200 | }, 201 | "packageName": "mpath", 202 | "version": [ 203 | ">=0.3.0 <0.5.1" 204 | ], 205 | "vulnId": "SNYK-JS-MPATH-72672" 206 | }, 207 | { 208 | "functionId": { 209 | "className": null, 210 | "filePath": "lib/unpack.js", 211 | "functionName": "Unpack.prototype.CHECKFS" 212 | }, 213 | "packageName": "tar", 214 | "version": [ 215 | ">=3.0.0 <4.4.2" 216 | ], 217 | "vulnId": "SNYK-JS-TAR-174125" 218 | }, 219 | { 220 | "functionId": { 221 | "className": null, 222 | "filePath": "lib/compress.js", 223 | "functionName": "Compressor" 224 | }, 225 | "packageName": "uglify-js", 226 | "version": [ 227 | ">=2.2.0 <2.4.24" 228 | ], 229 | "vulnId": "npm:uglify-js:20150824" 230 | }, 231 | { 232 | "functionId": { 233 | "className": null, 234 | "filePath": "validator.js", 235 | "functionName": "isDataURI" 236 | }, 237 | "packageName": "validator", 238 | "version": [ 239 | ">=5.2.0 <9.4.1" 240 | ], 241 | "vulnId": "npm:validator:20180218" 242 | }, 243 | { 244 | "functionId": { 245 | "className": null, 246 | "filePath": "lib/isDataURI.js", 247 | "functionName": "isDataURI" 248 | }, 249 | "packageName": "validator", 250 | "version": [ 251 | ">=5.2.0 <9.4.1" 252 | ], 253 | "vulnId": "npm:validator:20180218" 254 | }, 255 | { 256 | "functionId": { 257 | "className": null, 258 | "filePath": "lib/bson/decimal128.js", 259 | "functionName": "Decimal128.fromString" 260 | }, 261 | "packageName": "bson", 262 | "version": [ 263 | ">=0.5.0 <1.0.5" 264 | ], 265 | "vulnId": "npm:bson:20180225" 266 | }, 267 | { 268 | "functionId": { 269 | "className": null, 270 | "filePath": "lib/utils.js", 271 | "functionName": "exports.parseHost" 272 | }, 273 | "packageName": "hawk", 274 | "version": [ 275 | ">=0.3.0 <3.1.3", 276 | ">=4.0.0 <4.1.1" 277 | ], 278 | "vulnId": "npm:hawk:20160119" 279 | }, 280 | { 281 | "functionId": { 282 | "className": null, 283 | "filePath": "lib/hawk.js", 284 | "functionName": "exports.authenticate" 285 | }, 286 | "packageName": "hawk", 287 | "version": [ 288 | "<=0.0.6" 289 | ], 290 | "vulnId": "npm:hawk:20160119" 291 | }, 292 | { 293 | "functionId": { 294 | "className": null, 295 | "filePath": "lib/index.js", 296 | "functionName": "exports.authenticate" 297 | }, 298 | "packageName": "hawk", 299 | "version": [ 300 | ">=0.0.7 <0.10.0" 301 | ], 302 | "vulnId": "npm:hawk:20160119" 303 | }, 304 | { 305 | "functionId": { 306 | "className": null, 307 | "filePath": "lib/server.js", 308 | "functionName": "exports.authenticate" 309 | }, 310 | "packageName": "hawk", 311 | "version": [ 312 | ">=0.10.0 <0.12.1" 313 | ], 314 | "vulnId": "npm:hawk:20160119" 315 | }, 316 | { 317 | "functionId": { 318 | "className": null, 319 | "filePath": "lib/server.js", 320 | "functionName": "exports.authenticateBewit" 321 | }, 322 | "packageName": "hawk", 323 | "version": [ 324 | ">=0.12.1 <3.1.3", 325 | ">=4.0.0 <4.1.1" 326 | ], 327 | "vulnId": "npm:hawk:20160119" 328 | }, 329 | { 330 | "functionId": { 331 | "className": null, 332 | "filePath": "lib/verify.js", 333 | "functionName": "module.exports.verifySignature" 334 | }, 335 | "packageName": "http-signature", 336 | "version": [ 337 | "<0.10.1" 338 | ], 339 | "vulnId": "npm:http-signature:20150122" 340 | }, 341 | { 342 | "functionId": { 343 | "className": null, 344 | "filePath": "lib/verify.js", 345 | "functionName": "module.exports.verifyHMAC" 346 | }, 347 | "packageName": "http-signature", 348 | "version": [ 349 | ">=0.11.0 <1.0.0" 350 | ], 351 | "vulnId": "npm:http-signature:20150122" 352 | }, 353 | { 354 | "functionId": { 355 | "className": null, 356 | "filePath": "lodash.js", 357 | "functionName": "hasUnicodeWord" 358 | }, 359 | "packageName": "lodash", 360 | "version": [ 361 | ">=4.15.0 <4.17.11" 362 | ], 363 | "vulnId": "SNYK-JS-LODASH-73639" 364 | }, 365 | { 366 | "functionId": { 367 | "className": null, 368 | "filePath": "lib/adapters/http.js", 369 | "functionName": "module.exports" 370 | }, 371 | "packageName": "axios", 372 | "version": [ 373 | ">0.1.0" 374 | ], 375 | "vulnId": "SNYK-JS-AXIOS-174505" 376 | }, 377 | { 378 | "functionId": { 379 | "className": null, 380 | "filePath": "lib/jwt.js", 381 | "functionName": "jwt.decode" 382 | }, 383 | "packageName": "jwt-simple", 384 | "version": [ 385 | "<0.5.3" 386 | ], 387 | "vulnId": "SNYK-JS-JWTSIMPLE-174523" 388 | }, 389 | { 390 | "functionId": { 391 | "className": null, 392 | "filePath": "typed-function.js", 393 | "functionName": "typed" 394 | }, 395 | "packageName": "typed-function", 396 | "version": [ 397 | "0.3.1" 398 | ], 399 | "vulnId": "SNYK-JS-TYPEDFUNCTION-174139" 400 | }, 401 | { 402 | "functionId": { 403 | "className": null, 404 | "filePath": "typed-function.js", 405 | "functionName": "_typed" 406 | }, 407 | "packageName": "typed-function", 408 | "version": [ 409 | "<0.7.0" 410 | ], 411 | "vulnId": "SNYK-JS-TYPEDFUNCTION-174139" 412 | }, 413 | { 414 | "functionId": { 415 | "className": null, 416 | "filePath": "typed-function.js", 417 | "functionName": "create._typed" 418 | }, 419 | "packageName": "typed-function", 420 | "version": [ 421 | "<0.10.6" 422 | ], 423 | "vulnId": "SNYK-JS-TYPEDFUNCTION-174139" 424 | }, 425 | { 426 | "functionId": { 427 | "className": null, 428 | "filePath": "index.js", 429 | "functionName": "compile" 430 | }, 431 | "packageName": "morgan", 432 | "version": [ 433 | "<1.9.1" 434 | ], 435 | "vulnId": "SNYK-JS-MORGAN-72579" 436 | }, 437 | { 438 | "functionId": { 439 | "className": null, 440 | "filePath": "test/core.js", 441 | "functionName": "module.exports.jQuery.extend(Object, Object)" 442 | }, 443 | "packageName": "jquery", 444 | "version": [ 445 | "<=1.8.3" 446 | ], 447 | "vulnId": "SNYK-JS-JQUERY-174006" 448 | }, 449 | { 450 | "functionId": { 451 | "className": null, 452 | "filePath": "src/core.js", 453 | "functionName": "jQuery.extend.jQuery.fn.extend" 454 | }, 455 | "packageName": "jquery", 456 | "version": [ 457 | ">1.8.3 <=2.2.4" 458 | ], 459 | "vulnId": "SNYK-JS-JQUERY-174006" 460 | }, 461 | { 462 | "functionId": { 463 | "className": null, 464 | "filePath": "dist/core.js", 465 | "functionName": "jQuery.extend.jQuery.fn.extend" 466 | }, 467 | "packageName": "jquery", 468 | "version": [ 469 | ">2.2.4 <=3.3.1" 470 | ], 471 | "vulnId": "SNYK-JS-JQUERY-174006" 472 | }, 473 | { 474 | "functionId": { 475 | "className": null, 476 | "filePath": "adm-zip.js", 477 | "functionName": "module.exports.getEntry" 478 | }, 479 | "packageName": "adm-zip", 480 | "version": [ 481 | ">0.1.1 <0.4.11" 482 | ], 483 | "vulnId": "npm:adm-zip:20180415" 484 | }, 485 | { 486 | "functionId": { 487 | "className": null, 488 | "filePath": "index.js", 489 | "functionName": "createHeaderGetter" 490 | }, 491 | "packageName": "method-override", 492 | "version": [ 493 | ">1.0.2 <2.3.10" 494 | ], 495 | "vulnId": "npm:method-override:20170927" 496 | }, 497 | { 498 | "functionId": { 499 | "className": null, 500 | "filePath": "lib/extract.js", 501 | "functionName": "Extract" 502 | }, 503 | "packageName": "tar", 504 | "version": [ 505 | ">0.0.1 <2.0.0" 506 | ], 507 | "vulnId": "npm:tar:20151103" 508 | }, 509 | { 510 | "functionId": { 511 | "className": null, 512 | "filePath": "simple-markdown.js", 513 | "functionName": "sanitizeUrl" 514 | }, 515 | "packageName": "simple-markdown", 516 | "version": [ 517 | "<0.4.4" 518 | ], 519 | "vulnId": "SNYK-JS-SIMPLEMARKDOWN-173788" 520 | }, 521 | { 522 | "functionId": { 523 | "className": null, 524 | "filePath": "lib/validators.js", 525 | "functionName": "validators.isEmail" 526 | }, 527 | "packageName": "validator", 528 | "version": [ 529 | ">2.0.0 <=2.1.0" 530 | ], 531 | "vulnId": "npm:validator:20130705" 532 | }, 533 | { 534 | "functionId": { 535 | "className": null, 536 | "filePath": "validator.js", 537 | "functionName": "validator.isEmail" 538 | }, 539 | "packageName": "validator", 540 | "version": [ 541 | ">2.1.0 <3.22.1" 542 | ], 543 | "vulnId": "npm:validator:20130705" 544 | }, 545 | { 546 | "functionId": { 547 | "className": null, 548 | "filePath": "index.js", 549 | "functionName": "module.exports.result.walk" 550 | }, 551 | "packageName": "static-eval", 552 | "version": [ 553 | "<2.0.0" 554 | ], 555 | "vulnId": "npm:static-eval:20171016" 556 | }, 557 | { 558 | "functionId": { 559 | "className": null, 560 | "filePath": "unescapeHTML.js", 561 | "functionName": "module.exports" 562 | }, 563 | "packageName": "underscore.string", 564 | "version": [ 565 | ">2.4.1 <3.3.5" 566 | ], 567 | "vulnId": "npm:underscore.string:20170908" 568 | }, 569 | { 570 | "functionId": { 571 | "className": null, 572 | "filePath": "index.js", 573 | "functionName": "nativeTimingSafeEqual" 574 | }, 575 | "packageName": "safe-compare", 576 | "version": [ 577 | ">=1.0.4 <1.1.4" 578 | ], 579 | "vulnId": "SNYK-JS-SAFECOMPARE-173780" 580 | }, 581 | { 582 | "functionId": { 583 | "className": null, 584 | "filePath": "src/JSONPretty.js", 585 | "functionName": "module.exports.render" 586 | }, 587 | "packageName": "react-json-pretty", 588 | "version": [ 589 | "<2.0.0" 590 | ], 591 | "vulnId": "SNYK-JS-REACTJSONPRETTY-173779" 592 | }, 593 | { 594 | "functionId": { 595 | "className": null, 596 | "filePath": "dist/JSONPretty.js", 597 | "functionName": "JSONPretty.JSONPretty.prototype.render" 598 | }, 599 | "packageName": "react-json-pretty", 600 | "version": [ 601 | ">=2.0.0 <2.0.1" 602 | ], 603 | "vulnId": "SNYK-JS-REACTJSONPRETTY-173779" 604 | }, 605 | { 606 | "functionId": { 607 | "className": null, 608 | "filePath": "src/parse.js", 609 | "functionName": "parse.parseExtension.parseExtension_block" 610 | }, 611 | "packageName": "protobufjs", 612 | "version": [ 613 | "<5.0.3", 614 | ">=6.0.0 <6.8.6" 615 | ], 616 | "vulnId": "npm:protobufjs:20180305" 617 | }, 618 | { 619 | "functionId": { 620 | "className": null, 621 | "filePath": "src/parse.js", 622 | "functionName": "parse.parseExtension" 623 | }, 624 | "packageName": "protobufjs", 625 | "version": [ 626 | "<5.0.3", 627 | ">=6.0.0 <6.8.6" 628 | ], 629 | "vulnId": "npm:protobufjs:20180305" 630 | }, 631 | { 632 | "functionId": { 633 | "className": null, 634 | "filePath": "src/parse.js", 635 | "functionName": "parse.parseMethod" 636 | }, 637 | "packageName": "protobufjs", 638 | "version": [ 639 | "<5.0.3", 640 | ">=6.0.0 <6.8.6" 641 | ], 642 | "vulnId": "npm:protobufjs:20180305" 643 | }, 644 | { 645 | "functionId": { 646 | "className": null, 647 | "filePath": "src/parse.js", 648 | "functionName": "parse.parseOption" 649 | }, 650 | "packageName": "protobufjs", 651 | "version": [ 652 | "<5.0.3", 653 | ">=6.0.0 <6.8.6" 654 | ], 655 | "vulnId": "npm:protobufjs:20180305" 656 | }, 657 | { 658 | "functionId": { 659 | "className": null, 660 | "filePath": "src/parse.js", 661 | "functionName": "parse.parseMapField" 662 | }, 663 | "packageName": "protobufjs", 664 | "version": [ 665 | "<5.0.3", 666 | ">=6.0.0 <6.8.6" 667 | ], 668 | "vulnId": "npm:protobufjs:20180305" 669 | }, 670 | { 671 | "functionId": { 672 | "className": null, 673 | "filePath": "src/parse.js", 674 | "functionName": "parse.parseField" 675 | }, 676 | "packageName": "protobufjs", 677 | "version": [ 678 | "<5.0.3", 679 | ">=6.0.0 <6.8.6" 680 | ], 681 | "vulnId": "npm:protobufjs:20180305" 682 | }, 683 | { 684 | "functionId": { 685 | "className": null, 686 | "filePath": "src/parse.js", 687 | "functionName": "parse.parseType.parseType_block" 688 | }, 689 | "packageName": "protobufjs", 690 | "version": [ 691 | "<5.0.3", 692 | ">=6.0.0 <6.8.6" 693 | ], 694 | "vulnId": "npm:protobufjs:20180305" 695 | }, 696 | { 697 | "functionId": { 698 | "className": null, 699 | "filePath": "src/parse.js", 700 | "functionName": "parse.parsePackage" 701 | }, 702 | "packageName": "protobufjs", 703 | "version": [ 704 | "<5.0.3", 705 | ">=6.0.0 <6.8.6" 706 | ], 707 | "vulnId": "npm:protobufjs:20180305" 708 | }, 709 | { 710 | "functionId": { 711 | "className": null, 712 | "filePath": "src/parse.js", 713 | "functionName": "parse.readValue" 714 | }, 715 | "packageName": "protobufjs", 716 | "version": [ 717 | "<5.0.3", 718 | ">=6.0.0 <6.8.6" 719 | ], 720 | "vulnId": "npm:protobufjs:20180305" 721 | }, 722 | { 723 | "functionId": { 724 | "className": null, 725 | "filePath": "lib/time-span.js", 726 | "functionName": "exports.parseDate" 727 | }, 728 | "packageName": "timespan", 729 | "version": [ 730 | ">=2.1.0" 731 | ], 732 | "vulnId": "npm:timespan:20170907" 733 | }, 734 | { 735 | "functionId": { 736 | "className": null, 737 | "filePath": "lib/time-span.js", 738 | "functionName": "exports.test" 739 | }, 740 | "packageName": "timespan", 741 | "version": [ 742 | "*" 743 | ], 744 | "vulnId": "npm:timespan:20170907" 745 | }, 746 | { 747 | "functionId": { 748 | "className": null, 749 | "filePath": "lib/time-span.js", 750 | "functionName": "exports.parse" 751 | }, 752 | "packageName": "timespan", 753 | "version": [ 754 | "*" 755 | ], 756 | "vulnId": "npm:timespan:20170907" 757 | }, 758 | { 759 | "functionId": { 760 | "className": null, 761 | "filePath": "ejs.js", 762 | "functionName": "1.exports.render" 763 | }, 764 | "packageName": "ejs", 765 | "version": [ 766 | "<2.2.1" 767 | ], 768 | "vulnId": "npm:ejs:20161128" 769 | }, 770 | { 771 | "functionId": { 772 | "className": null, 773 | "filePath": "lib/ejs.js", 774 | "functionName": "exports.render" 775 | }, 776 | "packageName": "ejs", 777 | "version": [ 778 | "<2.2.1" 779 | ], 780 | "vulnId": "npm:ejs:20161128" 781 | }, 782 | { 783 | "functionId": { 784 | "className": null, 785 | "filePath": "lib/growl.js", 786 | "functionName": "exports.notify" 787 | }, 788 | "packageName": "growl", 789 | "version": [ 790 | "<1.4.0" 791 | ], 792 | "vulnId": "npm:growl:20160721" 793 | }, 794 | { 795 | "functionId": { 796 | "className": null, 797 | "filePath": "ua-parser.js", 798 | "functionName": "mapper.regex" 799 | }, 800 | "packageName": "ua-parser-js", 801 | "version": [ 802 | ">=0.4.0 <0.5.15" 803 | ], 804 | "vulnId": "npm:ua-parser-js:20180227" 805 | }, 806 | { 807 | "functionId": { 808 | "className": null, 809 | "filePath": "src/ua-parser.js", 810 | "functionName": "mapper.regex" 811 | }, 812 | "packageName": "ua-parser-js", 813 | "version": [ 814 | "=0.5.20" 815 | ], 816 | "vulnId": "npm:ua-parser-js:20180227" 817 | }, 818 | { 819 | "functionId": { 820 | "className": null, 821 | "filePath": "dist/handlebars.js", 822 | "functionName": "JavaScriptCompiler.Handlebars.JavaScriptCompiler" 823 | }, 824 | "packageName": "handlebars", 825 | "version": [ 826 | ">=1.0.6 <=1.0.12" 827 | ], 828 | "vulnId": "SNYK-JS-HANDLEBARS-173692" 829 | }, 830 | { 831 | "functionId": { 832 | "className": null, 833 | "filePath": "ejs.js", 834 | "functionName": "1.cpOptsInData" 835 | }, 836 | "packageName": "ejs", 837 | "version": [ 838 | ">=2.2.1 <2.5.3" 839 | ], 840 | "vulnId": "npm:ejs:20161128" 841 | }, 842 | { 843 | "functionId": { 844 | "className": null, 845 | "filePath": "ms.js", 846 | "functionName": "parse" 847 | }, 848 | "packageName": "ms", 849 | "version": [ 850 | ">0.1.0 <=0.3.0" 851 | ], 852 | "vulnId": "npm:ms:20170412" 853 | }, 854 | { 855 | "functionId": { 856 | "className": null, 857 | "filePath": "main.js", 858 | "functionName": "Request.prototype.request" 859 | }, 860 | "packageName": "request", 861 | "version": [ 862 | ">=2.2.6 <2.9.150" 863 | ], 864 | "vulnId": "npm:request:20160119" 865 | }, 866 | { 867 | "functionId": { 868 | "className": null, 869 | "filePath": "main.js", 870 | "functionName": "Request.prototype.multipart" 871 | }, 872 | "packageName": "request", 873 | "version": [ 874 | ">=2.9.150 <2.16.0" 875 | ], 876 | "vulnId": "npm:request:20160119" 877 | }, 878 | { 879 | "functionId": { 880 | "className": null, 881 | "filePath": "index.js", 882 | "functionName": "Request.prototype.multipart" 883 | }, 884 | "packageName": "request", 885 | "version": [ 886 | ">=2.16.0 <2.27.0" 887 | ], 888 | "vulnId": "npm:request:20160119" 889 | }, 890 | { 891 | "functionId": { 892 | "className": null, 893 | "filePath": "request.js", 894 | "functionName": "Request.prototype.multipart" 895 | }, 896 | "packageName": "request", 897 | "version": [ 898 | ">=2.27.0 <2.54.0" 899 | ], 900 | "vulnId": "npm:request:20160119" 901 | }, 902 | { 903 | "functionId": { 904 | "className": null, 905 | "filePath": "cjs/react-dom-server.node.development.js", 906 | "functionName": "DOMMarkupOperations.createMarkupForProperty" 907 | }, 908 | "packageName": "react-dom", 909 | "version": [ 910 | "16.0.0" 911 | ], 912 | "vulnId": "npm:react-dom:20180802" 913 | }, 914 | { 915 | "functionId": { 916 | "className": null, 917 | "filePath": "cjs/react-dom-server.browser.development.js", 918 | "functionName": "DOMMarkupOperations.createMarkupForProperty" 919 | }, 920 | "packageName": "react-dom", 921 | "version": [ 922 | "16.0.0" 923 | ], 924 | "vulnId": "npm:react-dom:20180802" 925 | }, 926 | { 927 | "functionId": { 928 | "className": null, 929 | "filePath": "cjs/react-dom-server.node.development.js", 930 | "functionName": "createMarkupForProperty" 931 | }, 932 | "packageName": "react-dom", 933 | "version": [ 934 | ">=16.0.0 <16.0.1", 935 | ">=16.1.0 <16.1.2", 936 | ">=16.2.0 <16.2.1", 937 | ">=16.3.0 <16.3.3", 938 | ">=16.4.0 <16.4.2" 939 | ], 940 | "vulnId": "npm:react-dom:20180802" 941 | }, 942 | { 943 | "functionId": { 944 | "className": null, 945 | "filePath": "cjs/react-dom-server.browser.development.js", 946 | "functionName": "createMarkupForProperty" 947 | }, 948 | "packageName": "react-dom", 949 | "version": [ 950 | ">=16.0.0 <16.0.1", 951 | ">=16.1.0 <16.1.2", 952 | ">=16.2.0 <16.2.1", 953 | ">=16.3.0 <16.3.3", 954 | ">=16.4.0 <16.4.2" 955 | ], 956 | "vulnId": "npm:react-dom:20180802" 957 | }, 958 | { 959 | "functionId": { 960 | "className": null, 961 | "filePath": "dist/lodash.js", 962 | "functionName": "merge" 963 | }, 964 | "packageName": "lodash", 965 | "version": [ 966 | ">=1.0.0 <1.0.3" 967 | ], 968 | "vulnId": "SNYK-JS-LODASH-73638" 969 | }, 970 | { 971 | "functionId": { 972 | "className": null, 973 | "filePath": "dist/lodash.js", 974 | "functionName": "merge" 975 | }, 976 | "packageName": "lodash", 977 | "version": [ 978 | ">= 1.0.0 <1.0.3" 979 | ], 980 | "vulnId": "npm:lodash:20180130" 981 | }, 982 | { 983 | "functionId": { 984 | "className": null, 985 | "filePath": "mime.js", 986 | "functionName": "mime.module.exports.lookup" 987 | }, 988 | "packageName": "mime", 989 | "version": [ 990 | "<1.2.6" 991 | ], 992 | "vulnId": "npm:mime:20170907" 993 | }, 994 | { 995 | "functionId": { 996 | "className": null, 997 | "filePath": "index.js", 998 | "functionName": "padString" 999 | }, 1000 | "packageName": "base64url", 1001 | "version": [ 1002 | "<=1.0.6" 1003 | ], 1004 | "vulnId": "npm:base64url:20180511" 1005 | }, 1006 | { 1007 | "functionId": { 1008 | "className": null, 1009 | "filePath": "index.js", 1010 | "functionName": "deepExtend" 1011 | }, 1012 | "packageName": "deep-extend", 1013 | "version": [ 1014 | "<0.2.1" 1015 | ], 1016 | "vulnId": "npm:deep-extend:20180409" 1017 | }, 1018 | { 1019 | "functionId": { 1020 | "className": null, 1021 | "filePath": "index.js", 1022 | "functionName": "module.exports" 1023 | }, 1024 | "packageName": "deep-extend", 1025 | "version": [ 1026 | "0.2.1" 1027 | ], 1028 | "vulnId": "npm:deep-extend:20180409" 1029 | }, 1030 | { 1031 | "functionId": { 1032 | "className": null, 1033 | "filePath": "index.js", 1034 | "functionName": "module.exports.deepExtend" 1035 | }, 1036 | "packageName": "deep-extend", 1037 | "version": [ 1038 | ">=0.2.2 <0.2.5" 1039 | ], 1040 | "vulnId": "npm:deep-extend:20180409" 1041 | }, 1042 | { 1043 | "functionId": { 1044 | "className": null, 1045 | "filePath": "index.js", 1046 | "functionName": "deepExtend.module.exports" 1047 | }, 1048 | "packageName": "deep-extend", 1049 | "version": [ 1050 | ">=0.2.5 <0.4.0" 1051 | ], 1052 | "vulnId": "npm:deep-extend:20180409" 1053 | }, 1054 | { 1055 | "functionId": { 1056 | "className": null, 1057 | "filePath": "index.js", 1058 | "functionName": "Framework.prototype.responseStatic" 1059 | }, 1060 | "packageName": "total.js", 1061 | "version": [ 1062 | ">=2.1.0 <2.1.1", 1063 | ">=2.2.0 <2.2.1", 1064 | ">=2.3.0 <2.3.1", 1065 | ">=2.4.0 <2.4.1" 1066 | ], 1067 | "vulnId": "SNYK-JS-TOTALJS-173710" 1068 | }, 1069 | { 1070 | "functionId": { 1071 | "className": null, 1072 | "filePath": "index.js", 1073 | "functionName": "safeGet" 1074 | }, 1075 | "packageName": "lodash.merge", 1076 | "version": [ 1077 | ">4.6.0 <=4.6.1" 1078 | ], 1079 | "vulnId": "SNYK-JS-LODASHMERGE-173732" 1080 | }, 1081 | { 1082 | "functionId": { 1083 | "className": null, 1084 | "filePath": "index.js", 1085 | "functionName": "baseMergeDeep" 1086 | }, 1087 | "packageName": "lodash.merge", 1088 | "version": [ 1089 | ">=3.0.0 <4.6.1" 1090 | ], 1091 | "vulnId": "SNYK-JS-LODASHMERGE-173732" 1092 | }, 1093 | { 1094 | "functionId": { 1095 | "className": null, 1096 | "filePath": "index.js", 1097 | "functionName": "baseMerge" 1098 | }, 1099 | "packageName": "lodash.merge", 1100 | "version": [ 1101 | ">=3.0.0 <4.6.1" 1102 | ], 1103 | "vulnId": "SNYK-JS-LODASHMERGE-173732" 1104 | }, 1105 | { 1106 | "functionId": { 1107 | "className": null, 1108 | "filePath": "index.js", 1109 | "functionName": "merge" 1110 | }, 1111 | "packageName": "lodash.merge", 1112 | "version": [ 1113 | ">=2.0.0 <3.0.0" 1114 | ], 1115 | "vulnId": "SNYK-JS-LODASHMERGE-173732" 1116 | }, 1117 | { 1118 | "functionId": { 1119 | "className": null, 1120 | "filePath": "index.js", 1121 | "functionName": "safeGet" 1122 | }, 1123 | "packageName": "lodash.merge", 1124 | "version": [ 1125 | ">4.6.0 <=4.6.1" 1126 | ], 1127 | "vulnId": "SNYK-JS-LODASHMERGE-173733" 1128 | }, 1129 | { 1130 | "functionId": { 1131 | "className": null, 1132 | "filePath": "index.js", 1133 | "functionName": "baseMergeDeep" 1134 | }, 1135 | "packageName": "lodash.merge", 1136 | "version": [ 1137 | ">=3.0.0 <4.6.1" 1138 | ], 1139 | "vulnId": "SNYK-JS-LODASHMERGE-173733" 1140 | }, 1141 | { 1142 | "functionId": { 1143 | "className": null, 1144 | "filePath": "index.js", 1145 | "functionName": "baseMerge" 1146 | }, 1147 | "packageName": "lodash.merge", 1148 | "version": [ 1149 | ">=3.0.0 <4.6.1" 1150 | ], 1151 | "vulnId": "SNYK-JS-LODASHMERGE-173733" 1152 | }, 1153 | { 1154 | "functionId": { 1155 | "className": null, 1156 | "filePath": "index.js", 1157 | "functionName": "merge" 1158 | }, 1159 | "packageName": "lodash.merge", 1160 | "version": [ 1161 | ">=2.0.0 <3.0.0" 1162 | ], 1163 | "vulnId": "SNYK-JS-LODASHMERGE-173733" 1164 | }, 1165 | { 1166 | "functionId": { 1167 | "className": null, 1168 | "filePath": "lodash.js", 1169 | "functionName": "merge" 1170 | }, 1171 | "packageName": "lodash", 1172 | "version": [ 1173 | ">=0.9.0 <1.0.0" 1174 | ], 1175 | "vulnId": "npm:lodash:20180130" 1176 | }, 1177 | { 1178 | "functionId": { 1179 | "className": null, 1180 | "filePath": "dist/lodash.js", 1181 | "functionName": "runInContext.merge" 1182 | }, 1183 | "packageName": "lodash", 1184 | "version": [ 1185 | ">=1.1.0 <2.0.0" 1186 | ], 1187 | "vulnId": "npm:lodash:20180130" 1188 | }, 1189 | { 1190 | "functionId": { 1191 | "className": null, 1192 | "filePath": "lodash.js", 1193 | "functionName": "runInContext.merge" 1194 | }, 1195 | "packageName": "lodash", 1196 | "version": [ 1197 | ">=2.0.0 <3.0.0" 1198 | ], 1199 | "vulnId": "npm:lodash:20180130" 1200 | }, 1201 | { 1202 | "functionId": { 1203 | "className": null, 1204 | "filePath": "lodash.js", 1205 | "functionName": "merge" 1206 | }, 1207 | "packageName": "lodash", 1208 | "version": [ 1209 | ">=0.9.0 <1.0.0" 1210 | ], 1211 | "vulnId": "SNYK-JS-LODASH-73638" 1212 | }, 1213 | { 1214 | "functionId": { 1215 | "className": null, 1216 | "filePath": "dist/lodash.js", 1217 | "functionName": "runInContext.merge" 1218 | }, 1219 | "packageName": "lodash", 1220 | "version": [ 1221 | ">=1.1.0 <2.0.0" 1222 | ], 1223 | "vulnId": "SNYK-JS-LODASH-73638" 1224 | }, 1225 | { 1226 | "functionId": { 1227 | "className": null, 1228 | "filePath": "lodash.js", 1229 | "functionName": "runInContext.merge" 1230 | }, 1231 | "packageName": "lodash", 1232 | "version": [ 1233 | ">=2.0.0 <3.0.0" 1234 | ], 1235 | "vulnId": "SNYK-JS-LODASH-73638" 1236 | }, 1237 | { 1238 | "functionId": { 1239 | "className": null, 1240 | "filePath": "index.js", 1241 | "functionName": "runInContext.baseMergeDeep" 1242 | }, 1243 | "packageName": "lodash", 1244 | "version": [ 1245 | ">=3.0.0 <4.0.0" 1246 | ], 1247 | "vulnId": "SNYK-JS-LODASH-73638" 1248 | }, 1249 | { 1250 | "functionId": { 1251 | "className": null, 1252 | "filePath": "index.js", 1253 | "functionName": "runInContext.baseMerge" 1254 | }, 1255 | "packageName": "lodash", 1256 | "version": [ 1257 | ">=3.0.0 <4.0.0" 1258 | ], 1259 | "vulnId": "SNYK-JS-LODASH-73638" 1260 | }, 1261 | { 1262 | "functionId": { 1263 | "className": null, 1264 | "filePath": "node.js", 1265 | "functionName": "exports.formatters.o" 1266 | }, 1267 | "packageName": "debug", 1268 | "version": [ 1269 | ">=1.0.0 <2.5.0" 1270 | ], 1271 | "vulnId": "npm:debug:20170905" 1272 | }, 1273 | { 1274 | "functionId": { 1275 | "className": null, 1276 | "filePath": "lodash.js", 1277 | "functionName": "runInContext.assignMergeValue" 1278 | }, 1279 | "packageName": "lodash", 1280 | "version": [ 1281 | ">=4.0.0 <4.17.5" 1282 | ], 1283 | "vulnId": "SNYK-JS-LODASH-73638" 1284 | }, 1285 | { 1286 | "functionId": { 1287 | "className": null, 1288 | "filePath": "lodash.js", 1289 | "functionName": "runInContext.mergeDefaults" 1290 | }, 1291 | "packageName": "lodash", 1292 | "version": [ 1293 | ">=4.0.0 <4.17.3" 1294 | ], 1295 | "vulnId": "SNYK-JS-LODASH-73638" 1296 | }, 1297 | { 1298 | "functionId": { 1299 | "className": null, 1300 | "filePath": "index.js", 1301 | "functionName": "runInContext.baseMergeDeep" 1302 | }, 1303 | "packageName": "lodash", 1304 | "version": [ 1305 | ">=3.0.0 <4.0.0" 1306 | ], 1307 | "vulnId": "npm:lodash:20180130" 1308 | }, 1309 | { 1310 | "functionId": { 1311 | "className": null, 1312 | "filePath": "lodash.js", 1313 | "functionName": "runInContext.baseMergeDeep" 1314 | }, 1315 | "packageName": "lodash", 1316 | "version": [ 1317 | ">=4.0.0 <4.17.5" 1318 | ], 1319 | "vulnId": "SNYK-JS-LODASH-73638" 1320 | }, 1321 | { 1322 | "functionId": { 1323 | "className": null, 1324 | "filePath": "lodash.js", 1325 | "functionName": "runInContext.baseMerge" 1326 | }, 1327 | "packageName": "lodash", 1328 | "version": [ 1329 | ">=4.0.0 <4.17.5" 1330 | ], 1331 | "vulnId": "SNYK-JS-LODASH-73638" 1332 | }, 1333 | { 1334 | "functionId": { 1335 | "className": null, 1336 | "filePath": "index.js", 1337 | "functionName": "F.$requestcontinue" 1338 | }, 1339 | "packageName": "total.js", 1340 | "version": [ 1341 | ">=2.5.0 <2.5.1", 1342 | ">=2.6.0 <2.6.3", 1343 | ">=2.7.0 <2.7.1", 1344 | ">=2.8.0 <2.8.1", 1345 | ">=2.9.0 <2.9.5", 1346 | ">=3.0.0 <3.0.1", 1347 | ">=3.1.0 <3.1.1", 1348 | ">=3.2.0 <3.2.4" 1349 | ], 1350 | "vulnId": "SNYK-JS-TOTALJS-173710" 1351 | }, 1352 | { 1353 | "functionId": { 1354 | "className": null, 1355 | "filePath": "lib/WebSocketServer.js", 1356 | "functionName": "WebSocketServer" 1357 | }, 1358 | "packageName": "ws", 1359 | "version": [ 1360 | "<1.1.1" 1361 | ], 1362 | "vulnId": "npm:ws:20160624" 1363 | }, 1364 | { 1365 | "functionId": { 1366 | "className": null, 1367 | "filePath": "lib/windows.js", 1368 | "functionName": "module.exports" 1369 | }, 1370 | "packageName": "macaddress", 1371 | "version": [ 1372 | "<0.2.9" 1373 | ], 1374 | "vulnId": "npm:macaddress:20180511" 1375 | }, 1376 | { 1377 | "functionId": { 1378 | "className": null, 1379 | "filePath": "lib/unix.js", 1380 | "functionName": "module.exports" 1381 | }, 1382 | "packageName": "macaddress", 1383 | "version": [ 1384 | "<0.2.9" 1385 | ], 1386 | "vulnId": "npm:macaddress:20180511" 1387 | }, 1388 | { 1389 | "functionId": { 1390 | "className": null, 1391 | "filePath": "lib/macosx.js", 1392 | "functionName": "module.exports" 1393 | }, 1394 | "packageName": "macaddress", 1395 | "version": [ 1396 | "<0.2.9" 1397 | ], 1398 | "vulnId": "npm:macaddress:20180511" 1399 | }, 1400 | { 1401 | "functionId": { 1402 | "className": null, 1403 | "filePath": "lib/linux.js", 1404 | "functionName": "module.exports" 1405 | }, 1406 | "packageName": "macaddress", 1407 | "version": [ 1408 | "<0.2.9" 1409 | ], 1410 | "vulnId": "npm:macaddress:20180511" 1411 | }, 1412 | { 1413 | "functionId": { 1414 | "className": null, 1415 | "filePath": "marked.js", 1416 | "functionName": "Renderer.prototype.link" 1417 | }, 1418 | "packageName": "marked", 1419 | "version": [ 1420 | "<0.3.7" 1421 | ], 1422 | "vulnId": "npm:marked:20170112" 1423 | }, 1424 | { 1425 | "functionId": { 1426 | "className": null, 1427 | "filePath": "rnj.js", 1428 | "functionName": "mathRNG" 1429 | }, 1430 | "packageName": "crypto-browserify", 1431 | "version": [ 1432 | "<2.1.11" 1433 | ], 1434 | "vulnId": "npm:crypto-browserify:20140722" 1435 | }, 1436 | { 1437 | "functionId": { 1438 | "className": null, 1439 | "filePath": "index.js", 1440 | "functionName": "isURL" 1441 | }, 1442 | "packageName": "is-url", 1443 | "version": [ 1444 | "<1.2.4" 1445 | ], 1446 | "vulnId": "npm:is-url:20180319" 1447 | }, 1448 | { 1449 | "functionId": { 1450 | "className": null, 1451 | "filePath": "lib/Server.js", 1452 | "functionName": "Server.prototype.checkHost" 1453 | }, 1454 | "packageName": "webpack-dev-server", 1455 | "version": [ 1456 | ">2.4.2 <3.1.11" 1457 | ], 1458 | "vulnId": "SNYK-JS-WEBPACKDEVSERVER-72405" 1459 | }, 1460 | { 1461 | "functionId": { 1462 | "className": null, 1463 | "filePath": "dist/amd/handlebars/compiler/javascript-compiler.js", 1464 | "functionName": "JavaScriptCompiler.prototype.nameLookup" 1465 | }, 1466 | "packageName": "handlebars", 1467 | "version": [ 1468 | ">1.0.12 <4.0.13" 1469 | ], 1470 | "vulnId": "SNYK-JS-HANDLEBARS-173692" 1471 | }, 1472 | { 1473 | "functionId": { 1474 | "className": null, 1475 | "filePath": "lib/growl.js", 1476 | "functionName": "growl" 1477 | }, 1478 | "packageName": "growl", 1479 | "version": [ 1480 | ">=1.4.0 <1.10.0" 1481 | ], 1482 | "vulnId": "npm:growl:20160721" 1483 | }, 1484 | { 1485 | "functionId": { 1486 | "className": null, 1487 | "filePath": "lib/cookie.js", 1488 | "functionName": "parse" 1489 | }, 1490 | "packageName": "tough-cookie", 1491 | "version": [ 1492 | ">=0.9.7 <2.3.0" 1493 | ], 1494 | "vulnId": "npm:tough-cookie:20160722" 1495 | }, 1496 | { 1497 | "functionId": { 1498 | "className": null, 1499 | "filePath": "dist/pad-string.js", 1500 | "functionName": "padString" 1501 | }, 1502 | "packageName": "base64url", 1503 | "version": [ 1504 | ">=2.0.0 <3.0.0" 1505 | ], 1506 | "vulnId": "npm:base64url:20180511" 1507 | }, 1508 | { 1509 | "functionId": { 1510 | "className": null, 1511 | "filePath": "lib/ejs.js", 1512 | "functionName": "cpOptsInData" 1513 | }, 1514 | "packageName": "ejs", 1515 | "version": [ 1516 | ">=2.2.1 <2.5.3" 1517 | ], 1518 | "vulnId": "npm:ejs:20161128" 1519 | }, 1520 | { 1521 | "functionId": { 1522 | "className": null, 1523 | "filePath": "index.js", 1524 | "functionName": "URL" 1525 | }, 1526 | "packageName": "url-parse", 1527 | "version": [ 1528 | ">=0.1.0 <1.4.3" 1529 | ], 1530 | "vulnId": "npm:url-parse:20180731" 1531 | }, 1532 | { 1533 | "functionId": { 1534 | "className": null, 1535 | "filePath": "index.js", 1536 | "functionName": "compact" 1537 | }, 1538 | "packageName": "qs", 1539 | "version": [ 1540 | "<1.0.0" 1541 | ], 1542 | "vulnId": "npm:qs:20140806" 1543 | }, 1544 | { 1545 | "functionId": { 1546 | "className": null, 1547 | "filePath": "lib/language.js", 1548 | "functionName": "parseLanguage" 1549 | }, 1550 | "packageName": "negotiator", 1551 | "version": [ 1552 | "<0.6.1" 1553 | ], 1554 | "vulnId": "npm:negotiator:20160616" 1555 | }, 1556 | { 1557 | "functionId": { 1558 | "className": null, 1559 | "filePath": "parse.js", 1560 | "functionName": "parsePatch" 1561 | }, 1562 | "packageName": "diff", 1563 | "version": [ 1564 | ">=3.0.0 <3.5.0" 1565 | ], 1566 | "vulnId": "npm:diff:20180305" 1567 | }, 1568 | { 1569 | "functionId": { 1570 | "className": null, 1571 | "filePath": "lib/index.js", 1572 | "functionName": "exports.type" 1573 | }, 1574 | "packageName": "content", 1575 | "version": [ 1576 | ">=4.0.0 <4.0.4" 1577 | ], 1578 | "vulnId": "npm:content:20180305" 1579 | }, 1580 | { 1581 | "functionId": { 1582 | "className": null, 1583 | "filePath": "index.js", 1584 | "functionName": "querystring" 1585 | }, 1586 | "packageName": "querystringify", 1587 | "version": [ 1588 | "<2.0.0" 1589 | ], 1590 | "vulnId": "npm:querystringify:20180419" 1591 | }, 1592 | { 1593 | "functionId": { 1594 | "className": null, 1595 | "filePath": "index.js", 1596 | "functionName": "module.exports" 1597 | }, 1598 | "packageName": "static-eval", 1599 | "version": [ 1600 | "<2.0.1" 1601 | ], 1602 | "vulnId": "SNYK-JS-STATICEVAL-173693" 1603 | }, 1604 | { 1605 | "functionId": { 1606 | "className": null, 1607 | "filePath": "index.js", 1608 | "functionName": "module.exports.memoized" 1609 | }, 1610 | "packageName": "mem", 1611 | "version": [ 1612 | "<=1.1.0" 1613 | ], 1614 | "vulnId": "npm:mem:20180117" 1615 | }, 1616 | { 1617 | "functionId": { 1618 | "className": null, 1619 | "filePath": "lib/marked.js", 1620 | "functionName": "unescape" 1621 | }, 1622 | "packageName": "marked", 1623 | "version": [ 1624 | "0.3.0<0.3.6" 1625 | ], 1626 | "vulnId": "npm:marked:20150520" 1627 | }, 1628 | { 1629 | "functionId": { 1630 | "className": null, 1631 | "filePath": "lodash.js", 1632 | "functionName": "safeGet" 1633 | }, 1634 | "packageName": "lodash", 1635 | "version": [ 1636 | ">=4.17.5 <4.17.11" 1637 | ], 1638 | "vulnId": "SNYK-JS-LODASH-73638" 1639 | }, 1640 | { 1641 | "functionId": { 1642 | "className": null, 1643 | "filePath": "lib/util/extract.js", 1644 | "functionName": "isSymlink" 1645 | }, 1646 | "packageName": "bower", 1647 | "version": [ 1648 | "<1.8.8" 1649 | ], 1650 | "vulnId": "SNYK-JS-BOWER-73627" 1651 | }, 1652 | { 1653 | "functionId": { 1654 | "className": null, 1655 | "filePath": "lib/decompress-zip.js", 1656 | "functionName": "DecompressZip.prototype.extract" 1657 | }, 1658 | "packageName": "decompress-zip", 1659 | "version": [ 1660 | "<0.3.2" 1661 | ], 1662 | "vulnId": "SNYK-JS-DECOMPRESSZIP-73598" 1663 | }, 1664 | { 1665 | "functionId": { 1666 | "className": null, 1667 | "filePath": "lib/controllers/proxy.js", 1668 | "functionName": "module.exports.buildReqHandler.handler" 1669 | }, 1670 | "packageName": "terriajs-server", 1671 | "version": [ 1672 | "<2.7.4" 1673 | ], 1674 | "vulnId": "SNYK-JS-TERRIAJSSERVER-73556" 1675 | }, 1676 | { 1677 | "functionId": { 1678 | "className": null, 1679 | "filePath": "lib/mongodb.js", 1680 | "functionName": "MongoDB.prototype.buildWhere" 1681 | }, 1682 | "packageName": "loopback-connector-mongodb", 1683 | "version": [ 1684 | "<3.6.0" 1685 | ], 1686 | "vulnId": "SNYK-JS-LOOPBACKCONNECTORMONGODB-73555" 1687 | }, 1688 | { 1689 | "functionId": { 1690 | "className": null, 1691 | "filePath": "lib/node/index.js", 1692 | "functionName": "Request.prototype.end" 1693 | }, 1694 | "packageName": "superagent", 1695 | "version": [ 1696 | "<3.4.0" 1697 | ], 1698 | "vulnId": "npm:superagent:20170807" 1699 | }, 1700 | { 1701 | "functionId": { 1702 | "className": null, 1703 | "filePath": "lib/parse.js", 1704 | "functionName": "parseObjectRecursive" 1705 | }, 1706 | "packageName": "qs", 1707 | "version": [ 1708 | ">=6.3.1 <6.3.2" 1709 | ], 1710 | "vulnId": "npm:qs:20170213" 1711 | }, 1712 | { 1713 | "functionId": { 1714 | "className": null, 1715 | "filePath": "lib/parse.js", 1716 | "functionName": "parseObject" 1717 | }, 1718 | "packageName": "qs", 1719 | "version": [ 1720 | ">=6.2.0 <6.2.3", 1721 | "6.3.0" 1722 | ], 1723 | "vulnId": "npm:qs:20170213" 1724 | }, 1725 | { 1726 | "functionId": { 1727 | "className": null, 1728 | "filePath": "src/iter.ts", 1729 | "functionName": "iter" 1730 | }, 1731 | "packageName": "rrule", 1732 | "version": [ 1733 | "<2.6.0" 1734 | ], 1735 | "vulnId": "SNYK-JS-RRULE-72455" 1736 | }, 1737 | { 1738 | "functionId": { 1739 | "className": null, 1740 | "filePath": "lib/key.js", 1741 | "functionName": "Key.parse" 1742 | }, 1743 | "packageName": "sshpk", 1744 | "version": [ 1745 | "<1.14.1" 1746 | ], 1747 | "vulnId": "npm:sshpk:20180409" 1748 | }, 1749 | { 1750 | "functionId": { 1751 | "className": null, 1752 | "filePath": "launchEditor.js", 1753 | "functionName": "launchEditor" 1754 | }, 1755 | "packageName": "react-dev-utils", 1756 | "version": [ 1757 | ">=1.0.0 <1.0.4", 1758 | ">=2.0.0 <2.0.2", 1759 | ">=3.0.0 <3.1.2", 1760 | ">=4.0.0 <4.2.2", 1761 | ">=5.0.0 <5.0.2" 1762 | ], 1763 | "vulnId": "SNYK-JS-REACTDEVUTILS-72875" 1764 | }, 1765 | { 1766 | "functionId": { 1767 | "className": null, 1768 | "filePath": "merge.js", 1769 | "functionName": "merge" 1770 | }, 1771 | "packageName": "merge", 1772 | "version": [ 1773 | ">1.0.0 <1.2.1" 1774 | ], 1775 | "vulnId": "SNYK-JS-MERGE-72553" 1776 | }, 1777 | { 1778 | "functionId": { 1779 | "className": null, 1780 | "filePath": "src/ua-parser.js", 1781 | "functionName": "UAParser.getOS" 1782 | }, 1783 | "packageName": "ua-parser-js", 1784 | "version": [ 1785 | "<0.7.16" 1786 | ], 1787 | "vulnId": "npm:ua-parser-js:20171012" 1788 | }, 1789 | { 1790 | "functionId": { 1791 | "className": null, 1792 | "filePath": "index.js", 1793 | "functionName": "module.exports" 1794 | }, 1795 | "packageName": "extend", 1796 | "version": [ 1797 | "<2.0.2", 1798 | ">=3.0.0 <3.0.2" 1799 | ], 1800 | "vulnId": "npm:extend:20180424" 1801 | }, 1802 | { 1803 | "functionId": { 1804 | "className": null, 1805 | "filePath": "lib/parsers.js", 1806 | "functionName": "module.exports" 1807 | }, 1808 | "packageName": "braces", 1809 | "version": [ 1810 | ">= 2.0.0 <2.3.1" 1811 | ], 1812 | "vulnId": "npm:braces:20180219" 1813 | }, 1814 | { 1815 | "functionId": { 1816 | "className": null, 1817 | "filePath": "src/ua-parser.js", 1818 | "functionName": "mapper.rgx" 1819 | }, 1820 | "packageName": "ua-parser-js", 1821 | "version": [ 1822 | "> 0.5.20 <0.7.18" 1823 | ], 1824 | "vulnId": "npm:ua-parser-js:20180227" 1825 | }, 1826 | { 1827 | "functionId": { 1828 | "className": null, 1829 | "filePath": "src/exec.js", 1830 | "functionName": "_exec" 1831 | }, 1832 | "packageName": "shelljs", 1833 | "version": [ 1834 | ">0.1.4 <=0.8.3" 1835 | ], 1836 | "vulnId": "npm:shelljs:20140723" 1837 | }, 1838 | { 1839 | "functionId": { 1840 | "className": null, 1841 | "filePath": "lib/multipart.js", 1842 | "functionName": "Multipart.prototype.build.add" 1843 | }, 1844 | "packageName": "request", 1845 | "version": [ 1846 | ">=2.54.0 <2.68.0" 1847 | ], 1848 | "vulnId": "npm:request:20160119" 1849 | }, 1850 | { 1851 | "functionId": { 1852 | "className": null, 1853 | "filePath": "lib/Extensions.js", 1854 | "functionName": "parse" 1855 | }, 1856 | "packageName": "ws", 1857 | "version": [ 1858 | ">=0.6.0 <1.1.5", 1859 | ">=2.0.0 <3.3.1" 1860 | ], 1861 | "vulnId": "npm:ws:20171108" 1862 | }, 1863 | { 1864 | "functionId": { 1865 | "className": null, 1866 | "filePath": "lib/optimizer/validator.js", 1867 | "functionName": "validator" 1868 | }, 1869 | "packageName": "clean-css", 1870 | "version": [ 1871 | "<4.1.11" 1872 | ], 1873 | "vulnId": "npm:clean-css:20180306" 1874 | }, 1875 | { 1876 | "functionId": { 1877 | "className": null, 1878 | "filePath": "chownr.js", 1879 | "functionName": "chownr" 1880 | }, 1881 | "packageName": "chownr", 1882 | "version": [ 1883 | "*" 1884 | ], 1885 | "vulnId": "npm:chownr:20180731" 1886 | }, 1887 | { 1888 | "functionId": { 1889 | "className": null, 1890 | "filePath": "index.js", 1891 | "functionName": "module.exports.memoized.setData" 1892 | }, 1893 | "packageName": "mem", 1894 | "version": [ 1895 | ">1.1.0<4.0.0" 1896 | ], 1897 | "vulnId": "npm:mem:20180117" 1898 | }, 1899 | { 1900 | "functionId": { 1901 | "className": null, 1902 | "filePath": "mime.js", 1903 | "functionName": "Mime.prototype.lookup" 1904 | }, 1905 | "packageName": "mime", 1906 | "version": [ 1907 | ">=1.2.6 <1.4.1" 1908 | ], 1909 | "vulnId": "npm:mime:20170907" 1910 | }, 1911 | { 1912 | "functionId": { 1913 | "className": null, 1914 | "filePath": "lib/js.js", 1915 | "functionName": "module.exports" 1916 | }, 1917 | "packageName": "inline-source", 1918 | "version": [ 1919 | "<6.1.7" 1920 | ], 1921 | "vulnId": "SNYK-JS-INLINESOURCE-72623" 1922 | }, 1923 | { 1924 | "functionId": { 1925 | "className": null, 1926 | "filePath": "lodash.js", 1927 | "functionName": "runInContext.baseMergeDeep" 1928 | }, 1929 | "packageName": "lodash", 1930 | "version": [ 1931 | ">=4.0.0 <4.17.5" 1932 | ], 1933 | "vulnId": "npm:lodash:20180130" 1934 | }, 1935 | { 1936 | "functionId": { 1937 | "className": null, 1938 | "filePath": "lodash.js", 1939 | "functionName": "runInContext.baseMerge" 1940 | }, 1941 | "packageName": "lodash", 1942 | "version": [ 1943 | ">=4.0.0 <4.17.5" 1944 | ], 1945 | "vulnId": "npm:lodash:20180130" 1946 | }, 1947 | { 1948 | "functionId": { 1949 | "className": null, 1950 | "filePath": "lib/index.js", 1951 | "functionName": "cachedPathRelative" 1952 | }, 1953 | "packageName": "cached-path-relative", 1954 | "version": [ 1955 | "<1.0.2" 1956 | ], 1957 | "vulnId": "SNYK-JS-CACHEDPATHRELATIVE-72573" 1958 | }, 1959 | { 1960 | "functionId": { 1961 | "className": null, 1962 | "filePath": "index.js", 1963 | "functionName": "module.exports.publish" 1964 | }, 1965 | "packageName": "apex-publish-static-files", 1966 | "version": [ 1967 | "<2.0.1" 1968 | ], 1969 | "vulnId": "SNYK-JS-APEXPUBLISHSTATICFILES-72552" 1970 | }, 1971 | { 1972 | "functionId": { 1973 | "className": "tools", 1974 | "filePath": "lib/classes/tools.js", 1975 | "functionName": "tools.prototype.funcs" 1976 | }, 1977 | "packageName": "libnmap", 1978 | "version": [ 1979 | "<0.4.16" 1980 | ], 1981 | "vulnId": "SNYK-JS-LIBNMAP-72551" 1982 | }, 1983 | { 1984 | "functionId": { 1985 | "className": null, 1986 | "filePath": "moment.js", 1987 | "functionName": "weekdaysMinRegex" 1988 | }, 1989 | "packageName": "moment", 1990 | "version": [ 1991 | ">=2.13.0 <2.19.3" 1992 | ], 1993 | "vulnId": "npm:moment:20170905" 1994 | }, 1995 | { 1996 | "functionId": { 1997 | "className": null, 1998 | "filePath": "moment.js", 1999 | "functionName": "weekdaysShortRegex" 2000 | }, 2001 | "packageName": "moment", 2002 | "version": [ 2003 | ">=2.13.0 <2.19.3" 2004 | ], 2005 | "vulnId": "npm:moment:20170905" 2006 | }, 2007 | { 2008 | "functionId": { 2009 | "className": null, 2010 | "filePath": "moment.js", 2011 | "functionName": "weekdaysRegex" 2012 | }, 2013 | "packageName": "moment", 2014 | "version": [ 2015 | ">=2.13.0 <2.19.3" 2016 | ], 2017 | "vulnId": "npm:moment:20170905" 2018 | }, 2019 | { 2020 | "functionId": { 2021 | "className": null, 2022 | "filePath": "moment.js", 2023 | "functionName": "monthsRegex" 2024 | }, 2025 | "packageName": "moment", 2026 | "version": [ 2027 | "<2.19.3" 2028 | ], 2029 | "vulnId": "npm:moment:20170905" 2030 | }, 2031 | { 2032 | "functionId": { 2033 | "className": null, 2034 | "filePath": "moment.js", 2035 | "functionName": "monthsShortRegex" 2036 | }, 2037 | "packageName": "moment", 2038 | "version": [ 2039 | "<2.19.3" 2040 | ], 2041 | "vulnId": "npm:moment:20170905" 2042 | }, 2043 | { 2044 | "functionId": { 2045 | "className": null, 2046 | "filePath": "lib/node/index.js", 2047 | "functionName": "Request.prototype._end" 2048 | }, 2049 | "packageName": "superagent", 2050 | "version": [ 2051 | ">=3.4.0 <3.7.0" 2052 | ], 2053 | "vulnId": "npm:superagent:20170807" 2054 | }, 2055 | { 2056 | "functionId": { 2057 | "className": null, 2058 | "filePath": "src/node.js", 2059 | "functionName": "exports.formatters.o" 2060 | }, 2061 | "packageName": "debug", 2062 | "version": [ 2063 | ">= 2.5.0 <2.6.9", 2064 | ">=3.0.0 <3.1.0" 2065 | ], 2066 | "vulnId": "npm:debug:20170905" 2067 | }, 2068 | { 2069 | "functionId": { 2070 | "className": null, 2071 | "filePath": "lib/cookie.js", 2072 | "functionName": "parse" 2073 | }, 2074 | "packageName": "tough-cookie", 2075 | "version": [ 2076 | "<2.3.3" 2077 | ], 2078 | "vulnId": "npm:tough-cookie:20170905" 2079 | }, 2080 | { 2081 | "functionId": { 2082 | "className": null, 2083 | "filePath": "index.js", 2084 | "functionName": "parse" 2085 | }, 2086 | "packageName": "ms", 2087 | "version": [ 2088 | "<2.0.0" 2089 | ], 2090 | "vulnId": "npm:ms:20170412" 2091 | }, 2092 | { 2093 | "functionId": { 2094 | "className": null, 2095 | "filePath": "lib/parse.js", 2096 | "functionName": "internals.parseObject" 2097 | }, 2098 | "packageName": "qs", 2099 | "version": [ 2100 | "<6.0.4" 2101 | ], 2102 | "vulnId": "npm:qs:20170213" 2103 | }, 2104 | { 2105 | "functionId": { 2106 | "className": null, 2107 | "filePath": "lib/handlebars/utils.js", 2108 | "functionName": "escapeExpression" 2109 | }, 2110 | "packageName": "handlebars", 2111 | "version": [ 2112 | "<4.0.0" 2113 | ], 2114 | "vulnId": "npm:handlebars:20151207" 2115 | }, 2116 | { 2117 | "functionId": { 2118 | "className": null, 2119 | "filePath": "lib/parse.js", 2120 | "functionName": "parse_js_number" 2121 | }, 2122 | "packageName": "uglify-js", 2123 | "version": [ 2124 | ">1.3.5 <2.6.0" 2125 | ], 2126 | "vulnId": "npm:uglify-js:20151024" 2127 | }, 2128 | { 2129 | "functionId": { 2130 | "className": null, 2131 | "filePath": "st.js", 2132 | "functionName": "Mount.prototype.getPath" 2133 | }, 2134 | "packageName": "st", 2135 | "version": [ 2136 | ">0.0.1 <0.2.5" 2137 | ], 2138 | "vulnId": "npm:st:20140206" 2139 | }, 2140 | { 2141 | "functionId": { 2142 | "className": null, 2143 | "filePath": "stringstream.js", 2144 | "functionName": "StringStream.prototype.write" 2145 | }, 2146 | "packageName": "stringstream", 2147 | "version": [ 2148 | ">0.0.0 <0.0.6" 2149 | ], 2150 | "vulnId": "npm:stringstream:20180511" 2151 | }, 2152 | { 2153 | "functionId": { 2154 | "className": null, 2155 | "filePath": "lib/index.js", 2156 | "functionName": "exports.randomDigits" 2157 | }, 2158 | "packageName": "cryptiles", 2159 | "version": [ 2160 | ">=3.1.0 <3.1.3", 2161 | ">=4.0.0 <4.1.2" 2162 | ], 2163 | "vulnId": "npm:cryptiles:20180710" 2164 | }, 2165 | { 2166 | "functionId": { 2167 | "className": null, 2168 | "filePath": "index.js", 2169 | "functionName": "fresh" 2170 | }, 2171 | "packageName": "fresh", 2172 | "version": [ 2173 | "<0.5.2" 2174 | ], 2175 | "vulnId": "npm:fresh:20170908" 2176 | }, 2177 | { 2178 | "functionId": { 2179 | "className": null, 2180 | "filePath": "lib/passport-saml/saml.js", 2181 | "functionName": "SAML.prototype.requestToUrl.requestToUrlHelper" 2182 | }, 2183 | "packageName": "passport-saml", 2184 | "version": [ 2185 | "*" 2186 | ], 2187 | "vulnId": "SNYK-JS-PASSPORTSAML-72411" 2188 | }, 2189 | { 2190 | "functionId": { 2191 | "className": null, 2192 | "filePath": "Mime.js", 2193 | "functionName": "Mime.prototype.getType" 2194 | }, 2195 | "packageName": "mime", 2196 | "version": [ 2197 | ">=2.0.0 <2.0.3" 2198 | ], 2199 | "vulnId": "npm:mime:20170907" 2200 | }, 2201 | { 2202 | "functionId": { 2203 | "className": null, 2204 | "filePath": "index.js", 2205 | "functionName": "TunnelingAgent.prototype.createSocket" 2206 | }, 2207 | "packageName": "tunnel-agent", 2208 | "version": [ 2209 | "<0.6.0" 2210 | ], 2211 | "vulnId": "npm:tunnel-agent:20170305" 2212 | }, 2213 | { 2214 | "functionId": { 2215 | "className": null, 2216 | "filePath": "lib/deep-extend.js", 2217 | "functionName": "cloneSpecificValue" 2218 | }, 2219 | "packageName": "deep-extend", 2220 | "version": [ 2221 | ">=0.4.0 <0.5.1" 2222 | ], 2223 | "vulnId": "npm:deep-extend:20180409" 2224 | }, 2225 | { 2226 | "functionId": { 2227 | "className": null, 2228 | "filePath": "minimatch.js", 2229 | "functionName": "braceExpand" 2230 | }, 2231 | "packageName": "minimatch", 2232 | "version": [ 2233 | ">0.0.5 <3.0.2" 2234 | ], 2235 | "vulnId": "npm:minimatch:20160620" 2236 | } 2237 | ] -------------------------------------------------------------------------------- /lib/snapshot/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const debug = require('debug')('snyk:nodejs-runtime-agent:snapshot'); 3 | const semver = require('semver'); 4 | 5 | const ast = require('../ast'); 6 | const reader = require('./reader'); 7 | const state = require('../state'); 8 | 9 | function init() { 10 | reader.loadFromLocal(); 11 | } 12 | 13 | function refresh() { 14 | return reader.loadFromUpstream() 15 | .then(() => {}) 16 | .catch(() => {}); 17 | } 18 | 19 | function getVulnerableFunctionsLocations(moduleInfo) { 20 | const vulnerabilitiesMetadata = reader.getLatest(); 21 | const {name: packageName, version, scriptRelativePath, scriptPath} = moduleInfo; 22 | if (!((packageName in vulnerabilitiesMetadata) && (scriptRelativePath in vulnerabilitiesMetadata[packageName]))) { 23 | return {}; 24 | } 25 | 26 | const vulnerableFunctionNames = []; 27 | const scriptPathFunctions = vulnerabilitiesMetadata[packageName][scriptRelativePath]; 28 | scriptPathFunctions.forEach(value => { 29 | value.semver.some(ver => { 30 | if (semver.satisfies(version, ver)) { 31 | vulnerableFunctionNames.push(value.name); 32 | return true; 33 | } 34 | 35 | return false; 36 | }); 37 | }); 38 | 39 | try { 40 | const vulnerableFunctionsFound = ast.findAllVulnerableFunctionsInScript( 41 | fs.readFileSync(scriptPath), vulnerableFunctionNames); 42 | const foundFunctionNames = Object.keys(vulnerableFunctionsFound); 43 | validateFoundFunctions(scriptPathFunctions, moduleInfo, vulnerableFunctionNames, foundFunctionNames); 44 | return vulnerableFunctionsFound; 45 | } catch (error) { 46 | debug(`Error finding vulnerable methods ${vulnerableFunctionNames}` + 47 | ` in script path ${scriptPath}: ${error}`); 48 | return {}; 49 | } 50 | } 51 | 52 | function validateFoundFunctions(scriptPathFunctions, moduleInfo, functionsExpected, functionsActual) { 53 | const functionsNotFound = functionsExpected.filter(f => !functionsActual.includes(f)); 54 | if (functionsNotFound && functionsNotFound.length > 0) { 55 | const warningEvent = { 56 | message: 'instrumentation discrepancy: missing functions from source code', 57 | moduleInfo, 58 | snapshotInfo: scriptPathFunctions, 59 | functionsInfo: { 60 | functionsExpected, 61 | functionsActual, 62 | functionsNotFound, 63 | }, 64 | }; 65 | state.addEvent({warning: warningEvent}); 66 | } 67 | } 68 | 69 | module.exports = { 70 | init, 71 | refresh, 72 | getVulnerableFunctionsLocations, 73 | }; 74 | -------------------------------------------------------------------------------- /lib/snapshot/reader.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const needle = require('needle'); 3 | const debug = require('debug')('snyk:nodejs-runtime-agent:snapshot'); 4 | 5 | const config = require('../config'); 6 | const moduleUtils = require('../module-utils'); 7 | 8 | let lastModified; 9 | let vulnerabiltiesMetadata = {}; 10 | 11 | module.exports = { 12 | getLatest, 13 | loadFromLocal, 14 | loadFromUpstream, 15 | processFunctionsToInspect, 16 | setVulnerabiltiesMetadata, 17 | }; 18 | 19 | function getLatest() { 20 | return vulnerabiltiesMetadata; 21 | } 22 | 23 | function loadFromLocal() { 24 | let rawFunctions; 25 | 26 | try { 27 | if (fs.existsSync(config.functionPaths.bundle.snapshot) && fs.existsSync(config.functionPaths.bundle.date)) { 28 | debug('attempting to load the functions bundled with the package'); 29 | rawFunctions = require(config.functionPaths.bundle.snapshot); 30 | vulnerabiltiesMetadata = processFunctionsToInspect(rawFunctions); 31 | lastModified = new Date(fs.readFileSync(config.functionPaths.bundle.date, 'utf8')); 32 | debug(`loaded the functions bundled with the package at ${lastModified.toUTCString()}`); 33 | return; 34 | } 35 | } catch (error) { 36 | debug('error loading the bundled functions, falling back to the snapshot provided in the repo'); 37 | debug(error); 38 | } 39 | 40 | rawFunctions = require(config.functionPaths.repo.snapshot); 41 | vulnerabiltiesMetadata = processFunctionsToInspect(rawFunctions); 42 | lastModified = new Date(fs.readFileSync(config.functionPaths.repo.date, 'utf8')); 43 | debug(`loaded the functions provided in the repository, created at ${lastModified.toUTCString()}`); 44 | } 45 | 46 | async function loadFromUpstream() { 47 | const url = config.snapshotUrl; 48 | try { 49 | debug(`attempting to retrieve latest snapshot from ${url}`); 50 | const requestOptions = { 51 | json: true, 52 | rejectUnauthorized: !config['allowUnknownCA'], 53 | headers: {'If-Modified-Since': lastModified.toUTCString()}, 54 | }; 55 | const response = await needle('get', url, requestOptions); 56 | if (response.statusCode === 304) { 57 | debug('snapshot not modified'); 58 | return; 59 | } 60 | if (response.statusCode !== 200) { 61 | debug(`failed retrieving latest snapshot from ${url}: ${response.statusCode}`); 62 | throw new Error('failed retrieving latest snapshot'); 63 | } 64 | 65 | lastModified = new Date(response.headers['last-modified']); 66 | const rawSnapshot = response.body; 67 | vulnerabiltiesMetadata = processFunctionsToInspect(rawSnapshot); 68 | } catch (error) { 69 | debug(`failed retrieving latest snapshot from ${url}: ${error}`); 70 | throw new Error('failed retrieving latest snapshot'); 71 | } 72 | } 73 | 74 | function processFunctionsToInspect(functions) { 75 | const processedFunctions = {}; 76 | functions.forEach(currentFunction => { 77 | try { 78 | if (!(currentFunction.packageName in processedFunctions)) { 79 | processedFunctions[currentFunction.packageName] = {}; 80 | } 81 | const normalisedFilePath = moduleUtils.normaliseSeparator(currentFunction.functionId.filePath); 82 | if (!(normalisedFilePath in processedFunctions[currentFunction.packageName])) { 83 | processedFunctions[currentFunction.packageName][normalisedFilePath] = []; 84 | } 85 | processedFunctions[currentFunction.packageName][normalisedFilePath].push({ 86 | name: currentFunction.functionId.functionName, 87 | semver: currentFunction.version, 88 | }); 89 | } catch (err) { 90 | debug(`Failed inspecting ${currentFunction}:`, err); 91 | } 92 | }); 93 | return processedFunctions; 94 | } 95 | 96 | //TODO: fix it hack for tests 97 | function setVulnerabiltiesMetadata(vulnMetadata) { 98 | vulnerabiltiesMetadata = processFunctionsToInspect(vulnMetadata); 99 | } 100 | -------------------------------------------------------------------------------- /lib/state.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('snyk:nodejs:state'); 2 | 3 | const moduleUtils = require('./module-utils'); 4 | 5 | const state = { 6 | events: [], 7 | filters: {}, 8 | packages: {}, 9 | }; 10 | 11 | module.exports = { 12 | get, 13 | clean, 14 | addEvent, 15 | addFilter, 16 | addPackage, 17 | removeFilter, 18 | }; 19 | 20 | function get() { 21 | return state; 22 | } 23 | 24 | function clean() { 25 | state.events.length = 0; 26 | } 27 | 28 | function addEvent(event) { 29 | event.timestamp = (new Date()).toISOString(); 30 | state.events.push(event); 31 | debug(`Event added to transmission queue: ${JSON.stringify(event)}`); 32 | } 33 | 34 | function addFilter(packageName, fileRelativePath, functionName) { 35 | const filters = state.filters; 36 | const denormalisedFileRelativePath = moduleUtils.denormaliseSeparator(fileRelativePath); 37 | 38 | if (!(packageName in filters)) { 39 | filters[packageName] = {}; 40 | } 41 | if (!(denormalisedFileRelativePath in filters[packageName])) { 42 | filters[packageName][denormalisedFileRelativePath] = {}; 43 | } 44 | filters[packageName][denormalisedFileRelativePath][functionName] = null; 45 | } 46 | 47 | function removeFilter(packageName, fileRelativePath, functionName) { 48 | const filters = state.filters; 49 | const denormalisedFileRelativePath = moduleUtils.denormaliseSeparator(fileRelativePath); 50 | 51 | delete filters[packageName][denormalisedFileRelativePath][functionName]; 52 | if (Object.keys(filters[packageName][denormalisedFileRelativePath]).length === 0) { 53 | delete filters[packageName][denormalisedFileRelativePath]; 54 | } 55 | if (Object.keys(filters[packageName]).length === 0) { 56 | delete filters[packageName]; 57 | } 58 | } 59 | 60 | function addPackage(name, version) { 61 | if (!(name in state.packages)) { 62 | state.packages[name] = {}; 63 | } 64 | 65 | // an empty object so it's easier to overload additional data here 66 | // such as "introducedThrough" etc. 67 | state.packages[name][version] = {}; 68 | } 69 | -------------------------------------------------------------------------------- /lib/system-info.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | 3 | const packageJson = require('../package.json'); 4 | 5 | function getSystemInfo() { 6 | let systemInfo = {agentVersion: packageJson.version || ''}; 7 | 8 | try { 9 | systemInfo.hostName = os.hostname(); 10 | systemInfo.node = {versions: process.versions}, 11 | systemInfo.os = { 12 | type: os.type(), 13 | release: os.release(), 14 | platform: os.platform(), 15 | }; 16 | } catch (error) { 17 | systemInfo.error = `failed getting system info: ${error}`; 18 | } 19 | 20 | return systemInfo; 21 | } 22 | 23 | module.exports = {getSystemInfo}; 24 | -------------------------------------------------------------------------------- /lib/transmitter.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('snyk:nodejs:transmitter'); 2 | const needle = require('needle'); 3 | 4 | const state = require('./state'); 5 | const config = require('./config'); 6 | const systemInfo = require('./system-info').getSystemInfo(); 7 | 8 | function handlePeriodicTasks() { 9 | const url = config.beaconUrl; 10 | return transmitEvents(url, config.projectId, config.agentId); 11 | } 12 | 13 | function transmitEvents(url, projectId, agentId) { 14 | const currentState = state.get(); 15 | let postPromise = Promise.resolve(); 16 | 17 | debug(`agent:${agentId} transmitting ${currentState.events.length} events to ${url} with project ID ${projectId}.`); 18 | const body = { 19 | agentId, 20 | projectId, 21 | systemInfo, 22 | filters: currentState.filters, 23 | eventsToSend: currentState.events, 24 | loadedSources: currentState.packages, 25 | }; 26 | 27 | const options = { 28 | json: true, 29 | rejectUnauthorized: !config['allowUnknownCA'], 30 | }; 31 | 32 | postPromise = needle('post', url, body, options) 33 | .then((response) => { 34 | if (response && response.statusCode !== 200) { 35 | debug('Unexpected response for events transmission: ' + 36 | `${response.statusCode} : ${JSON.stringify(response.body)}`); 37 | } else if (response && response.statusCode === 200) { 38 | debug('Successfully transmitted events.'); 39 | } 40 | }) 41 | .catch((error) => { 42 | debug(`Error transmitting events: ${error}`); 43 | }); 44 | state.clean(); 45 | return postPromise; 46 | } 47 | 48 | module.exports = { 49 | transmitEvents, 50 | handlePeriodicTasks, 51 | }; 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@snyk/nodejs-runtime-agent", 3 | "description": "Snyk Node.js runtime agent", 4 | "homepage": "https://github.com/snyk/nodejs-runtime-agent", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/snyk/nodejs-runtime-agent" 8 | }, 9 | "main": "lib/index.js", 10 | "directories": { 11 | "test": "test" 12 | }, 13 | "engines": { 14 | "node": "^8 || ^10" 15 | }, 16 | "scripts": { 17 | "dump": "node test/dump.js", 18 | "start": "node demo/index.js", 19 | "test": "npm run lint && tap --jobs 1 ./test/*.test.js -R spec", 20 | "lint": "eslint -c .eslintrc lib" 21 | }, 22 | "keywords": [ 23 | "agent", 24 | "instrumentation", 25 | "monitor", 26 | "monitoring", 27 | "policy", 28 | "runtime", 29 | "secure", 30 | "security", 31 | "snyk", 32 | "validate" 33 | ], 34 | "author": "snyk.io", 35 | "license": "Proprietary; see LICENSE", 36 | "devDependencies": { 37 | "eslint": "^6.2.2", 38 | "nock": "^10.0.6", 39 | "proxyquire": "^2.1.3", 40 | "sinon": "^7.4.1", 41 | "sleep-promise": "^8.0.1", 42 | "st": "^0.1.0", 43 | "tap": "^14.6.1" 44 | }, 45 | "dependencies": { 46 | "acorn": "5.7.4", 47 | "debug": "^4.3.3", 48 | "needle": "^2.6.0", 49 | "semver": "^6.3.0", 50 | "uuid": "^3.3.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/config.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const nock = require('nock'); 3 | const sinon = require('sinon'); 4 | const needle = require('needle'); 5 | 6 | const config = require('../lib/config'); 7 | 8 | test('Beacons and snapshots are sent to configured base url', async function(t) { 9 | config.initConfig({projectId: 'whatever', baseUrl: 'http://localhost:8000'}); 10 | t.equal(config.beaconUrl, 'http://localhost:8000/api/v1/beacon', 'beacon url with prefix is correct') 11 | t.equal(config.snapshotUrl, 'http://localhost:8000/api/v2/snapshot/whatever/node', 'snapshot url with prefix is correct') 12 | }); 13 | -------------------------------------------------------------------------------- /test/debugger.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const sinon = require('sinon'); 3 | const inspector = require('inspector'); 4 | const EventEmitter = require('events'); 5 | 6 | const dbg = require('../lib/debugger-wrapper'); 7 | const state = require('../lib/state'); 8 | const moduleUtils = require('../lib/module-utils'); 9 | const snapshotReader = require('../lib/snapshot/reader'); 10 | 11 | class MockSession extends EventEmitter { 12 | constructor() { 13 | super(); 14 | }; 15 | 16 | connect() {}; 17 | 18 | post(method, params, cb) { 19 | if ('Debugger.setBreakpointByUrl' !== method) { 20 | return; 21 | } 22 | 23 | switch (params.lineNumber) { 24 | case 157: 25 | cb(undefined, {breakpointId: 'getPath_BP_ID'}); 26 | return; 27 | case 186: 28 | cb(undefined, {breakpointId: 'serve_BP_ID'}); 29 | return; 30 | case 178: 31 | cb(undefined, {breakpointId: 'getUrl_BP_ID'}); 32 | return; 33 | default: 34 | cb({error: `mocking has no mock for line number ${params.lineNumber}`}, undefined); 35 | } 36 | } 37 | } 38 | 39 | test('test setting a breakpoint', function (t) { 40 | const mock = new MockSession(); 41 | sinon.stub(inspector, 'Session').returns(mock); 42 | sinon.stub(moduleUtils, 'getModuleInfo').returns({ 43 | 'version': '0.2.1', 44 | 'name': 'st', 45 | 'scriptRelativePath': 'st.js', 46 | 'scriptPath': `${__dirname}/fixtures/st/node_modules/st.js` 47 | }); 48 | dbg.init(); 49 | snapshotReader.setVulnerabiltiesMetadata(require('./fixtures/st/vulnerable_methods.json')); 50 | const stScriptInfo = require('./fixtures/st/script.json'); 51 | const stateSpy = sinon.spy(state, 'addEvent'); 52 | stScriptInfo.params.url = __dirname + '/' + stScriptInfo.params.url; 53 | mock.emit('Debugger.scriptParsed', stScriptInfo); 54 | 55 | t.assert(stScriptInfo.params.url in dbg.scriptUrlToInstrumentedFunctions); 56 | const monitoredFunctionsBefore = dbg.scriptUrlToInstrumentedFunctions[stScriptInfo.params.url]; 57 | t.equal(Object.keys(monitoredFunctionsBefore).length, 2, 'two monitored functions before'); 58 | t.assert('Mount.prototype.getPath' in monitoredFunctionsBefore, 'getPath newly monitored'); 59 | t.equal(monitoredFunctionsBefore['Mount.prototype.getPath'], 'getPath_BP_ID'); 60 | t.assert('Mount.prototype.getUrl' in monitoredFunctionsBefore, 'getUrl newly monitored'); 61 | t.equal(monitoredFunctionsBefore['Mount.prototype.getUrl'], 'getUrl_BP_ID'); 62 | t.assert('error' in stateSpy.args[0][0], 'Error event was added to state'); 63 | t.equal(1, stateSpy.callCount, 'Add event was called once because of set bp error'); 64 | 65 | snapshotReader.setVulnerabiltiesMetadata(require('./fixtures/st/vulnerable_methods_new.json')); 66 | dbg.refreshInstrumentation(); 67 | 68 | t.assert(stScriptInfo.params.url in dbg.scriptUrlToInstrumentedFunctions); 69 | const monitoredFunctionsAfter = dbg.scriptUrlToInstrumentedFunctions[stScriptInfo.params.url]; 70 | t.equal(Object.keys(monitoredFunctionsAfter).length, 2, 'two monitored functions after'); 71 | t.assert('Mount.prototype.getPath' in monitoredFunctionsAfter, 'getPath still monitored'); 72 | t.equal(monitoredFunctionsAfter['Mount.prototype.getPath'], 'getPath_BP_ID'); 73 | t.assert('Mount.prototype.serve' in monitoredFunctionsAfter, 'serve newly monitored'); 74 | t.equal(monitoredFunctionsAfter['Mount.prototype.serve'], 'serve_BP_ID'); 75 | t.assert(!('Mount.prototype.getUrl' in monitoredFunctionsBefore), 'getUrl removed'); 76 | 77 | stateSpy.restore(); 78 | t.end(); 79 | }); 80 | 81 | test('skip unnecessary debugger pauses', function (t) { 82 | const pauseContextDueToOOM = {reason: 'OOM'}; 83 | t.assert(dbg.ignorePause(pauseContextDueToOOM)); 84 | 85 | const pauseContextWithoutBreakpointsObject = {reason: 'other'}; 86 | t.assert(dbg.ignorePause(pauseContextWithoutBreakpointsObject)); 87 | 88 | const pauseContextWithoutBreakpoints = {reason: 'other', hitBreakpoints: []}; 89 | t.assert(dbg.ignorePause(pauseContextWithoutBreakpoints)); 90 | 91 | const pauseContextWithBreakpoints = {reason: 'other', hitBreakpoints: ['breakpoint-id']}; 92 | t.assert(!dbg.ignorePause(pauseContextWithBreakpoints)); 93 | 94 | t.end(); 95 | }); 96 | 97 | test('handle fuctions not instrumented', function (t) { 98 | const stateSpy = sinon.spy(state, 'addEvent'); 99 | snapshotReader.setVulnerabiltiesMetadata(require('./fixtures/st/vulnerable_methods_invalid.json')); 100 | dbg.refreshInstrumentation(); 101 | t.assert('warning' in stateSpy.args[0][0], 'warning event was added to state'); 102 | t.equal(1, stateSpy.callCount, 'Add event was called once because of missing function from source'); 103 | stateSpy.restore(); 104 | t.end(); 105 | }); 106 | -------------------------------------------------------------------------------- /test/disable.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const needle = require('needle'); 3 | const nock = require('nock'); 4 | const sleep = require('sleep-promise'); 5 | 6 | test('agent can be disabled', async (t) => { 7 | // intercept potential beacons 8 | nock('http://localhost:7000') 9 | .post('/api/v1/beacon') 10 | .reply(200); 11 | 12 | const BEACON_INTERVAL_MS = 1000; // 1 sec agent beacon interval 13 | // configure agent in demo server via env vars 14 | process.env.SNYK_HOMEBASE_URL = 'http://localhost:7000/api/v1/beacon'; 15 | process.env.SNYK_BEACON_INTERVAL_MS = BEACON_INTERVAL_MS; 16 | process.env.SNYK_RUNTIME_AGENT_DISABLE = 'yes please'; 17 | // 0: let the OS pick a free port 18 | process.env.PORT = 0; 19 | 20 | // bring up the demo server 21 | const demoApp = require('../demo'); 22 | const port = demoApp.address().port; 23 | 24 | // wait to let the agent go through a cycle 25 | await sleep(BEACON_INTERVAL_MS); 26 | 27 | // trigger the vuln method 28 | await needle.get(`http://localhost:${port}/hello.txt`); 29 | 30 | // wait to let the agent go through a cycle 31 | await sleep(BEACON_INTERVAL_MS); 32 | 33 | // make sure no beacon calls were made 34 | t.ok(!nock.isDone(), 'no beacon calls were made'); 35 | 36 | delete process.env.SNYK_HOMEBASE_URL; 37 | delete process.env.SNYK_BEACON_INTERVAL_MS; 38 | delete process.env.SNYK_RUNTIME_AGENT_DISABLE; 39 | delete process.env.PORT; 40 | 41 | await new Promise((resolve) => demoApp.close(resolve)); 42 | }); 43 | -------------------------------------------------------------------------------- /test/dump.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const ast = require('../lib/ast.js'); 4 | 5 | for (let i = 2; i < process.argv.length; ++i) { 6 | dump(process.argv[i]); 7 | } 8 | 9 | function dump(path) { 10 | const funcs = ast.findAllVulnerableFunctionsInScript(fs.readFileSync(path), {includes: () => true}); 11 | for (const [name, loc] of Object.entries(funcs)) { 12 | console.log(name, showLoc(loc)); 13 | } 14 | } 15 | 16 | function showLoc(loc) { 17 | return `${loc.start.line}c${loc.start.column}` + 18 | ` -> ${loc.end.line}c${loc.end.column}`; 19 | } 20 | -------------------------------------------------------------------------------- /test/e2e.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const needle = require('needle'); 3 | const nock = require('nock'); 4 | const sleep = require('sleep-promise'); 5 | const path = require('path'); 6 | 7 | test('demo app reports a vuln method when called', async (t) => { 8 | const newSnapshotModificationDate = new Date(); 9 | 10 | // first call will have one event triggered when the demo starts 11 | nock('http://localhost:8000') 12 | .post('/api/v1/beacon') 13 | .reply(200, (uri, requestBody) => { 14 | // assert the expected beacon data 15 | const beaconData = JSON.parse(requestBody); 16 | t.ok(beaconData.projectId, 'projectId present in beacon data'); 17 | t.ok(beaconData.agentId, 'agentId present in beacon data'); 18 | t.ok(beaconData.systemInfo, 'systemInfo present in beacon data'); 19 | t.ok(!('error' in beaconData.systemInfo), 'systemInfo has no errors'); 20 | t.ok(beaconData.eventsToSend, 'eventsToSend present in beacon data'); 21 | t.equal(beaconData.eventsToSend.length, 1, 'one event sent'); 22 | t.equal(beaconData.eventsToSend[0].methodEntry.methodName, 'mime.Mime.prototype.lookup', 'only vulnerability on startup is mime.lookup which st imports'); 23 | const expectedFilters = { 24 | 'st': {'st.js': {'Mount.prototype.getPath': null}}, 25 | 'mime': {'mime.js': {'Mime.prototype.lookup': null}}, 26 | 'negotiator': {'lib/language.js': {'parseLanguage': null}}, 27 | }; 28 | t.deepEqual(beaconData.filters, expectedFilters, 'instrumentation appears in beacon'); 29 | t.ok(beaconData.loadedSources, 'loadedSources present in beacon data'); 30 | t.ok('st' in beaconData.loadedSources, 'st was loaded'); 31 | t.deepEqual(beaconData.loadedSources['st'], {'0.1.4': {}}, 'expected st version'); 32 | t.ok('mime' in beaconData.loadedSources, 'mime was loaded'); 33 | t.deepEqual(beaconData.loadedSources['mime'], {'1.2.11': {}}, 'expected mime version'); 34 | }); 35 | 36 | // second call will have an additional event because we trigger the vuln method 37 | nock('http://localhost:8000') 38 | .post('/api/v1/beacon') 39 | .reply(200, (uri, requestBody) => { 40 | 41 | // assert the expected beacon data 42 | const beaconData = JSON.parse(requestBody); 43 | t.ok(beaconData.projectId, 'projectId present in beacon data'); 44 | t.ok(beaconData.agentId, 'agentId present in beacon data'); 45 | t.ok(beaconData.systemInfo, 'systemInfo present in beacon data'); 46 | t.ok(!('error' in beaconData.systemInfo), 'systemInfo has no errors'); 47 | t.ok(beaconData.eventsToSend, 'eventsToSend present in beacon data'); 48 | 49 | t.equal(beaconData.eventsToSend.length, 2, '2 events sent'); 50 | const beaconEvent = beaconData.eventsToSend[0].methodEntry; 51 | t.ok(beaconEvent, 'method event sent'); 52 | t.equal(beaconEvent.methodName, 'st.Mount.prototype.getPath', 'proper vulnerable method name'); 53 | t.same(beaconEvent.coordinates, ['node:st:0.1.4'], 'proper vulnerable module coordinate'); 54 | t.ok(beaconEvent.sourceUri.endsWith('/st.js'), 'proper vulnerable module script'); 55 | t.ok(beaconEvent.sourceUri.includes(`node_modules${path.sep}st`), 'proper vulnerable module base dir'); 56 | const secondBeaconEvent = beaconData.eventsToSend[1].methodEntry; 57 | t.ok(secondBeaconEvent, 'method event sent'); 58 | t.equal(secondBeaconEvent.methodName, 'mime.Mime.prototype.lookup', 'proper vulnerable method name'); 59 | t.same(secondBeaconEvent.coordinates, ['node:mime:1.2.11'], 'proper vulnerable module coordinate'); 60 | t.ok(secondBeaconEvent.sourceUri.endsWith('/mime.js'), 'proper vulnerable module script'); 61 | t.ok(secondBeaconEvent.sourceUri.includes(`node_modules${path.sep}st${path.sep}node_modules${path.sep}mime`), 'proper vulnerable module base dir'); 62 | const expectedFilters = { 63 | 'st': {'st.js': {'Mount.prototype.getPath': null}}, 64 | 'mime': {'mime.js': {'Mime.prototype.lookup': null}}, 65 | 'negotiator': {'lib/language.js': {'parseLanguage': null}}, 66 | }; 67 | t.deepEqual(beaconData.filters, expectedFilters, 'instrumentation appears in beacon'); 68 | t.ok(beaconData.loadedSources, 'loadedSources present in beacon data'); 69 | t.ok('st' in beaconData.loadedSources, 'st was loaded'); 70 | t.deepEqual(beaconData.loadedSources['st'], {'0.1.4': {}}, 'expected st version'); 71 | t.ok('mime' in beaconData.loadedSources, 'mime was loaded'); 72 | t.deepEqual(beaconData.loadedSources['mime'], {'1.2.11': {}}, 'expected mime version'); 73 | }); 74 | 75 | // expecting a call to homebase for the newest snapshot 76 | nock('http://localhost:8000') 77 | .matchHeader('if-modified-since', (val) => { 78 | // making sure we got a Date here since I'm not sure what else to test in the 1st request 79 | try { 80 | new Date(val); 81 | return true; 82 | } catch (error) { 83 | return false; 84 | } 85 | }) 86 | .get('/api/v2/snapshot/A3B8ADA9-B726-41E9-BC6B-5169F7F89A0C/node') 87 | .reply(200, () => { 88 | const baseVulnerableFunctions = require('../lib/resources/functions.repo.json'); 89 | const newlyDiscoveredVulnerability = { 90 | functionId: { 91 | className: null, 92 | filePath: 'st.js', 93 | functionName: 'Mount.prototype.getUrl', 94 | }, 95 | packageName: 'st', 96 | version: ['<0.2.5'], 97 | }; 98 | const newSnapshot = baseVulnerableFunctions; 99 | newSnapshot.push(newlyDiscoveredVulnerability); 100 | return newSnapshot; 101 | }, {'Last-Modified': newSnapshotModificationDate.toUTCString()}); 102 | 103 | // third call will have three events because we updated the snapshot 104 | nock('http://localhost:8000') 105 | .post('/api/v1/beacon') 106 | .reply(200, (uri, requestBody) => { 107 | // assert the expected beacon data 108 | const beaconData = JSON.parse(requestBody); 109 | t.ok(beaconData.projectId, 'projectId present in beacon data'); 110 | t.ok(beaconData.agentId, 'agentId present in beacon data'); 111 | t.ok(beaconData.systemInfo, 'systemInfo present in beacon data'); 112 | t.ok(!('error' in beaconData.systemInfo), 'systemInfo has no errors'); 113 | t.ok(beaconData.eventsToSend, 'eventsToSend present in beacon data'); 114 | 115 | t.equal(beaconData.eventsToSend.length, 3, '3 events sent'); 116 | const methodNames = []; 117 | methodNames.push(beaconData.eventsToSend[0].methodEntry.methodName); 118 | methodNames.push(beaconData.eventsToSend[1].methodEntry.methodName); 119 | methodNames.push(beaconData.eventsToSend[2].methodEntry.methodName); 120 | t.ok(methodNames.indexOf('st.Mount.prototype.getPath') !== -1); 121 | t.ok(methodNames.indexOf('st.Mount.prototype.getUrl') !== -1); 122 | t.ok(methodNames.indexOf('mime.Mime.prototype.lookup') !== -1); 123 | const expectedFilters = { 124 | 'st': {'st.js': {'Mount.prototype.getPath': null, 'Mount.prototype.getUrl': null}}, 125 | 'mime': {'mime.js': {'Mime.prototype.lookup': null}}, 126 | 'negotiator': {'lib/language.js': {'parseLanguage': null}}, 127 | }; 128 | t.deepEqual(beaconData.filters, expectedFilters, 'instrumentation appears in beacon'); 129 | t.ok(beaconData.loadedSources, 'loadedSources present in beacon data'); 130 | t.ok('st' in beaconData.loadedSources, 'st was loaded'); 131 | t.deepEqual(beaconData.loadedSources['st'], {'0.1.4': {}}, 'expected st version'); 132 | t.ok('mime' in beaconData.loadedSources, 'mime was loaded'); 133 | t.deepEqual(beaconData.loadedSources['mime'], {'1.2.11': {}}, 'expected mime version'); 134 | }); 135 | 136 | // expecting next call to homebase for new snapshot to contain different If-Modified-Since header 137 | nock('http://localhost:8000') 138 | .matchHeader('if-modified-since', newSnapshotModificationDate.toUTCString()) 139 | .get('/api/v2/snapshot/A3B8ADA9-B726-41E9-BC6B-5169F7F89A0C/node') 140 | .reply(304, 'OK or whatever', {'Last-Modified': newSnapshotModificationDate.toUTCString()}); 141 | 142 | const BEACON_INTERVAL_MS = 1000; // 1 sec agent beacon interval 143 | const SNAPSHOT_INTERVAL_MS = 2500; // retrieve newer snapshot every 2.5 seconds 144 | 145 | // configure agent in demo server via env vars 146 | process.env.SNYK_HOMEBASE_ORIGIN = 'http://localhost:8000'; 147 | process.env.SNYK_BEACON_INTERVAL_MS = BEACON_INTERVAL_MS; 148 | process.env.SNYK_SNAPSHOT_INTERVAL_MS = SNAPSHOT_INTERVAL_MS; 149 | process.env.SNYK_TRIGGER_EXTRA_VULN = true; 150 | // 0: let the OS pick a free port 151 | process.env.PORT = 0; 152 | 153 | // bring up the demo server 154 | const demoApp = require('../demo'); 155 | const port = demoApp.address().port; 156 | 157 | // wait to let the agent go through a cycle 158 | await sleep(BEACON_INTERVAL_MS); 159 | 160 | // trigger the vuln method 161 | await needle.get(`http://localhost:${port}/hello.txt`); 162 | 163 | // wait to let the agent go through a cycle 164 | await sleep(BEACON_INTERVAL_MS); 165 | 166 | // wait until we refresh the snapshot 167 | await sleep(SNAPSHOT_INTERVAL_MS - BEACON_INTERVAL_MS * 2); 168 | 169 | // trigger the vuln method again 170 | await needle.get(`http://localhost:${port}/hello.txt`); 171 | 172 | // wait to let the agent go through another cycle with a new snapshot 173 | await sleep(BEACON_INTERVAL_MS); 174 | 175 | // wait to let the agent request another snapshot even though he has the latest 176 | await sleep(SNAPSHOT_INTERVAL_MS); 177 | 178 | // make sure all beacon calls were made 179 | t.ok(nock.isDone(), 'all beacon call were made'); 180 | 181 | delete process.env.SNYK_HOMEBASE_ORIGIN; 182 | delete process.env.SNYK_BEACON_INTERVAL_MS; 183 | delete process.env.SNYK_SNAPSHOT_INTERVAL_MS; 184 | delete process.env.SNYK_TRIGGER_EXTRA_VULN; 185 | delete process.env.PORT; 186 | 187 | await new Promise((resolve) => demoApp.close(resolve)); 188 | }); 189 | -------------------------------------------------------------------------------- /test/failures.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const sleep = require('sleep-promise'); 3 | 4 | const proxyquire = require('proxyquire'); 5 | 6 | test('node agent does not crash the demo app', async (t) => { 7 | const BEACON_INTERVAL_MS = 1000; 8 | process.env.SNYK_HOMEBASE_URL = 'http://localhost:8000/api/v1/beacon'; 9 | process.env.SNYK_BEACON_INTERVAL_MS = BEACON_INTERVAL_MS; 10 | // 0: let the OS pick a free port 11 | process.env.PORT = 0; 12 | 13 | // bring up the demo server, will fail on periodic tasks 14 | const demoApp = proxyquire('../demo', { 15 | '../lib': proxyquire('../lib', { 16 | './debugger-wrapper': { 17 | resumeSnoozedBreakpoints: () => { 18 | throw new Error("periodic failure"); 19 | }, 20 | }, 21 | }), 22 | }); 23 | 24 | // wait to let the agent go through a cycle 25 | await sleep(BEACON_INTERVAL_MS * 1); 26 | 27 | delete process.env.SNYK_HOMEBASE_URL; 28 | delete process.env.SNYK_BEACON_INTERVAL_MS; 29 | delete process.env.PORT; 30 | 31 | await new Promise((resolve) => demoApp.close(resolve)); 32 | }); 33 | 34 | test('node agent does not crash the demo app', async (t) => { 35 | const BEACON_INTERVAL_MS = 1000; 36 | process.env.SNYK_HOMEBASE_ORIGIN = 'http://localhost:-1'; 37 | process.env.SNYK_BEACON_INTERVAL_MS = BEACON_INTERVAL_MS; 38 | process.env.SNYK_SNAPSHOT_INTERVAL_MS = 200; 39 | // 0: let the OS pick a free port 40 | process.env.PORT = 0; 41 | 42 | // bring up the demo server, will fail on any outgoing request 43 | const demoApp = require('../demo'); 44 | 45 | // wait to let the agent go through a cycle 46 | await sleep(BEACON_INTERVAL_MS); 47 | 48 | delete process.env.SNYK_HOMEBASE_ORIGIN; 49 | delete process.env.SNYK_SNAPSHOT_INTERVAL_MS; 50 | delete process.env.SNYK_BEACON_INTERVAL_MS; 51 | delete process.env.PORT; 52 | 53 | await new Promise((resolve) => demoApp.close(resolve)); 54 | }); 55 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/build-date.repo: -------------------------------------------------------------------------------- 1 | Thu, 22 Dec 2018 14:02:33 GMT 2 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/functions.repo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionId": { 4 | "className": null, 5 | "filePath": "index.js", 6 | "functionName": "f" 7 | }, 8 | "packageName": "one-liner", 9 | "version": [ 10 | "<4.1.2" 11 | ] 12 | }, 13 | { 14 | "functionId": { 15 | "className": null, 16 | "filePath": "index.js", 17 | "functionName": "f0" 18 | }, 19 | "packageName": "multiple-one-liners", 20 | "version": [ 21 | "<4.1.2" 22 | ] 23 | }, 24 | { 25 | "functionId": { 26 | "className": null, 27 | "filePath": "index.js", 28 | "functionName": "f1" 29 | }, 30 | "packageName": "multiple-one-liners", 31 | "version": [ 32 | "<4.1.2" 33 | ] 34 | }, 35 | { 36 | "functionId": { 37 | "className": null, 38 | "filePath": "index.js", 39 | "functionName": "f2" 40 | }, 41 | "packageName": "multiple-one-liners", 42 | "version": [ 43 | "<4.1.2" 44 | ] 45 | }, 46 | { 47 | "functionId": { 48 | "className": null, 49 | "filePath": "index.js", 50 | "functionName": "f3" 51 | }, 52 | "packageName": "multiple-one-liners", 53 | "version": [ 54 | "<4.1.2" 55 | ] 56 | }, 57 | { 58 | "functionId": { 59 | "className": null, 60 | "filePath": "index.js", 61 | "functionName": "f4" 62 | }, 63 | "packageName": "multiple-one-liners", 64 | "version": [ 65 | "<4.1.2" 66 | ] 67 | }, 68 | { 69 | "functionId": { 70 | "className": null, 71 | "filePath": "index.js", 72 | "functionName": "f5" 73 | }, 74 | "packageName": "multiple-one-liners", 75 | "version": [ 76 | "<4.1.2" 77 | ] 78 | }, 79 | { 80 | "functionId": { 81 | "className": null, 82 | "filePath": "index.js", 83 | "functionName": "f6" 84 | }, 85 | "packageName": "multiple-one-liners", 86 | "version": [ 87 | "<4.1.2" 88 | ] 89 | }, 90 | { 91 | "functionId": { 92 | "className": null, 93 | "filePath": "index.js", 94 | "functionName": "f7" 95 | }, 96 | "packageName": "multiple-one-liners", 97 | "version": [ 98 | "<4.1.2" 99 | ] 100 | }, 101 | { 102 | "functionId": { 103 | "className": null, 104 | "filePath": "index.js", 105 | "functionName": "f8" 106 | }, 107 | "packageName": "multiple-one-liners", 108 | "version": [ 109 | "<4.1.2" 110 | ] 111 | }, 112 | { 113 | "functionId": { 114 | "className": null, 115 | "filePath": "index.js", 116 | "functionName": "f9" 117 | }, 118 | "packageName": "multiple-one-liners", 119 | "version": [ 120 | "<4.1.2" 121 | ] 122 | }, 123 | { 124 | "functionId": { 125 | "className": null, 126 | "filePath": "index.js", 127 | "functionName": "f10" 128 | }, 129 | "packageName": "multiple-one-liners", 130 | "version": [ 131 | "<4.1.2" 132 | ] 133 | }, 134 | { 135 | "functionId": { 136 | "className": null, 137 | "filePath": "index.js", 138 | "functionName": "f11" 139 | }, 140 | "packageName": "multiple-one-liners", 141 | "version": [ 142 | "<4.1.2" 143 | ] 144 | }, 145 | { 146 | "functionId": { 147 | "className": null, 148 | "filePath": "index.js", 149 | "functionName": "f12" 150 | }, 151 | "packageName": "multiple-one-liners", 152 | "version": [ 153 | "<4.1.2" 154 | ] 155 | }, 156 | { 157 | "functionId": { 158 | "className": null, 159 | "filePath": "index.js", 160 | "functionName": "f13" 161 | }, 162 | "packageName": "multiple-one-liners", 163 | "version": [ 164 | "<4.1.2" 165 | ] 166 | }, 167 | { 168 | "functionId": { 169 | "className": null, 170 | "filePath": "index.js", 171 | "functionName": "f14" 172 | }, 173 | "packageName": "multiple-one-liners", 174 | "version": [ 175 | "<4.1.2" 176 | ] 177 | }, 178 | { 179 | "functionId": { 180 | "className": null, 181 | "filePath": "index.js", 182 | "functionName": "f15" 183 | }, 184 | "packageName": "multiple-one-liners", 185 | "version": [ 186 | "<4.1.2" 187 | ] 188 | }, 189 | { 190 | "functionId": { 191 | "className": null, 192 | "filePath": "index.js", 193 | "functionName": "f16" 194 | }, 195 | "packageName": "multiple-one-liners", 196 | "version": [ 197 | "<4.1.2" 198 | ] 199 | }, 200 | { 201 | "functionId": { 202 | "className": null, 203 | "filePath": "index.js", 204 | "functionName": "f17" 205 | }, 206 | "packageName": "multiple-one-liners", 207 | "version": [ 208 | "<4.1.2" 209 | ] 210 | }, 211 | { 212 | "functionId": { 213 | "className": null, 214 | "filePath": "index.js", 215 | "functionName": "f18" 216 | }, 217 | "packageName": "multiple-one-liners", 218 | "version": [ 219 | "<4.1.2" 220 | ] 221 | }, 222 | { 223 | "functionId": { 224 | "className": null, 225 | "filePath": "index.js", 226 | "functionName": "f19" 227 | }, 228 | "packageName": "multiple-one-liners", 229 | "version": [ 230 | "<4.1.2" 231 | ] 232 | }, 233 | { 234 | "functionId": { 235 | "className": null, 236 | "filePath": "index.js", 237 | "functionName": "Moog.prototype.f" 238 | }, 239 | "packageName": "class-member", 240 | "version": [ 241 | "<4.1.2" 242 | ] 243 | }, 244 | { 245 | "functionId": { 246 | "className": null, 247 | "filePath": "index.js", 248 | "functionName": "module.exports.f0" 249 | }, 250 | "packageName": "multiple-declarations-in-exports", 251 | "version": [ 252 | "<4.1.2" 253 | ] 254 | }, 255 | { 256 | "functionId": { 257 | "className": null, 258 | "filePath": "index.js", 259 | "functionName": "module.exports.f1" 260 | }, 261 | "packageName": "multiple-declarations-in-exports", 262 | "version": [ 263 | "<4.1.2" 264 | ] 265 | }, 266 | { 267 | "functionId": { 268 | "className": null, 269 | "filePath": "index.js", 270 | "functionName": "module.exports.f2" 271 | }, 272 | "packageName": "multiple-declarations-in-exports", 273 | "version": [ 274 | "<4.1.2" 275 | ] 276 | }, 277 | { 278 | "functionId": { 279 | "className": null, 280 | "filePath": "index.js", 281 | "functionName": "module.exports.f3" 282 | }, 283 | "packageName": "multiple-declarations-in-exports", 284 | "version": [ 285 | "<4.1.2" 286 | ] 287 | }, 288 | { 289 | "functionId": { 290 | "className": null, 291 | "filePath": "index.js", 292 | "functionName": "module.exports.f4" 293 | }, 294 | "packageName": "multiple-declarations-in-exports", 295 | "version": [ 296 | "<4.1.2" 297 | ] 298 | }, 299 | { 300 | "functionId": { 301 | "className": null, 302 | "filePath": "index.js", 303 | "functionName": "module.exports.f5" 304 | }, 305 | "packageName": "multiple-declarations-in-exports", 306 | "version": [ 307 | "<4.1.2" 308 | ] 309 | }, 310 | { 311 | "functionId": { 312 | "className": null, 313 | "filePath": "index.js", 314 | "functionName": "module.exports.f6" 315 | }, 316 | "packageName": "multiple-declarations-in-exports", 317 | "version": [ 318 | "<4.1.2" 319 | ] 320 | }, 321 | { 322 | "functionId": { 323 | "className": null, 324 | "filePath": "index.js", 325 | "functionName": "module.exports.f7" 326 | }, 327 | "packageName": "multiple-declarations-in-exports", 328 | "version": [ 329 | "<4.1.2" 330 | ] 331 | }, 332 | { 333 | "functionId": { 334 | "className": null, 335 | "filePath": "index.js", 336 | "functionName": "module.exports.f8" 337 | }, 338 | "packageName": "multiple-declarations-in-exports", 339 | "version": [ 340 | "<4.1.2" 341 | ] 342 | }, 343 | { 344 | "functionId": { 345 | "className": null, 346 | "filePath": "index.js", 347 | "functionName": "module.exports.f9" 348 | }, 349 | "packageName": "multiple-declarations-in-exports", 350 | "version": [ 351 | "<4.1.2" 352 | ] 353 | }, 354 | { 355 | "functionId": { 356 | "className": null, 357 | "filePath": "index.js", 358 | "functionName": "module.exports.f" 359 | }, 360 | "packageName": "one-liner-declaration-in-exports", 361 | "version": [ 362 | "<4.1.2" 363 | ] 364 | } 365 | ] -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/class-member/index.js: -------------------------------------------------------------------------------- 1 | class Moog { 2 | constructor() {} 3 | sampleRate(freq) {} 4 | static f() { return 5; } 5 | } 6 | 7 | module.exports = Moog; 8 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/class-member/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "class-member", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "description": "" 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/inner-function/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inner-function", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "description": "" 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/lodash-cc-style/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash-cc-style", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "description": "" 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/multiple-declarations-in-exports/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | f0() {}, 3 | f1() {}, 4 | f2() {}, 5 | f3() {}, 6 | f4() {}, 7 | f5() {}, 8 | f6() {}, 9 | f7() {}, 10 | f8() {}, 11 | f9() {}, 12 | }; 13 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/multiple-declarations-in-exports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiple-declarations-in-exports", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "description": "" 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/multiple-one-liners/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | f0, f1, f2, f3, f4, 3 | f5, f6, f7, f8, f9, 4 | f10, f11, f12, f13, f14, 5 | f15, f16, f17, f18, f19, 6 | }; 7 | 8 | function f0() { return 0; } 9 | function f1() { return 0; } 10 | function f2() { return 0; } 11 | function f3() { return 0; } 12 | function f4() { return 0; } 13 | function f5() { return 0; } 14 | function f6() { return 0; } 15 | function f7() { return 0; } 16 | function f8() { return 0; } 17 | function f9() { return 0; } 18 | function f10() { return 0; } 19 | function f11() { return 0; } 20 | function f12() { return 0; } 21 | function f13() { return 0; } 22 | function f14() { return 0; } 23 | function f15() { return 0; } 24 | function f16() { return 0; } 25 | function f17() { return 0; } 26 | function f18() { return 0; } 27 | function f19() { return 0; } 28 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/multiple-one-liners/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiple-one-liners", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "description": "" 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/one-liner-declaration-in-exports/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { f() {} }; -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/one-liner-declaration-in-exports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "one-liner-declaration-in-exports", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "description": "" 11 | } -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/one-liner/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | f, 3 | }; 4 | 5 | function f() { return 0; } -------------------------------------------------------------------------------- /test/fixtures/function-declarations/node_modules/one-liner/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "one-liner", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "description": "" 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/handlebars/lib/handlebars/utils.js: -------------------------------------------------------------------------------- 1 | const escape = { 2 | '&': '&', 3 | '<': '<', 4 | '>': '>', 5 | '"': '"', 6 | "'": ''', 7 | '`': '`' 8 | }; 9 | 10 | const badChars = /[&<>"'`]/g, 11 | possible = /[&<>"'`]/; 12 | 13 | function escapeChar(chr) { 14 | return escape[chr]; 15 | } 16 | 17 | export function extend(obj /* , ...source */) { 18 | for (let i = 1; i < arguments.length; i++) { 19 | for (let key in arguments[i]) { 20 | if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { 21 | obj[key] = arguments[i][key]; 22 | } 23 | } 24 | } 25 | 26 | return obj; 27 | } 28 | 29 | export let toString = Object.prototype.toString; 30 | 31 | // Sourced from lodash 32 | // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt 33 | /*eslint-disable func-style, no-var */ 34 | var isFunction = function(value) { 35 | return typeof value === 'function'; 36 | }; 37 | // fallback for older versions of Chrome and Safari 38 | /* istanbul ignore next */ 39 | if (isFunction(/x/)) { 40 | isFunction = function(value) { 41 | return typeof value === 'function' && toString.call(value) === '[object Function]'; 42 | }; 43 | } 44 | export var isFunction; 45 | /*eslint-enable func-style, no-var */ 46 | 47 | /* istanbul ignore next */ 48 | export const isArray = Array.isArray || function(value) { 49 | return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; 50 | }; 51 | 52 | // Older IE versions do not directly support indexOf so we must implement our own, sadly. 53 | export function indexOf(array, value) { 54 | for (let i = 0, len = array.length; i < len; i++) { 55 | if (array[i] === value) { 56 | return i; 57 | } 58 | } 59 | return -1; 60 | } 61 | 62 | 63 | export function escapeExpression(string) { 64 | if (typeof string !== 'string') { 65 | // don't escape SafeStrings, since they're already safe 66 | if (string && string.toHTML) { 67 | return string.toHTML(); 68 | } else if (string == null) { 69 | return ''; 70 | } else if (!string) { 71 | return string + ''; 72 | } 73 | 74 | // Force a string conversion as this will be done by the append regardless and 75 | // the regex test will do this transparently behind the scenes, causing issues if 76 | // an object's to string has escaped characters in it. 77 | string = '' + string; 78 | } 79 | 80 | if (!possible.test(string)) { return string; } 81 | return string.replace(badChars, escapeChar); 82 | } 83 | 84 | export function isEmpty(value) { 85 | if (!value && value !== 0) { 86 | return true; 87 | } else if (isArray(value) && value.length === 0) { 88 | return true; 89 | } else { 90 | return false; 91 | } 92 | } 93 | 94 | export function blockParams(params, ids) { 95 | params.path = ids; 96 | return params; 97 | } 98 | 99 | export function appendContextPath(contextPath, id) { 100 | return (contextPath ? contextPath + '.' : '') + id; 101 | } 102 | -------------------------------------------------------------------------------- /test/fixtures/snapshots/bundled-snapshot.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionId": { 4 | "className": null, 5 | "filePath": "bundle.js", 6 | "functionName": "bundle.prototype.lookup" 7 | }, 8 | "packageName": "bundle", 9 | "version": ["<1.4.1"] 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /test/fixtures/st/node_modules/st.js: -------------------------------------------------------------------------------- 1 | module.exports = st 2 | 3 | st.Mount = Mount 4 | 5 | var mime = require('mime') 6 | var path = require('path') 7 | var fs 8 | try { 9 | fs = require('graceful-fs') 10 | } catch (e) { 11 | fs = require('fs') 12 | } 13 | var url = require('url') 14 | var zlib = require('zlib') 15 | var Neg = require('negotiator') 16 | var http = require('http') 17 | var AC = require('async-cache') 18 | var util = require('util') 19 | var FD = require('fd') 20 | 21 | // default caching options 22 | var defaultCacheOptions = { 23 | fd: { 24 | max: 1000, 25 | maxAge: 1000 * 60 * 60, 26 | }, 27 | stat: { 28 | max: 5000, 29 | maxAge: 1000 * 60 30 | }, 31 | content: { 32 | max: 1024 * 1024 * 64, 33 | length: function (n) { 34 | return n.length 35 | }, 36 | maxAge: 1000 * 60 * 10 37 | }, 38 | index: { 39 | max: 1024 * 8, 40 | length: function (n) { 41 | return n.length 42 | }, 43 | maxAge: 1000 * 60 * 10 44 | }, 45 | readdir: { 46 | max: 1000, 47 | length: function (n) { 48 | return n.length 49 | }, 50 | maxAge: 1000 * 60 * 10 51 | } 52 | } 53 | 54 | function st (opt) { 55 | var p, u 56 | if (typeof opt === 'string') { 57 | p = opt 58 | opt = arguments[1] 59 | if (typeof opt === 'string') { 60 | u = opt 61 | opt = arguments[2] 62 | } 63 | } 64 | 65 | if (!opt) opt = {} 66 | else opt = util._extend({}, opt) 67 | 68 | if (!p) p = opt.path 69 | if (typeof p !== 'string') throw new Error('no path specified') 70 | p = path.resolve(p) 71 | if (!u) u = opt.url 72 | if (!u) u = '' 73 | if (u.charAt(0) !== '/') u = '/' + u 74 | 75 | opt.url = u 76 | opt.path = p 77 | 78 | var m = new Mount(opt) 79 | var fn = m.serve.bind(m) 80 | fn._this = m 81 | return fn 82 | } 83 | 84 | function Mount (opt) { 85 | if (!opt) throw new Error('no options provided') 86 | if (typeof opt !== 'object') throw new Error('invalid options') 87 | if (!(this instanceof Mount)) return new Mount(opt) 88 | 89 | this.opt = opt 90 | this.url = opt.url 91 | this.path = opt.path 92 | this._index = opt.index === false ? false 93 | : typeof opt.index === 'string' ? opt.index 94 | : true 95 | this.fdman = FD() 96 | 97 | // cache basically everything 98 | var c = this.getCacheOptions(opt) 99 | this.cache = { 100 | fd: AC(c.fd), 101 | stat: AC(c.stat), 102 | index: AC(c.index), 103 | readdir: AC(c.readdir), 104 | content: AC(c.content) 105 | } 106 | } 107 | 108 | // lru-cache doesn't like when max=0, so we just pretend 109 | // everything is really big. kind of a kludge, but easiest way 110 | // to get it done 111 | var none = { max: 1, maxSize: 0, length: function() { 112 | return Infinity 113 | }} 114 | var noCaching = { 115 | fd: none, 116 | stat: none, 117 | index: none, 118 | readdir: none, 119 | content: none 120 | } 121 | 122 | Mount.prototype.getCacheOptions = function (opt) { 123 | var o = opt.cache 124 | , set = function (key) { 125 | return o[key] === false 126 | ? none 127 | : util._extend(util._extend({}, d[key]), o[key]) 128 | } 129 | 130 | if (o === false) 131 | o = noCaching 132 | else if (!o) 133 | o = {} 134 | 135 | var d = defaultCacheOptions 136 | 137 | // should really only ever set max and maxAge here. 138 | // load and fd disposal is important to control. 139 | var c = { 140 | fd: set('fd'), 141 | stat: set('stat'), 142 | index: set('index'), 143 | readdir: set('readdir'), 144 | content: set('content'), 145 | } 146 | 147 | c.fd.dispose = this.fdman.close.bind(this.fdman) 148 | c.fd.load = this.fdman.open.bind(this.fdman) 149 | 150 | c.stat.load = this._loadStat.bind(this) 151 | c.index.load = this._loadIndex.bind(this) 152 | c.readdir.load = this._loadReaddir.bind(this) 153 | c.content.load = this._loadContent.bind(this) 154 | return c 155 | } 156 | 157 | // get a path from a url 158 | Mount.prototype.getPath = function (u) { 159 | u = path.normalize(url.parse(u).pathname.replace(/^[\/\\]?/, '/')).replace(/\\/g, '/') 160 | if (u.indexOf(this.url) !== 0) return false 161 | 162 | try { 163 | u = decodeURIComponent(u) 164 | } 165 | catch (e) { 166 | // if decodeURIComponent failed, we weren't given a valid URL to begin with. 167 | return false 168 | } 169 | 170 | // /a/b/c mounted on /path/to/z/d/x 171 | // /a/b/c/d --> /path/to/z/d/x/d 172 | u = u.substr(this.url.length) 173 | if (u.charAt(0) !== '/') u = '/' + u 174 | var p = path.join(this.path, u) 175 | return p 176 | } 177 | 178 | // get a url from a path 179 | Mount.prototype.getUrl = function (p) { 180 | p = path.resolve(p) 181 | if (p.indexOf(this.path) !== 0) return false 182 | p = path.join('/', p.substr(this.path.length)) 183 | var u = path.join(this.url, p).replace(/\\/g, '/') 184 | return u 185 | } 186 | 187 | Mount.prototype.serve = function (req, res, next) { 188 | if (req.method !== 'HEAD' && req.method !== 'GET') { 189 | if (typeof next === 'function') next() 190 | return false 191 | } 192 | 193 | // querystrings are of no concern to us 194 | req.url = url.parse(req.url).pathname 195 | 196 | var p = this.getPath(req.url) 197 | if (!p) { 198 | if (typeof next === 'function') next() 199 | return false 200 | } 201 | 202 | // don't allow dot-urls by default, unless explicitly allowed. 203 | if (!this.opt.dot && p.match(/(^|\/)\./)) { 204 | res.statusCode = 403 205 | res.end('Forbidden') 206 | return true 207 | } 208 | 209 | // now we have a path. check for the fd. 210 | this.cache.fd.get(p, function (er, fd) { 211 | // inability to open is some kind of error, probably 404 212 | // if we're in passthrough, AND got a next function, we can 213 | // fall through to that. otherwise, we already returned true, 214 | // send an error. 215 | if (er) { 216 | if (this.opt.passthrough === true && er.code === 'ENOENT' && next) 217 | return next() 218 | return this.error(er, res) 219 | } 220 | 221 | // we may be about to use this, so don't let it be closed by cache purge 222 | this.fdman.checkout(p, fd) 223 | // a safe end() function that can be called multiple times but 224 | // only perform a single checkin 225 | var end = this.fdman.checkinfn(p, fd) 226 | 227 | this.cache.stat.get(fd+':'+p, function (er, stat) { 228 | if (er) { 229 | if (next && this.opt.passthrough === true && this._index === false) { 230 | return next() 231 | } 232 | end() 233 | return this.error(er, res) 234 | } 235 | 236 | var ims = req.headers['if-modified-since'] 237 | if (ims) ims = new Date(ims).getTime() 238 | if (ims && ims >= stat.mtime.getTime()) { 239 | res.statusCode = 304 240 | res.end() 241 | return end() 242 | } 243 | 244 | var etag = getEtag(stat) 245 | if (req.headers['if-none-match'] === etag) { 246 | res.statusCode = 304 247 | res.end() 248 | return end() 249 | } 250 | 251 | res.setHeader('cache-control', 'public') 252 | res.setHeader('last-modified', stat.mtime.toUTCString()) 253 | res.setHeader('etag', etag) 254 | 255 | if (stat.isDirectory()) { 256 | end() 257 | if (next && this.opt.passthrough === true && this._index === false) { 258 | return next() 259 | } 260 | return this.index(p, req, res) 261 | } 262 | 263 | return this.file(p, fd, stat, etag, req, res, end) 264 | }.bind(this)) 265 | }.bind(this)) 266 | 267 | return true 268 | } 269 | 270 | Mount.prototype.error = function (er, res) { 271 | res.statusCode = typeof er === 'number' ? er 272 | : er.code === 'ENOENT' || er.code === 'EISDIR' ? 404 273 | : er.code === 'EPERM' || er.code === 'EACCES' ? 403 274 | : 500 275 | 276 | if (typeof res.error === 'function') { 277 | // pattern of express and ErrorPage 278 | return res.error(res.statusCode, er) 279 | } 280 | 281 | res.setHeader('content-type', 'text/plain') 282 | res.end(http.STATUS_CODES[res.statusCode] + '\n') 283 | } 284 | 285 | Mount.prototype.index = function (p, req, res) { 286 | if (this._index === true) { 287 | return this.autoindex(p, req, res) 288 | } 289 | if (typeof this._index === 'string') { 290 | if (!/\/$/.test(req.url)) req.url += '/' 291 | req.url += this._index 292 | return this.serve(req, res) 293 | } 294 | return this.error(404, res) 295 | } 296 | 297 | Mount.prototype.autoindex = function (p, req, res) { 298 | if (!/\/$/.exec(req.url)) { 299 | res.statusCode = 301 300 | res.setHeader('location', req.url + '/') 301 | res.end('Moved: ' + req.url + '/') 302 | return 303 | } 304 | 305 | this.cache.index.get(p, function (er, html) { 306 | if (er) return this.error(er, res) 307 | 308 | res.statusCode = 200 309 | res.setHeader('content-type', 'text/html') 310 | res.setHeader('content-length', html.length) 311 | res.end(html) 312 | }.bind(this)) 313 | } 314 | 315 | 316 | Mount.prototype.file = function (p, fd, stat, etag, req, res, end) { 317 | var key = stat.size + ':' + etag 318 | 319 | var mt = mime.lookup(path.extname(p)) 320 | if (mt !== 'application/octet-stream') { 321 | res.setHeader('content-type', mt) 322 | } 323 | 324 | // only use the content cache if it will actually fit there. 325 | if (this.cache.content.has(key)) { 326 | end() 327 | this.cachedFile(p, stat, etag, req, res) 328 | } else { 329 | this.streamFile(p, fd, stat, etag, req, res, end) 330 | } 331 | } 332 | 333 | Mount.prototype.cachedFile = function (p, stat, etag, req, res) { 334 | var key = stat.size + ':' + etag 335 | var gz = getGz(p, req) 336 | 337 | this.cache.content.get(key, function (er, content) { 338 | if (er) return this.error(er, res) 339 | res.statusCode = 200 340 | if (gz && content.gz) { 341 | res.setHeader('content-encoding', 'gzip') 342 | res.setHeader('content-length', content.gz.length) 343 | res.end(content.gz) 344 | } else { 345 | res.setHeader('content-length', content.length) 346 | res.end(content) 347 | } 348 | }.bind(this)) 349 | } 350 | 351 | Mount.prototype.streamFile = function (p, fd, stat, etag, req, res, end) { 352 | var streamOpt = { fd: fd, start: 0, end: stat.size } 353 | var stream = fs.createReadStream(p, streamOpt) 354 | stream.destroy = function () {} 355 | 356 | // too late to effectively handle any errors. 357 | // just kill the connection if that happens. 358 | stream.on('error', function(e) { 359 | console.error('Error serving %s fd=%d\n%s', p, fd, e.stack || e.message) 360 | res.socket.destroy() 361 | end() 362 | }) 363 | 364 | if (res.filter) { 365 | stream = stream.pipe(res.filter) 366 | } 367 | var gzstr = zlib.Gzip() 368 | 369 | var gz = getGz(p, req) 370 | 371 | res.statusCode = 200 372 | stream.pipe(gzstr) 373 | 374 | if (gz) { 375 | // we don't know how long it'll be, since it will be compressed. 376 | res.setHeader('content-encoding', 'gzip') 377 | gzstr.pipe(res) 378 | } else { 379 | if (!res.filter) res.setHeader('content-length', stat.size) 380 | stream.pipe(res) 381 | } 382 | 383 | stream.on('end', function () { 384 | process.nextTick(end) 385 | }) 386 | 387 | if (this.cache.content._cache.max > stat.size) { 388 | // collect it, and put it in the cache 389 | var key = fd + ':' + stat.size + ':' + etag 390 | var bufs = [] 391 | var gzbufs = [] 392 | stream.on('data', function (c) { 393 | bufs.push(c) 394 | }) 395 | gzstr.on('data', function (c) { 396 | gzbufs.push(c) 397 | }) 398 | gzstr.on('end', function () { 399 | var content = Buffer.concat(bufs) 400 | content.gz = Buffer.concat(gzbufs) 401 | this.cache.content.set(key, content) 402 | }.bind(this)) 403 | } 404 | } 405 | 406 | 407 | // cache-fillers 408 | 409 | Mount.prototype._loadIndex = function (p, cb) { 410 | // truncate off the first bits 411 | var url = p.substr(this.path.length).replace(/\\/g, '/') 412 | var str = 413 | '' + 414 | '' + 415 | 'Index of ' + url + '' + 416 | '' + 417 | '

Index of ' + url + '

' + 418 | '
../\n'
419 | 
420 |   this.cache.readdir.get(p, function (er, data) {
421 |     if (er) return cb(er)
422 | 
423 |     var nameLen = 0
424 |     var sizeLen = 0
425 | 
426 |     Object.keys(data).map(function (f) {
427 |       var d = data[f]
428 |       var name = f.replace(/"/g, '"')
429 |       if (d.size === '-') name += '/'
430 |       var showName = name.replace(/^(.{40}).{3,}$/, '$1..>')
431 |       nameLen = Math.max(nameLen, showName.length)
432 |       sizeLen = Math.max(sizeLen, ('' + d.size).length)
433 |       return [ '' + showName + '',
434 |                d.mtime, d.size, showName ]
435 |     }).sort(function (a, b) {
436 |       return a[2] === '-' && b[2] !== '-' ? -1 // dirs first
437 |            : a[2] !== '-' && b[2] === '-' ? 1
438 |            : a[0].toLowerCase() < b[0].toLowerCase() ? -1 // then alpha
439 |            : a[0].toLowerCase() > b[0].toLowerCase() ? 1
440 |            : 0
441 |     }).forEach(function (line) {
442 |       var namePad = new Array(8 + nameLen - line[3].length).join(' ')
443 |       var sizePad = new Array(8 + sizeLen - ('' + line[2]).length).join(' ')
444 |       str += line[0] + namePad +
445 |              line[1].toISOString() +
446 |              sizePad + line[2] + '\n'
447 |     })
448 | 
449 |     str += '

' 450 | cb(null, new Buffer(str)) 451 | }) 452 | } 453 | 454 | Mount.prototype._loadReaddir = function (p, cb) { 455 | var len 456 | var data 457 | fs.readdir(p, function (er, files) { 458 | if (er) return cb(er) 459 | files = files.filter(function (f) { 460 | if (!this.opt.dot) return !/^\./.test(f) 461 | else return f !== '.' && f !== '..' 462 | }.bind(this)) 463 | len = files.length 464 | data = {} 465 | files.forEach(function (file) { 466 | var pf = path.join(p, file) 467 | this.cache.stat.get(pf, function (er, stat) { 468 | if (er) return cb(er) 469 | if (stat.isDirectory()) stat.size = '-' 470 | data[file] = stat 471 | next() 472 | }.bind(this)) 473 | }.bind(this)) 474 | }.bind(this)) 475 | 476 | function next () { 477 | if (--len === 0) cb(null, data) 478 | } 479 | } 480 | 481 | Mount.prototype._loadStat = function (key, cb) { 482 | // key is either fd:path or just a path 483 | var fdp = key.match(/^(\d+):(.*)/) 484 | if (fdp) { 485 | var fd = +fdp[1] 486 | var p = fdp[2] 487 | fs.fstat(fd, function (er, stat) { 488 | if (er) return cb(er) 489 | this.cache.stat.set(p, stat) 490 | cb(null, stat) 491 | }.bind(this)) 492 | } else { 493 | fs.stat(key, cb) 494 | } 495 | } 496 | 497 | Mount.prototype._loadContent = function (key, cb) { 498 | // this function should never be called. 499 | // we check if the thing is in the cache, and if not, stream it in 500 | // manually. this.cache.content.get() should not ever happen. 501 | throw new Error('This should not ever happen') 502 | } 503 | 504 | function getEtag (s) { 505 | return '"' + s.dev + '-' + s.ino + '-' + s.mtime.getTime() + '"' 506 | } 507 | 508 | function getGz (p,req) { 509 | var gz = false 510 | if (!/\.t?gz$/.exec(p)) { 511 | var neg = req.negotiator || new Neg(req) 512 | gz = neg.preferredEncoding(['gzip', 'identity']) === 'gzip' 513 | } 514 | return gz 515 | } 516 | -------------------------------------------------------------------------------- /test/fixtures/st/script.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "scriptId": 1111, 4 | "url": "./fixtures/st/node_modules/st.js" 5 | } 6 | } -------------------------------------------------------------------------------- /test/fixtures/st/vulnerable_methods.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionId": { 4 | "className": null, 5 | "filePath": "st.js", 6 | "functionName": "Mount.prototype.getPath" 7 | }, 8 | "packageName": "st", 9 | "version": [ 10 | "<0.2.5" 11 | ] 12 | }, 13 | { 14 | "functionId": { 15 | "className": null, 16 | "filePath": "st.js", 17 | "functionName": "Mount.prototype.getCacheOptions" 18 | }, 19 | "packageName": "st", 20 | "version": [ 21 | "<0.2.5" 22 | ] 23 | }, 24 | { 25 | "functionId": { 26 | "className": null, 27 | "filePath": "st.js", 28 | "functionName": "Mount.prototype.getUrl" 29 | }, 30 | "packageName": "st", 31 | "version": [ 32 | "<0.2.5" 33 | ] 34 | } 35 | ] -------------------------------------------------------------------------------- /test/fixtures/st/vulnerable_methods_invalid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionId": { 4 | "className": null, 5 | "filePath": "st.js", 6 | "functionName": "getPath" 7 | }, 8 | "packageName": "st", 9 | "version": [ 10 | "<0.2.5" 11 | ] 12 | }, 13 | { 14 | "functionId": { 15 | "className": null, 16 | "filePath": "St.js", 17 | "functionName": "Mount.prototype.getCacheOptions" 18 | }, 19 | "packageName": "st", 20 | "version": [ 21 | "<0.2.5" 22 | ] 23 | }, 24 | { 25 | "functionId": { 26 | "className": null, 27 | "filePath": "st.js", 28 | "functionName": "Mount.prototype.getUrl" 29 | }, 30 | "packageName": "st", 31 | "version": [ 32 | "<0.2.5" 33 | ] 34 | } 35 | ] -------------------------------------------------------------------------------- /test/fixtures/st/vulnerable_methods_new.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionId": { 4 | "className": null, 5 | "filePath": "st.js", 6 | "functionName": "Mount.prototype.getPath" 7 | }, 8 | "packageName": "st", 9 | "version": [ 10 | "<0.2.5" 11 | ] 12 | }, 13 | { 14 | "functionId": { 15 | "className": null, 16 | "filePath": "st.js", 17 | "functionName": "Mount.prototype.serve" 18 | }, 19 | "packageName": "st", 20 | "version": [ 21 | "<0.2.5" 22 | ] 23 | } 24 | ] -------------------------------------------------------------------------------- /test/function-declaration-types.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const nock = require('nock'); 3 | const path = require('path'); 4 | const sleep = require('sleep-promise'); 5 | 6 | const agentConfig = { 7 | projectId: 'hi', 8 | beaconIntervalMs: 1000, 9 | url: 'http://localhost:8000/api/v1/beacon', 10 | functionPaths: { 11 | repo: { 12 | snapshot: path.join(__dirname, 'fixtures/function-declarations/functions.repo.json'), 13 | date: path.join(__dirname, 'fixtures/function-declarations/build-date.repo'), 14 | }, 15 | bundle: { 16 | snapshot: 'FILE/DOES/NOT/EXIST/EVER/EVER', 17 | date: 'FILE/DOES/NOT/EXIST/EVER/EVER', 18 | }, 19 | }, 20 | }; 21 | 22 | test('function declaration variations', async (t) => { 23 | 24 | nock('http://localhost:8000') 25 | .post('/api/v1/beacon') 26 | .reply(200, (uri, requestBody) => { 27 | const beaconData = JSON.parse(requestBody); 28 | t.ok(beaconData.eventsToSend, 'eventsToSend present in beacon data'); 29 | t.equal(beaconData.eventsToSend.length, 0, 'no events sent'); 30 | }); 31 | 32 | nock('http://localhost:8000') 33 | .post('/api/v1/beacon') 34 | .reply(200, (uri, requestBody) => { 35 | const beaconData = JSON.parse(requestBody); 36 | t.ok(beaconData.eventsToSend, 'eventsToSend present in beacon data'); 37 | t.equal(beaconData.eventsToSend.length, 8, 'no events sent'); 38 | t.equal(beaconData.eventsToSend[0].methodEntry.methodName, 'one-liner.f', 'one-liner method detected'); 39 | t.equal(beaconData.eventsToSend[1].methodEntry.methodName, 'multiple-one-liners.f0', 'multiple-one-liners method detected'); 40 | t.equal(beaconData.eventsToSend[2].methodEntry.methodName, 'multiple-one-liners.f10', 'multiple-one-liners method detected'); 41 | t.equal(beaconData.eventsToSend[3].methodEntry.methodName, 'multiple-one-liners.f18', 'multiple-one-liners method detected'); 42 | t.equal(beaconData.eventsToSend[4].methodEntry.methodName, 'class-member.Moog.prototype.f', 'class member method detected'); 43 | t.equal(beaconData.eventsToSend[5].methodEntry.methodName, 'multiple-declarations-in-exports.module.exports.f0', 'multiple-declarations-in-exports method detected'); 44 | t.equal(beaconData.eventsToSend[6].methodEntry.methodName, 'multiple-declarations-in-exports.module.exports.f4', 'multiple-declarations-in-exports method detected'); 45 | t.equal(beaconData.eventsToSend[7].methodEntry.methodName, 'multiple-declarations-in-exports.module.exports.f9', 'multiple-declarations-in-exports method detected'); 46 | // not supported yet 47 | // t.equal(beaconData.eventsToSend[8].methodEntry.methodName, 'one-liner-declaration-in-exports.module.exports.f', 'one-liner-declaration-in-exports method detected'); 48 | }); 49 | 50 | // start the agent 51 | require('../lib')(agentConfig); 52 | 53 | // require vulnerable packages 54 | const oneLiner = require('./fixtures/function-declarations/node_modules/one-liner'); 55 | const multipleOneLiners = require('./fixtures/function-declarations/node_modules/multiple-one-liners'); 56 | const classMember = require('./fixtures/function-declarations/node_modules/class-member'); 57 | const multipleDeclarationInExports = require('./fixtures/function-declarations/node_modules/multiple-declarations-in-exports'); 58 | // not supported yet 59 | // const oneLinerDeclarationInExports = require('./fixtures/function-declarations/node_modules/one-liner-declaration-in-exports'); 60 | 61 | // wait to let the agent go through a cycle 62 | await sleep(1000); 63 | 64 | // trigger vulnerable functions 65 | oneLiner.f(); 66 | multipleOneLiners.f0(); 67 | multipleOneLiners.f10(); 68 | multipleOneLiners.f18(); 69 | classMember.f(); 70 | multipleDeclarationInExports.f0(); 71 | multipleDeclarationInExports.f4(); 72 | multipleDeclarationInExports.f9(); 73 | // not supported yet 74 | // oneLinerDeclarationInExports.f(); 75 | 76 | // wait to let the agent go through another cycle 77 | await sleep(1000); 78 | 79 | t.ok(nock.isDone(), 'all beacon call were made'); 80 | }); 81 | -------------------------------------------------------------------------------- /test/method-detection.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const test = require('tap').test; 3 | 4 | const ast = require('../lib/ast.js'); 5 | 6 | test('test bootstrap +function method detection', function (t) { 7 | const contents = ` 8 | +function ($){ 9 | var Aye = function (one, two) { 10 | } 11 | Aye.prototype.foo = function (three) { 12 | } 13 | $.fn.aye = Aye 14 | }(jQuery); 15 | `; 16 | const methods = ['Aye', 'Aye.prototype.foo']; 17 | const found = ast.findAllVulnerableFunctionsInScript( 18 | contents, methods, 19 | ); 20 | t.same(sorted(Object.keys(found)), sorted(methods)); 21 | t.equal(found[methods[0]].start.line, 3, 'A found'); 22 | t.equal(found[methods[1]].start.line, 5, 'A.prototype.foo found'); 23 | t.end(); 24 | }); 25 | 26 | 27 | test('test clashing variable/function declaration', function (t) { 28 | const contents = ` 29 | var foo; 30 | { 31 | function foo() { 32 | } 33 | } 34 | `; 35 | const methods = ['foo']; 36 | const found = ast.findAllVulnerableFunctionsInScript( 37 | contents, methods, 38 | ); 39 | t.same(sorted(Object.keys(found)), sorted(methods)); 40 | t.equal(found[methods[0]].start.line, 4, 'foo'); 41 | t.end(); 42 | }); 43 | 44 | test('test function body method detection', function (t) { 45 | const contents = ` 46 | function foo() { 47 | function bar() {} 48 | } 49 | `; 50 | const methods = ['foo', 'foo.bar']; 51 | const found = ast.findAllVulnerableFunctionsInScript( 52 | contents, methods, 53 | ); 54 | t.same(sorted(Object.keys(found)), sorted(methods)); 55 | t.equal(found[methods[0]].start.line, 2, 'foo'); 56 | t.equal(found[methods[1]].start.line, 3, 'foo.bar'); 57 | t.end(); 58 | }); 59 | 60 | test('test anonymous function not splatting parent', function (t) { 61 | const contents = ` 62 | function foo() { 63 | console.log(function() {}); 64 | } 65 | `; 66 | const methods = ['foo']; 67 | const found = ast.findAllVulnerableFunctionsInScript( 68 | contents, methods, 69 | ); 70 | t.same(sorted(Object.keys(found)), sorted(methods)); 71 | t.equal(found[methods[0]].start.line, 2, 'foo'); 72 | t.end(); 73 | }); 74 | 75 | test('test array element inspection', function (t) { 76 | const contents = ` 77 | console.log([function() { 78 | function foo() {} 79 | }]); 80 | `; 81 | const methods = ['foo']; 82 | const found = ast.findAllVulnerableFunctionsInScript( 83 | contents, methods, 84 | ); 85 | t.same(sorted(Object.keys(found)), sorted(methods)); 86 | t.equal(found[methods[0]].start.line, 3, 'foo'); 87 | t.end(); 88 | }); 89 | 90 | test('test if body inspection', function (t) { 91 | const contents = ` 92 | if (console.singular) { 93 | function foo() {} 94 | } 95 | if (console.both) { 96 | function bar() {} 97 | } else { 98 | function baz() {} 99 | } 100 | `; 101 | const methods = ['bar', 'baz', 'foo']; 102 | const found = ast.findAllVulnerableFunctionsInScript( 103 | contents, methods, 104 | ); 105 | t.same(sorted(Object.keys(found)), sorted(methods)); 106 | t.equal(found[methods[0]].start.line, 6, 'foo'); 107 | t.equal(found[methods[1]].start.line, 8, 'bar'); 108 | t.equal(found[methods[2]].start.line, 3, 'baz'); 109 | t.end(); 110 | }); 111 | 112 | test('test lazy class declaration', function (t) { 113 | const contents = ` 114 | var Class = Class || (function (Object) { 115 | function foo() {} 116 | }); 117 | `; 118 | const methods = ['Class.foo']; 119 | const found = ast.findAllVulnerableFunctionsInScript( 120 | contents, methods, 121 | ); 122 | t.same(sorted(Object.keys(found)), sorted(methods)); 123 | t.equal(found[methods[0]].start.line, 3, 'foo'); 124 | t.end(); 125 | }); 126 | 127 | test('test st method detection', function (t) { 128 | const content = fs.readFileSync(__dirname + '/fixtures/st/node_modules/st.js'); 129 | const methods = ['Mount.prototype.getPath']; 130 | const line = 158; 131 | const found = ast.findAllVulnerableFunctionsInScript( 132 | content, methods, 133 | ); 134 | t.same(sorted(Object.keys(found)), sorted(methods)); 135 | t.equal(found[methods[0]].start.line, line, 'Mount.prototype.getPath found'); 136 | t.end(); 137 | }); 138 | 139 | test('test handlebars method detection', function (t) { 140 | const content = fs.readFileSync(__dirname + '/fixtures/handlebars/lib/handlebars/utils.js'); 141 | const methods = ['escapeExpression']; 142 | const line = 63; 143 | const found = ast.findAllVulnerableFunctionsInScript( 144 | content, methods, 145 | ); 146 | t.same(sorted(Object.keys(found)), sorted(methods)); 147 | t.equal(found[methods[0]].start.line, line, 'escapeExpression found'); 148 | t.end(); 149 | }); 150 | 151 | test('test uglify-js method detection', function (t) { 152 | const content = fs.readFileSync(__dirname + '/fixtures/uglify-js/lib/parse.js'); 153 | const methods = ['parse_js_number']; 154 | const line = 180; 155 | const found = ast.findAllVulnerableFunctionsInScript( 156 | content, methods, 157 | ); 158 | t.same(sorted(Object.keys(found)), sorted(methods)); 159 | t.equal(found[methods[0]].start.line, line, 'parse_js_number found'); 160 | t.end(); 161 | }); 162 | 163 | 164 | test('test explicit fake-anonymous function', function (t) { 165 | const contents = ` 166 | const foo = function bar() { 167 | function baz() {} 168 | }; 169 | `; 170 | const methods = ['bar', 'bar.baz']; 171 | const found = ast.findAllVulnerableFunctionsInScript( 172 | contents, methods, 173 | ); 174 | t.same(sorted(Object.keys(found)), sorted(methods)); 175 | t.equal(found[methods[0]].start.line, 2, 'bar found'); 176 | t.equal(found[methods[1]].start.line, 3, 'bar.baz found'); 177 | t.end(); 178 | }); 179 | 180 | test('test export = { f() {} } method detection', function (t) { 181 | const contents = ` 182 | module.exports = { 183 | foo() {}, 184 | bar() {}, 185 | }; 186 | `; 187 | const methods = ['module.exports.foo', 'module.exports.bar']; 188 | const found = ast.findAllVulnerableFunctionsInScript( 189 | contents, methods, 190 | ); 191 | t.same(sorted(Object.keys(found)), sorted(methods)); 192 | t.equal(found[methods[0]].start.line, 3, 'foo found'); 193 | t.equal(found[methods[1]].start.line, 4, 'bar found'); 194 | t.end(); 195 | }); 196 | 197 | test('test export default method detection', function (t) { 198 | const contents = ` 199 | export default function() {} 200 | `; 201 | const methods = ['module.exports']; 202 | const found = ast.findAllVulnerableFunctionsInScript( 203 | contents, methods, 204 | ); 205 | t.same(sorted(Object.keys(found)), sorted(methods)); 206 | t.equal(found[methods[0]].start.line, 2, 207 | 'export default aliased to module.exports'); 208 | t.end(); 209 | }); 210 | 211 | test('test class member detection', function (t) { 212 | const contents = ` 213 | class Moog { 214 | constructor() {} 215 | sampleRate(freq) {} 216 | static lfo() { return 5; } 217 | } 218 | 219 | module.exports = Moog; 220 | `; 221 | const methods = ['Moog.prototype.constructor', 'Moog.prototype.sampleRate', 'Moog.prototype.lfo']; 222 | const found = ast.findAllVulnerableFunctionsInScript( 223 | contents, methods, 224 | ); 225 | t.same(sorted(Object.keys(found)), sorted(methods)); 226 | t.equal(found[methods[0]].start.line, 3, 'constructor found'); 227 | t.equal(found[methods[1]].start.line, 4, 'sampleRate found'); 228 | t.equal(found[methods[2]].start.line, 5, 'lfo found'); 229 | t.end(); 230 | }); 231 | 232 | test('test inner function function detection', function (t) { 233 | const contents = ` 234 | ;(function() { 235 | var runInContext = (function yellow(context) { 236 | function baseMerge() {} 237 | }); 238 | var _ = runInContext(); 239 | })(); 240 | `; 241 | const methods = ['yellow.baseMerge']; 242 | const found = ast.findAllVulnerableFunctionsInScript( 243 | contents, methods, 244 | ); 245 | t.same(sorted(Object.keys(found)), sorted(methods)); 246 | t.equal(found[methods[0]].start.line, 4, 'baseMerge found'); 247 | t.end(); 248 | }); 249 | 250 | test('test literals in objects detection', function (t) { 251 | const contents = ` 252 | console.log({ 253 | 1: function() { function foo() { } }, 254 | 'foo-bar': function() { function foo_bar() { } }, 255 | }); 256 | `; 257 | const methods = ['1.foo', 'foo-bar.foo_bar']; 258 | const found = ast.findAllVulnerableFunctionsInScript( 259 | contents, methods, 260 | ); 261 | t.same(sorted(Object.keys(found)), sorted(methods)); 262 | t.equal(found[methods[0]].start.line, 3, 'foo found'); 263 | t.equal(found[methods[1]].start.line, 4, 'foo_bar found'); 264 | t.end(); 265 | }); 266 | 267 | test('test lodash-CC-style function detection', function (t) { 268 | const contents = ` 269 | ;(function() { 270 | var runInContext = (function yellow(context) { 271 | function baseMerge() {} 272 | }); 273 | var _ = runInContext(); 274 | }.call(this)); 275 | `; 276 | const methods = ['yellow.baseMerge']; 277 | const found = ast.findAllVulnerableFunctionsInScript( 278 | contents, methods, 279 | ); 280 | t.same(sorted(Object.keys(found)), sorted(methods)); 281 | t.equal(found[methods[0]].start.line, 4, 'baseMerge found'); 282 | t.end(); 283 | }); 284 | 285 | test('test moment-style wrapper detection', function (t) { 286 | const contents = ` 287 | ;(function () { 288 | }(this, (function () { 289 | function hooks() {} 290 | }))); 291 | `; 292 | const methods = ['hooks']; 293 | const found = ast.findAllVulnerableFunctionsInScript( 294 | contents, methods, 295 | ); 296 | t.same(sorted(Object.keys(found)), sorted(methods)); 297 | t.equal(found[methods[0]].start.line, 4, 'hooks found'); 298 | t.end(); 299 | }); 300 | 301 | test('test octal parsing', function (t) { 302 | const contents = ` 303 | function foo() { 304 | return 0777; 305 | } 306 | `; 307 | const methods = ['foo']; 308 | const found = ast.findAllVulnerableFunctionsInScript( 309 | contents, methods, 310 | ); 311 | t.same(sorted(Object.keys(found)), sorted(methods)); 312 | t.equal(found[methods[0]].start.line, 2, 'foo found'); 313 | t.end(); 314 | }); 315 | 316 | test('test return expression parsing', function (t) { 317 | const contents = ` 318 | foo(function () { 319 | return function bar() { 320 | function baz() {} 321 | } 322 | }); 323 | `; 324 | const methods = ['bar', 'bar.baz']; 325 | const found = ast.findAllVulnerableFunctionsInScript( 326 | contents, methods, 327 | ); 328 | t.same(sorted(Object.keys(found)), sorted(methods)); 329 | t.equal(found[methods[0]].start.line, 3, 'bar found'); 330 | t.equal(found[methods[1]].start.line, 4, 'bar.baz found'); 331 | t.end(); 332 | }); 333 | 334 | test('test ws const arrow function detection', function (t) { 335 | const contents = ` 336 | const parse = (value) => { 337 | console.log("hi!"); 338 | function foo() {} 339 | }; 340 | 341 | module.exports = { parse }; 342 | `; 343 | const methods = ['parse', 'parse.foo']; 344 | const found = ast.findAllVulnerableFunctionsInScript( 345 | contents, methods, 346 | ); 347 | t.same(sorted(Object.keys(found)), sorted(methods)); 348 | t.equal(found[methods[0]].start.line, 2, 'parse found'); 349 | t.equal(found[methods[1]].start.line, 4, 'parse.foo found'); 350 | t.end(); 351 | }); 352 | 353 | function sorted(list) { 354 | const copy = []; 355 | copy.push.apply(copy, list); 356 | copy.sort(); 357 | return copy; 358 | } 359 | -------------------------------------------------------------------------------- /test/shutdown.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const http = require('http'); 3 | const spawn = require('child_process').spawn; 4 | 5 | test('agent transmits before exit by default', t => { 6 | t.plan(3); 7 | 8 | // small server the agent can report to, upon exit 9 | const server = http.createServer(function (req, res) { 10 | t.equal(req.url, '/api/v1/beacon', 'agent reported before shutting down'); 11 | t.equal(req.method, 'POST'); 12 | t.equal(req.headers.host, 'localhost:9000'); 13 | res.writeHead(200, {'Content-Type': 'text/plain'}); 14 | res.end('carry on my wayward son!'); 15 | }).listen(9000) 16 | 17 | // bring up the demo server, then close it 18 | var env = Object.create( process.env ); 19 | env.DEBUG = 'snyk*'; 20 | env.flushBeforeExit = 'yes'; 21 | env.runtimeAgentPort = 9000; 22 | const demoApp = spawn('node', ['demo/justrequireagent.js'], {env: env}); 23 | 24 | // these snippets are nice for debugging the test 25 | // but seem to affect the behaviour of tap :scream: 26 | // demoApp.stdout.on('data', function (data) { 27 | // var str = data.toString() 28 | // var lines = str.split(/(\r?\n)/g); 29 | // console.log(lines.join("")); 30 | // }); 31 | // demoApp.stderr.on('data', function (data) { 32 | // var str = data.toString() 33 | // var lines = str.split(/(\r?\n)/g); 34 | // console.log(lines.join("")); 35 | // }); 36 | 37 | demoApp.on('close', function (code) { 38 | server.close(); 39 | }); 40 | }); 41 | 42 | test('allow turning flushBeforeExit off', t => { 43 | // small server the agent can report to, upon exit 44 | const server = http.createServer(function (req, res) { 45 | t.fail('agent should not have reported'); 46 | res.writeHead(200, {'Content-Type': 'text/plain'}); 47 | res.end('shame on you!'); 48 | }).listen(9001) 49 | 50 | // bring up the demo server, then close it 51 | var env = Object.create( process.env ); 52 | env.DEBUG = 'snyk*'; 53 | env.flushBeforeExit = 'plz no'; 54 | env.runtimeAgentPort = 9001; 55 | const demoApp = spawn('node', ['demo/justrequireagent.js'], {env: env}); 56 | 57 | demoApp.on('close', function (code) { 58 | t.end(); 59 | server.close(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/snapshot.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const test = require('tap').test; 3 | const nock = require('nock'); 4 | const sinon = require('sinon'); 5 | const path = require('path'); 6 | const needle = require('needle'); 7 | 8 | const config = require('../lib/config'); 9 | const snapshotReader = require('../lib/snapshot/reader'); 10 | 11 | config.initConfig({projectId: 'whatever'}); 12 | 13 | test('snapshot reader defaults to repo snapshot when bundled is missing', async (t) => { 14 | const repoSnapshotStub = [{ 15 | "functionId": { 16 | "className": null, 17 | "filePath": "mime.js", 18 | "functionName": "Mime.prototype.lookup" 19 | }, 20 | "packageName": "mime", 21 | "version": ["<1.4.1"] 22 | }]; 23 | const stub = sinon.stub(fs, 'readFileSync'); 24 | stub.withArgs(config.functionPaths.repo.snapshot).returns(JSON.stringify(repoSnapshotStub)); 25 | 26 | snapshotReader.loadFromLocal(); 27 | const result = snapshotReader.getLatest(); 28 | 29 | t.deepEqual(Object.keys(result), ['mime']); 30 | t.deepEqual(Object.keys(result['mime']), ['mime.js']); 31 | stub.restore(); 32 | t.end(); 33 | }); 34 | 35 | function readerFallsBackToRepoSnapshotWhenBundledError(t, bundledResponse) { 36 | const repoSnapshotStub = [{ 37 | "functionId": { 38 | "className": null, 39 | "filePath": "mime.js", 40 | "functionName": "Mime.prototype.lookup" 41 | }, 42 | "packageName": "mime", 43 | "version": ["<1.4.1"] 44 | }]; 45 | 46 | config.functionPaths.bundle.snapshot = path.join(__dirname, './fixtures/snapshots/bundled-snapshot.json'); //Just needs to point to an existing file 47 | const stub = sinon.stub(fs, 'readFileSync'); 48 | stub.withArgs(config.functionPaths.repo.snapshot).returns(JSON.stringify(repoSnapshotStub)); 49 | stub.withArgs(config.functionPaths.bundle.snapshot).returns(bundledResponse); 50 | 51 | const existsStub = sinon.stub(fs, 'existsSync').returns(true); 52 | 53 | snapshotReader.loadFromLocal(); 54 | const result = snapshotReader.getLatest(); 55 | 56 | t.deepEqual(Object.keys(result), ['mime']); 57 | t.deepEqual(Object.keys(result['mime']), ['mime.js']); 58 | 59 | stub.restore(); 60 | existsStub.restore(); 61 | } 62 | 63 | test('snapshot reader falls back to repo snapshot when bundled errors', async (t) => { 64 | readerFallsBackToRepoSnapshotWhenBundledError(t, ''); //Empty bundle 65 | readerFallsBackToRepoSnapshotWhenBundledError(t, 'Not a valid json'); //Invalid json 66 | readerFallsBackToRepoSnapshotWhenBundledError(t, '{t]}'); //Invalid json 67 | t.end(); 68 | }); 69 | 70 | test('snapshot reader favours bundled snapshot when possible', async (t) => { 71 | const bundleSnapshotStub = [{ 72 | "functionId": { 73 | "className": null, 74 | "filePath": "bundle.js", 75 | "functionName": "bundle.prototype.lookup" 76 | }, 77 | "packageName": "bundle", 78 | "version": ["<1.4.1"] 79 | }]; 80 | 81 | config.functionPaths.bundle.snapshot = path.join(__dirname, './fixtures/snapshots/bundled-snapshot.json'); //Just needs to point to an existing file 82 | const stub = sinon.stub(fs, 'readFileSync'); 83 | stub.withArgs(config.functionPaths.bundle.snapshot).returns(JSON.stringify(bundleSnapshotStub)); 84 | stub.withArgs(config.functionPaths.bundle.date).returns('Thu, 06 Dec 2018 14:02:33 GMT'); 85 | const existsStub = sinon.stub(fs, 'existsSync').returns(true); 86 | 87 | snapshotReader.loadFromLocal(); 88 | const result = snapshotReader.getLatest(); 89 | 90 | t.deepEqual(Object.keys(result), ['bundle']); 91 | t.deepEqual(Object.keys(result['bundle']), ['bundle.js']); 92 | 93 | stub.restore(); 94 | existsStub.restore(); 95 | t.end(); 96 | }); 97 | 98 | test('reader loading snapshot from upstream', async (t) => { 99 | nock('https://homebase.snyk.io') 100 | .get('/api/v2/snapshot/whatever/node') 101 | .reply(200, []); 102 | nock('https://homebase.snyk.io') 103 | .get('/api/v2/snapshot/whatever/node') 104 | .reply(200, []); 105 | 106 | const needleSpy = sinon.spy(needle, 'request'); 107 | 108 | snapshotReader.loadFromUpstream(); 109 | t.equal(needleSpy.args[0][0], 'get', 'snapshots retrieved with get'); 110 | t.equal(needleSpy.args[0][1], 'https://homebase.snyk.io/api/v2/snapshot/whatever/node', 'url is correct'); 111 | const expectedRequestOptions = { 112 | json: true, 113 | rejectUnauthorized: true, 114 | headers: {"If-Modified-Since": "Thu, 06 Dec 2018 14:02:33 GMT"}, 115 | }; 116 | t.deepEqual(needleSpy.args[0][3], expectedRequestOptions, 'request options are correct'); 117 | 118 | config['allowUnknownCA'] = true; 119 | snapshotReader.loadFromUpstream(); 120 | expectedRequestOptions.rejectUnauthorized = false; 121 | t.deepEqual(needleSpy.args[1][3], expectedRequestOptions, 'request options are correct'); 122 | 123 | t.ok(nock.isDone(), 'snapshot requests made'); 124 | nock.cleanAll(); 125 | }); 126 | -------------------------------------------------------------------------------- /test/transmitter.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const proxyquire = require('proxyquire'); 3 | const nock = require('nock'); 4 | const needle = require('needle'); 5 | 6 | const sinon = require('sinon'); 7 | const spy = sinon.spy(); 8 | const debugMock = (loggerType) => (msg) => {spy(msg);}; 9 | const state = require('../lib/state'); 10 | const config = require('../lib/config'); 11 | config.initConfig({projectId: 'some-project-id'}); 12 | const transmitter = proxyquire('../lib/transmitter', {'debug': debugMock}); 13 | 14 | test('Transmitter transmits 0 events for no events', async function (t) { 15 | nock('http://host') 16 | .post('/method') 17 | .reply(200, {}); 18 | nock('http://host') 19 | .post('/method') 20 | .reply(200, {}); 21 | 22 | spy.resetHistory(); 23 | const needleSpy = sinon.spy(needle, 'request'); 24 | 25 | await transmitter.transmitEvents('http://host/method', 'some-project-id', 'some-agent-id'); 26 | t.equal(needleSpy.args[0][0], 'post', 'beacons are being posted'); 27 | t.equal(needleSpy.args[0][1], 'http://host/method', 'url is correct'); 28 | t.ok('agentId' in needleSpy.args[0][2], 'agent ID is transmitted'); 29 | t.equal(needleSpy.args[0][2]['agentId'], 'some-agent-id', 'agent ID is correct'); 30 | t.ok('projectId' in needleSpy.args[0][2], 'project ID is transmitted'); 31 | t.equal(needleSpy.args[0][2]['projectId'], 'some-project-id', 'project ID is correct'); 32 | t.deepEqual(needleSpy.args[0][3], {json: true, rejectUnauthorized: true}, 'request options are correct'); 33 | 34 | config['allowUnknownCA'] = true; 35 | await transmitter.transmitEvents('http://host/method', 'some-project-id', 'some-agent-id'); 36 | t.deepEqual(needleSpy.args[1][3], {json: true, rejectUnauthorized: false}, 'request options are correct'); 37 | 38 | t.ok(nock.isDone(), 'two transmissions sent'); 39 | 40 | nock.cleanAll(); 41 | }); 42 | 43 | test('Trasmitter prints success on transmitted events', async function(t) { 44 | nock('http://host') 45 | .post('/method') 46 | .reply(200, {}); 47 | 48 | state.addEvent({foo: 'bar'}); 49 | spy.resetHistory(); 50 | 51 | await transmitter.transmitEvents('http://host/method', 'some-project-id', 'some-agent-id') 52 | .then(() => { 53 | const calls = spy.getCalls(); 54 | t.equal(calls[0].args[0], 'agent:some-agent-id transmitting 1 events to http://host/method with project ID some-project-id.', 'printing count of transmitted events'); 55 | t.equal(calls[1].args[0], 'Successfully transmitted events.', 'printing count of transmitted events'); 56 | t.end(); 57 | nock.cleanAll(); 58 | }); 59 | }); 60 | 61 | test('Transmitter prints errors on non-OK http responses', async function(t) { 62 | nock('http://host') 63 | .post('/method') 64 | .reply(404, (uri, requestBody) => {}); 65 | 66 | state.addEvent({foo: 'bar'}); 67 | spy.resetHistory(); 68 | 69 | await transmitter.transmitEvents('http://host/method', 'some-project-id', 'some-agent-id') 70 | .then(() => { 71 | const calls = spy.getCalls(); 72 | t.equal(calls[0].args[0], 'agent:some-agent-id transmitting 1 events to http://host/method with project ID some-project-id.', 'printing count of transmitted events'); 73 | t.equal(calls[1].args[0], 'Unexpected response for events transmission: 404 : {"type":"Buffer","data":[]}', 'printing unexpected http responses'); 74 | t.end(); 75 | nock.cleanAll(); 76 | }); 77 | }); 78 | 79 | test('Transmitter prints errors on errors', async function(t) { 80 | nock('http://host') 81 | .post('/method') 82 | .reply(404, (uri, requestBody) => {throw new Error('network is down!');}); 83 | 84 | state.addEvent({foo: 'bar'}); 85 | spy.resetHistory(); 86 | 87 | await transmitter.transmitEvents('http://host/method', 'some-project-id', 'some-agent-id') 88 | .then(() => { 89 | const calls = spy.getCalls(); 90 | t.equal(calls[0].args[0], 'agent:some-agent-id transmitting 1 events to http://host/method with project ID some-project-id.', 'printing count of transmitted events'); 91 | t.equal(calls[1].args[0], 'Error transmitting events: Error: network is down!', 'printing errors from needle'); 92 | t.end(); 93 | nock.cleanAll(); 94 | }); 95 | }); 96 | --------------------------------------------------------------------------------