Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scan for vulnerable JS Libraries #2372

Merged
merged 62 commits into from
Sep 29, 2017
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b6551af
Feat: Vulnerable JS libraries audit/gatherer
tkadlec May 26, 2017
59f4ee9
Feat: Adding JS Vulnerability check to default config
tkadlec May 26, 2017
24b8461
Merge branch 'master' into js-vulnerabilities
tkadlec May 26, 2017
86ac495
Add identification to snyk request so Snyk can better monitor if some…
tkadlec May 26, 2017
9ebc72b
Chore: Adjusting description to be more clear
tkadlec May 26, 2017
ac9cc34
Fix: body constructed using object
tkadlec May 26, 2017
f0fd224
Cleaner vuln filtering
tkadlec May 26, 2017
56cad31
clean audit return object
tkadlec May 26, 2017
26af245
Pass full list of libraries detected in extendedInfo
tkadlec May 26, 2017
5d63089
Wording tweak for audit
tkadlec May 31, 2017
0c700b2
Removing npmMapper as npm info is now in library detector
tkadlec May 31, 2017
0a23ee2
Feat: Vulnerable libs now look at a local snapshot of the Snyk DB ins…
tkadlec Jun 15, 2017
c398a92
Adding local snapshot of Snyk JSON for testing
tkadlec Jun 26, 2017
77c34f8
removing unnecessary console.log
tkadlec Jun 26, 2017
118417b
Merge branch 'master' into js-vulnerabilities
tkadlec Jun 26, 2017
d7fdc16
Pull library detector from npm and update audit text
tkadlec Jun 26, 2017
a7038dc
Chore: fixing typo
tkadlec Jun 26, 2017
26b59be
Merge pull request #1 from GoogleChrome/master
tkadlec Jun 26, 2017
f27b75e
Merge branch 'master' into js-vulnerabilities
tkadlec Jun 26, 2017
ce6bd00
Fix: Bringing full Snyk URL back
tkadlec Jun 26, 2017
3bd16ef
Chore: Linting error fixes
tkadlec Jun 26, 2017
c36914f
Merge remote-tracking branch 'upstream/master'
tkadlec Jul 6, 2017
01d30ef
Cleanup based on feedback
tkadlec Jul 6, 2017
b0ac61a
Merge remote-tracking branch 'upstream/master' into js-vulnerabilities
tkadlec Jul 6, 2017
ea8be78
Chore: bump js-library-detector version:
tkadlec Aug 18, 2017
de78540
Merge pull request #2 from GoogleChrome/master
tkadlec Aug 24, 2017
c17d5fc
Merge branch 'master' of https://github.com/tkadlec/lighthouse
tkadlec Aug 24, 2017
f27ce59
Merge branch 'master' into js-vulnerabilities
tkadlec Aug 24, 2017
e55ac62
Adding failureDescription to vulnerable JS audit
tkadlec Aug 24, 2017
168aa50
Prettier formatting of JSVuln audit
tkadlec Aug 24, 2017
24f708d
Fixing exception when no JS libs are found, writing tests
tkadlec Aug 24, 2017
cd17774
Vulns grouped by library, with tests
tkadlec Aug 25, 2017
f24dc9d
Script for updating snyk snapshot
tkadlec Aug 25, 2017
eb53e66
better tests
tkadlec Aug 28, 2017
3fb21f8
Merge remote-tracking branch 'upstream/master' into js-vulnerabilities
tkadlec Aug 30, 2017
678b63b
Adding new fancy links to vuln table results
tkadlec Aug 30, 2017
eb81ef2
Merge branch 'js-vulnerabilities' of https://github.com/tkadlec/light…
tkadlec Aug 30, 2017
ae7947b
One additional test
tkadlec Aug 30, 2017
b44728c
Linting fixes. whoops.
tkadlec Aug 30, 2017
09b61a2
Minor change for string formatting, dict for severity
tkadlec Aug 31, 2017
ee1f494
Change comment on severity map dict
tkadlec Aug 31, 2017
4578cd7
Early exit of audit if no libs found
tkadlec Aug 31, 2017
0adf078
New, shorter licenses
tkadlec Aug 31, 2017
a080f7c
Adding local snapshot of Snyk JSON for testing
tkadlec Jun 26, 2017
2fcff67
removing unnecessary console.log
tkadlec Jun 26, 2017
a85d697
Merge branch 'master' into js-vulnerabilities
tkadlec Sep 19, 2017
1c1db60
Moving Snyk logic to audit, not gatherer
tkadlec Sep 22, 2017
21c0a2c
Merge remote-tracking branch 'upstream/master'
tkadlec Sep 22, 2017
d57b81a
Merge branch 'master' into js-vulnerabilities
tkadlec Sep 22, 2017
a8edaf5
Adding dangling commas
tkadlec Sep 22, 2017
c07833e
yarn.lock for js-lib-detector
paulirish Sep 22, 2017
d1a3756
rename gather
paulirish Sep 22, 2017
cf3d743
rename gather file
paulirish Sep 22, 2017
250dbea
Merge remote-tracking branch 'upstream/master'
tkadlec Sep 29, 2017
90f40f9
Merge branch 'master' into js-vulnerabilities
tkadlec Sep 29, 2017
507588d
Merge branch 'js-vulnerabilities' of https://github.com/tkadlec/light…
tkadlec Sep 29, 2017
abb3246
New line at end of snapshot shell script
tkadlec Sep 29, 2017
b3e182e
rename mostSevere to highestSeverity
tkadlec Sep 29, 2017
5e45f33
More defined param comments, numericSeverity assigned earlier
tkadlec Sep 29, 2017
ab8ff76
renaming and moving snyk snapshot
tkadlec Sep 29, 2017
c0569e5
end to end test
tkadlec Sep 29, 2017
88ff656
add last JSDoc types
brendankenny Sep 29, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* @license
* Copyright 2017 Google Inc. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have short license headers now... copy one from one of the other files?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All set.

*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @fileoverview Audits a page to make sure there are no JS libraries with
* known vulnerabilities being used.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention the db is provided by snyk.io and checked in locally as third-party/snyk-snapshot.json.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GTG

*/

'use strict';

const Audit = require('../audit');

class NoVulnerableLibrariesAudit extends Audit {

/**
* @return {!AuditMeta}
*/
static get meta() {
return {
category: 'Security',
name: 'no-vulnerable-libraries',
description: 'Avoids front-end JavaScript libraries'
Copy link
Member

@rviscomi rviscomi Aug 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also include failureDescription. Eg "Includes known vulnerabilities"

I get the following error: Runtime error encountered: Error: dobetterweb/no-vulnerable-libraries has no failureDescription and should.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

+ ' with known security vulnerabilities',
helpText: 'Some third-party scripts may contain known security vulnerabilities ' +
' that are easily identified and exploited by attackers.',
requiredArtifacts: ['JSVulnerableLibraries']
};
}

/**
* @param {!Artifacts} artifacts
* @return {!AuditResult}
*/
static audit(artifacts) {
const libraries = artifacts.JSVulnerableLibraries.libraries;

const finalVulns = Object.assign(...libraries.filter(obj => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im getting an exception on this line
image

image

its equivalent to

x = Object.assign(...[])

I got this when running against example.com

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found it, fixed it, and added a test. :)

return obj.vulns;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also the formatting made this a little hard to follow
here's what prettier gave me for the same source, which i think is clearer:

    const finalVulns = Object.assign(
      ...libraries
        .filter(obj => {
          return obj.vulns;
        })
        .map(record => {
          const libVulns = [];
          for (const i in record.vulns) {
            if (Object.hasOwnProperty.call(record.vulns, i)) {
              libVulns.push(record.vulns[i]);
            }
          }
          return libVulns;
        })
    );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So Prettier was new to me. Preettttty cool stuff.

}).map(record => {
const libVulns = [];
for (const i in record.vulns) {
if (Object.hasOwnProperty.call(record.vulns, i)) {
libVulns.push(record.vulns[i]);
Copy link
Member

@rviscomi rviscomi Aug 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think markdown is supported here, so could you make the Snyk URLs clickable? Also, is it possible to see the vulnerability itself in the results?

For example:

Library Vulnerability Severity More Info
jQuery@1.12.4 Cross Site Scripting (XSS) medium https://snyk.io/vuln/npm:jquery:20150627

Here's how it appears now:

image

I also reordered the table so the lib is first but I nitpick.

https://techcrunch.com/ used for the audit. Full results: https://googlechrome.github.io/lighthouse/viewer/?gist=81ecfb452d3e13104d0816e16a1dde1e

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So as far as I can see, there's no (current) way to get markdown rendered in a table.

@paulirish, if I'm right, I'm happy to add it, but not sure if you would prefer that happens in a separate PR for tidiness.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No generic markdown support but there's a url type in the report renderer that should be easy to add clickable support for :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you ok with me doing that on this PR? Just didn't know if I should break it out separately for any reason.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be super useful elsewhere too and is fairly standalone so might be easier to review in isolation 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #3154

}
}
return libVulns;
}));

let displayValue = '';
if (finalVulns.length > 1) {
displayValue = `${finalVulns.length} vulnerabilities detected.`;
} else if (finalVulns.length === 1) {
displayValue = `${finalVulns.length} vulnerability was detected.`;
}

const headings = [
{key: 'url', itemType: 'text', text: 'Details'},
Copy link
Member

@paulirish paulirish Aug 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We think the output could be slightly different, though admittedly it does require some changes on the synk side, but I think it's worth it.

Basically, instead of this:
image
(live version)

We get:

Library version Vulnerability Count Highest Severity
jQuery@2.14 2 Medium
AngularJS@1.4.9 8 High

And clicking through to the jquery page for example, is showing the details for that library, and with the affected version's vulnerabilities highlighted... The most basic version being something like...

image

Hows that sound?
With that sorted I think we'll be in the clear to land this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this differs slightly from @rviscomi's suggestion in that he wanted to see the vulnerability type directly in the report, whereas this just displays a count. Just want to confirm we're ok with no type before I implement....

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I defer to Paul and the LH maintainers ☺️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted above (#2372 (comment)), I don't see a way to make the links clickable with markdown in a table. Otherwise the formatting of the table has been updated.

{key: 'library', itemType: 'text', text: 'Library'},
{key: 'severity', itemType: 'text', text: 'Severity'}
];
const details = Audit.makeV2TableDetails(headings, finalVulns);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just removed the "V2":

static makeTableDetails(headings, results) {


return {
rawValue: finalVulns.length === 0,
displayValue,
extendedInfo: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extendedInfo is legacy in LH 2.0. What do you think about just returning the new detail object. Our JSON results are getting out of control :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wouldn't say it's "legacy", but its there only for folks consuming more details in the LHR, usually via the lighthouse module. not there for presentational purposes.

I think this sorta return would make sense:

extendedInfo: {
  value: {
    vulnerabilities: finalVulns
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, I think. :)

So the way I was using it, apparently it was just unnecessary as I was also passing details. @rviscomi suggested including a full list of detected libs here as well, so I'm thinking:

extendedInfo: {
  value: {
    js_libs: libraries,
    vulnerabilities: finalVulns
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All set. See 26af245

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulirish anything that's useful is also being surfaced in details.items. If module consumers are pulling lower level details themselves, they're probably having a heck of time figuring out which of these to use/trust:

screen shot 2017-05-26 at 8 41 23 pm

It's time to nip bloat in the bud. #2276

js_libs: libraries,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsLibs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GTG

vulnerabilities: finalVulns
},
details,
};
}

}

module.exports = NoVulnerableLibrariesAudit;
4 changes: 3 additions & 1 deletion lighthouse-core/config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = {
'dobetterweb/anchors-with-no-rel-noopener',
'dobetterweb/appcache',
'dobetterweb/domstats',
'dobetterweb/js-vulnerable-libraries',
'dobetterweb/optimized-images',
'dobetterweb/password-inputs-with-prevented-paste',
'dobetterweb/response-compression',
Expand Down Expand Up @@ -60,7 +61,6 @@ module.exports = {
'html-without-javascript',
]
}],

audits: [
'is-on-https',
'redirects-http',
Expand Down Expand Up @@ -137,6 +137,7 @@ module.exports = {
'dobetterweb/no-document-write',
'dobetterweb/no-mutation-events',
// 'dobetterweb/no-old-flexbox',
'dobetterweb/no-vulnerable-libraries',
'dobetterweb/no-websql',
'dobetterweb/notification-on-start',
'dobetterweb/password-inputs-can-be-pasted-into',
Expand Down Expand Up @@ -300,6 +301,7 @@ module.exports = {
{id: 'no-document-write', weight: 1},
{id: 'external-anchors-use-rel-noopener', weight: 1},
{id: 'geolocation-on-start', weight: 1},
{id: 'no-vulnerable-libraries', weight: 1},
{id: 'notification-on-start', weight: 1},
{id: 'deprecations', weight: 1},
{id: 'manifest-short-name-length', weight: 1},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* @license
* Copyright 2017 Google Inc. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same license header comment.

*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @fileoverview Gathers a list of libraries and
any known vulnerabilities they contain.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this fit on one line?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not, add leading * so it's formatted correctly

*/

/* global window */
/* global d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests */

'use strict';

const Gatherer = require('../gatherer');
const fs = require('fs');
const semver = require('semver');
const libDetectorSource = fs.readFileSync(
require.resolve('js-library-detector/library/libraries.js'), 'utf8');
// https://snyk.io/partners/api/v2/vulndb/clientside.json
const snykDB = JSON.parse(fs.readFileSync(
require.resolve('../../../../third-party/snyk-snapshot.json'), 'utf8'));

/**
* Obtains a list of an object contain any detected JS libraries
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obtains list of detected vulnerable JS libraries and their versions.

* and the versions they're using.
* @return {!Object}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think: {!Array<!{name: string, version: number, npmPkgName: string}>}

*/
/* istanbul ignore next */
/* eslint-disable camelcase */
function detectLibraries() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about detectVulnerableLibraries?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method only grabs the libraries. It has no idea about whether they're vulnerable yet. That happens later on in the gatherer.

const libraries = [];
for (const i in d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests) {
Copy link
Contributor

@ebidel ebidel Jun 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests in a comment? Does this name change based on the version of libraries.js? Need some info :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests is an array. Can you use .forEach instead of for in?

if (Object.hasOwnProperty.call(d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests, i)) {
try {
const result = d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests[i].test(window);
if (result === false) continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about instead, push if there's a result?

if (result) {
  libraries.push({...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GTG

libraries.push({
name: i,
version: result.version,
npmPkgName: d41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests[i].npm
});
} catch(e) {}
}
}
return libraries;
}
/* eslint-enable camelcase */

class JSVulnerableLibraries extends Gatherer {
/**
* @param {!Object} options
* @return {!Promise<!Array<!Object>>}
*/
afterPass(options) {
const expression = `(function () {
${libDetectorSource};
return (${detectLibraries.toString()}());
})()`;

return options.driver
.evaluateAsync(expression)
.then(libraries => {
// add vulns to raw libraries results
const vulns = [];
for (const i in libraries) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.forEach instead of for in

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GTG

if (snykDB.npm[libraries[i].npmPkgName]) {
const snykInfo = snykDB.npm[libraries[i].npmPkgName];
for (const j in snykInfo) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use .forEach instead of for in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GTG

if (semver.satisfies(libraries[i].version, snykInfo[j].semver.vulnerable[0])) {
// valid vulnerability
vulns.push({
severity: snykInfo[j].severity,
library: libraries[i].name + '@' + libraries[i].version,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: `${libraries[i].name}@${libraries[i].version}`,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GTG

url: 'https://snyk.io/vuln/' + snykInfo[j].id
});
}
}
libraries[i].vulns = vulns;
}
}
return {libraries};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need an object? Just return libraries and in the audit use const libraries = artifacts.JSVulnerableLibraries;?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GTG

})
.then(returnedValue => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need the last return.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GTG

return returnedValue;
});
}
}

module.exports = JSVulnerableLibraries;
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,15 @@
"gl-matrix": "2.3.2",
"handlebars": "4.0.5",
"jpeg-js": "0.1.2",
"js-library-detector": "^2.11.0",
"json-stringify-safe": "5.0.1",
"lighthouse-logger": "^1.0.0",
"marked": "0.3.6",
"metaviewport-parser": "0.0.1",
"mkdirp": "0.5.1",
"opn": "4.0.2",
"rimraf": "2.2.8",
"semver": "5.3.0",
"semver": "^5.3.0",
"speedline": "1.2.0",
"update-notifier": "^2.1.0",
"whatwg-url": "4.0.0",
Expand Down
1 change: 1 addition & 0 deletions third-party/snyk-snapshot.json

Large diffs are not rendered by default.

Loading