dependabot-bun 0.296.2 → 0.296.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/.eslintrc +11 -0
- data/helpers/README.md +29 -0
- data/helpers/build +26 -0
- data/helpers/jest.config.js +5 -0
- data/helpers/lib/npm/conflicting-dependency-parser.js +78 -0
- data/helpers/lib/npm/index.js +9 -0
- data/helpers/lib/npm/vulnerability-auditor.js +291 -0
- data/helpers/lib/npm6/helpers.js +25 -0
- data/helpers/lib/npm6/index.js +9 -0
- data/helpers/lib/npm6/peer-dependency-checker.js +111 -0
- data/helpers/lib/npm6/remove-dependencies-from-lockfile.js +22 -0
- data/helpers/lib/npm6/subdependency-updater.js +78 -0
- data/helpers/lib/npm6/updater.js +199 -0
- data/helpers/lib/pnpm/index.js +5 -0
- data/helpers/lib/pnpm/lockfile-parser.js +82 -0
- data/helpers/lib/yarn/conflicting-dependency-parser.js +176 -0
- data/helpers/lib/yarn/fix-duplicates.js +80 -0
- data/helpers/lib/yarn/helpers.js +54 -0
- data/helpers/lib/yarn/index.js +14 -0
- data/helpers/lib/yarn/lockfile-parser.js +21 -0
- data/helpers/lib/yarn/peer-dependency-checker.js +132 -0
- data/helpers/lib/yarn/replace-lockfile-declaration.js +57 -0
- data/helpers/lib/yarn/subdependency-updater.js +83 -0
- data/helpers/lib/yarn/updater.js +209 -0
- data/helpers/package-lock.json +28519 -0
- data/helpers/package.json +29 -0
- data/helpers/patches/npm++pacote+9.5.12.patch +14 -0
- data/helpers/run.js +30 -0
- data/helpers/test/npm6/conflicting-dependency-parser.test.js +66 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json +591 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package-lock.json +188 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package-lock.json +27 -0
- data/helpers/test/npm6/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
- data/helpers/test/npm6/fixtures/updater/original/package-lock.json +16 -0
- data/helpers/test/npm6/fixtures/updater/original/package.json +9 -0
- data/helpers/test/npm6/fixtures/updater/updated/package-lock.json +16 -0
- data/helpers/test/npm6/helpers.js +21 -0
- data/helpers/test/npm6/updater.test.js +30 -0
- data/helpers/test/pnpm/fixtures/parser/empty_version/pnpm-lock.yaml +72 -0
- data/helpers/test/pnpm/fixtures/parser/no_lockfile_change/pnpm-lock.yaml +2744 -0
- data/helpers/test/pnpm/fixtures/parser/only_dev_dependencies/pnpm-lock.yaml +16 -0
- data/helpers/test/pnpm/fixtures/parser/peer_disambiguation/pnpm-lock.yaml +855 -0
- data/helpers/test/pnpm/lockfile-parser.test.js +62 -0
- data/helpers/test/yarn/conflicting-dependency-parser.test.js +83 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/package.json +14 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/yarn.lock +496 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/package.json +14 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/dev-dependencies/yarn.lock +21 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/package.json +14 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/nested/yarn.lock +183 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/package.json +14 -0
- data/helpers/test/yarn/fixtures/conflicting-dependency-parser/simple/yarn.lock +21 -0
- data/helpers/test/yarn/fixtures/updater/illegal_character/package.json +8 -0
- data/helpers/test/yarn/fixtures/updater/illegal_character/yarn.lock +14 -0
- data/helpers/test/yarn/fixtures/updater/original/package.json +6 -0
- data/helpers/test/yarn/fixtures/updater/original/yarn.lock +11 -0
- data/helpers/test/yarn/fixtures/updater/updated/yarn.lock +12 -0
- data/helpers/test/yarn/fixtures/updater/with-version-comments/package.json +5 -0
- data/helpers/test/yarn/fixtures/updater/with-version-comments/yarn.lock +13 -0
- data/helpers/test/yarn/helpers.js +18 -0
- data/helpers/test/yarn/updater.test.js +117 -0
- data/lib/dependabot/bun/bun_package_manager.rb +47 -0
- data/lib/dependabot/bun/constraint_helper.rb +359 -0
- data/lib/dependabot/bun/dependency_files_filterer.rb +157 -0
- data/lib/dependabot/bun/file_fetcher/path_dependency_builder.rb +184 -0
- data/lib/dependabot/bun/file_fetcher.rb +402 -0
- data/lib/dependabot/bun/file_parser/bun_lock.rb +140 -0
- data/lib/dependabot/bun/file_parser/lockfile_parser.rb +105 -0
- data/lib/dependabot/bun/file_parser.rb +477 -0
- data/lib/dependabot/bun/file_updater/bun_lockfile_updater.rb +144 -0
- data/lib/dependabot/bun/file_updater/npmrc_builder.rb +256 -0
- data/lib/dependabot/bun/file_updater/package_json_preparer.rb +88 -0
- data/lib/dependabot/bun/file_updater/package_json_updater.rb +378 -0
- data/lib/dependabot/bun/file_updater.rb +203 -0
- data/lib/dependabot/bun/helpers.rb +93 -0
- data/lib/dependabot/bun/language.rb +45 -0
- data/lib/dependabot/bun/metadata_finder.rb +214 -0
- data/lib/dependabot/bun/native_helpers.rb +19 -0
- data/lib/dependabot/bun/package_manager.rb +280 -0
- data/lib/dependabot/bun/package_name.rb +118 -0
- data/lib/dependabot/bun/pnpm_package_manager.rb +55 -0
- data/lib/dependabot/bun/registry_helper.rb +188 -0
- data/lib/dependabot/bun/registry_parser.rb +93 -0
- data/lib/dependabot/bun/requirement.rb +146 -0
- data/lib/dependabot/bun/sub_dependency_files_filterer.rb +82 -0
- data/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb +59 -0
- data/lib/dependabot/bun/update_checker/dependency_files_builder.rb +79 -0
- data/lib/dependabot/bun/update_checker/latest_version_finder.rb +448 -0
- data/lib/dependabot/bun/update_checker/library_detector.rb +76 -0
- data/lib/dependabot/bun/update_checker/registry_finder.rb +279 -0
- data/lib/dependabot/bun/update_checker/requirements_updater.rb +206 -0
- data/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb +154 -0
- data/lib/dependabot/bun/update_checker/version_resolver.rb +583 -0
- data/lib/dependabot/bun/update_checker/vulnerability_auditor.rb +164 -0
- data/lib/dependabot/bun/update_checker.rb +455 -0
- data/lib/dependabot/bun/version.rb +138 -0
- data/lib/dependabot/bun/version_selector.rb +61 -0
- data/lib/dependabot/bun.rb +337 -35
- metadata +108 -65
- data/lib/dependabot/javascript/bun/file_fetcher.rb +0 -77
- data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +0 -156
- data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +0 -55
- data/lib/dependabot/javascript/bun/file_parser.rb +0 -74
- data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +0 -138
- data/lib/dependabot/javascript/bun/file_updater.rb +0 -75
- data/lib/dependabot/javascript/bun/helpers.rb +0 -72
- data/lib/dependabot/javascript/bun/package_manager.rb +0 -48
- data/lib/dependabot/javascript/bun/requirement.rb +0 -11
- data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +0 -64
- data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +0 -47
- data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +0 -450
- data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +0 -76
- data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +0 -203
- data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +0 -144
- data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +0 -525
- data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +0 -165
- data/lib/dependabot/javascript/bun/update_checker.rb +0 -440
- data/lib/dependabot/javascript/bun/version.rb +0 -11
- data/lib/dependabot/javascript/shared/constraint_helper.rb +0 -359
- data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +0 -164
- data/lib/dependabot/javascript/shared/file_fetcher.rb +0 -283
- data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +0 -106
- data/lib/dependabot/javascript/shared/file_parser.rb +0 -454
- data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +0 -394
- data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +0 -87
- data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +0 -376
- data/lib/dependabot/javascript/shared/file_updater.rb +0 -179
- data/lib/dependabot/javascript/shared/language.rb +0 -45
- data/lib/dependabot/javascript/shared/metadata_finder.rb +0 -209
- data/lib/dependabot/javascript/shared/native_helpers.rb +0 -21
- data/lib/dependabot/javascript/shared/package_manager_detector.rb +0 -72
- data/lib/dependabot/javascript/shared/package_name.rb +0 -118
- data/lib/dependabot/javascript/shared/registry_helper.rb +0 -190
- data/lib/dependabot/javascript/shared/registry_parser.rb +0 -93
- data/lib/dependabot/javascript/shared/requirement.rb +0 -144
- data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +0 -79
- data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +0 -87
- data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +0 -358
- data/lib/dependabot/javascript/shared/version.rb +0 -133
- data/lib/dependabot/javascript/shared/version_selector.rb +0 -60
- data/lib/dependabot/javascript.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02ce7a49ca717ce8cf12b669fc7caf8d2a685bc00cfe9f8d45771b1839b01abb
|
4
|
+
data.tar.gz: f56292f958d22f40a726b649000e2ccbc7d6f87bc869579f62e4d77c7ca458f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccff16321a7243441dc13cf525a69eb0bffc9413537fdd7d7131f185d366d0a8dfd1ec28980d2805740e8af4bb671615bf7ec7ba1ef0a415b96d8dcd2f9065c9
|
7
|
+
data.tar.gz: 86b243daf58eb0912265c8f04f9677173b5dd60f183bb6ea62186d31cb0862be133abdb855a55eab6153f90c2b5e5f865c77030b9847ea78af7df73a2287bf65
|
data/helpers/.eslintrc
ADDED
data/helpers/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Native JavaScript helpers
|
2
|
+
-------------------------
|
3
|
+
|
4
|
+
This directory contains helper functions for npm and yarn, natively written in
|
5
|
+
Javascript so that we can utilize the package managers internal APIs and other
|
6
|
+
native tooling for these ecosystems.
|
7
|
+
|
8
|
+
These helpers are called from the Ruby code via `run.js`, they are passed
|
9
|
+
arguments via stdin and return JSON data to stdout.
|
10
|
+
|
11
|
+
## Testing
|
12
|
+
|
13
|
+
When working on these helpers, it's convenient to write some high level tests in
|
14
|
+
JavaScript to make it easier to debug the code.
|
15
|
+
|
16
|
+
You can now run the tests from this directory by running:
|
17
|
+
|
18
|
+
```
|
19
|
+
yarn test path/to/test.js
|
20
|
+
```
|
21
|
+
|
22
|
+
### Debugging
|
23
|
+
|
24
|
+
In order to run an interactive debugger:
|
25
|
+
|
26
|
+
- `node --inspect-brk node_modules/.bin/jest --runInBand path/to/test/test.js`
|
27
|
+
- In Chrome, navigate to `chrome://inspect`
|
28
|
+
- Click `Open dedicated DevTools for Node`
|
29
|
+
- You'll now be able to interactively debug using the Chrome dev tools.
|
data/helpers/build
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
if [ -z "$DEPENDABOT_NATIVE_HELPERS_PATH" ]; then
|
6
|
+
echo "Unable to build, DEPENDABOT_NATIVE_HELPERS_PATH is not set"
|
7
|
+
exit 1
|
8
|
+
fi
|
9
|
+
|
10
|
+
install_dir="$DEPENDABOT_NATIVE_HELPERS_PATH/bun"
|
11
|
+
mkdir -p "$install_dir"
|
12
|
+
|
13
|
+
helpers_dir="$(dirname "${BASH_SOURCE[0]}")"
|
14
|
+
cp -r \
|
15
|
+
"$helpers_dir/lib" \
|
16
|
+
"$helpers_dir/test" \
|
17
|
+
"$helpers_dir/run.js" \
|
18
|
+
"$helpers_dir/.eslintrc" \
|
19
|
+
"$helpers_dir/jest.config.js" \
|
20
|
+
"$helpers_dir/package.json" \
|
21
|
+
"$helpers_dir/package-lock.json" \
|
22
|
+
"$helpers_dir/patches" \
|
23
|
+
"$install_dir"
|
24
|
+
|
25
|
+
cd "$install_dir"
|
26
|
+
npm ci --no-audit --fetch-timeout=600000 --fetch-retries=5 --no-dry-run --no-ignore-scripts
|
@@ -0,0 +1,78 @@
|
|
1
|
+
/* Conflicting dependency parser for npm
|
2
|
+
*
|
3
|
+
* Inputs:
|
4
|
+
* - directory containing a package.json and a yarn.lock
|
5
|
+
* - dependency name
|
6
|
+
* - target dependency version
|
7
|
+
*
|
8
|
+
* Outputs:
|
9
|
+
* - An array of objects with conflicting dependencies
|
10
|
+
*/
|
11
|
+
|
12
|
+
const Arborist = require("@npmcli/arborist");
|
13
|
+
const semver = require("semver");
|
14
|
+
|
15
|
+
async function findConflictingDependencies(directory, depName, targetVersion) {
|
16
|
+
const arb = new Arborist({
|
17
|
+
path: directory,
|
18
|
+
dryRun: true,
|
19
|
+
ignoreScripts: true,
|
20
|
+
});
|
21
|
+
|
22
|
+
return await arb.loadVirtual().then((tree) => {
|
23
|
+
const parents = [];
|
24
|
+
for (const node of tree.inventory.query("name", depName)) {
|
25
|
+
for (const edge of node.edgesIn) {
|
26
|
+
if (!semver.satisfies(targetVersion, edge.spec)) {
|
27
|
+
findTopLevelEdges(edge).forEach((topLevel) => {
|
28
|
+
explanation = buildExplanation(node, edge, topLevel);
|
29
|
+
|
30
|
+
parents.push({
|
31
|
+
explanation: explanation,
|
32
|
+
name: edge.from.name,
|
33
|
+
version: edge.from.version,
|
34
|
+
requirement: edge.spec,
|
35
|
+
});
|
36
|
+
});
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
return parents;
|
42
|
+
});
|
43
|
+
}
|
44
|
+
|
45
|
+
function buildExplanation(node, directEdge, topLevelEdge) {
|
46
|
+
if (directEdge.from === topLevelEdge.to) {
|
47
|
+
// The nodes parent is top-level
|
48
|
+
return `${directEdge.from.name}@${directEdge.from.version} requires ${directEdge.to.name}@${directEdge.spec}`;
|
49
|
+
} else if (topLevelEdge.to.edgesOut.has(directEdge.from.name)) {
|
50
|
+
// The nodes parent is a direct dependency of the top-level dependency
|
51
|
+
return (
|
52
|
+
`${topLevelEdge.to.name}@${topLevelEdge.to.version} requires ${directEdge.to.name}@${directEdge.spec} ` +
|
53
|
+
`via ${directEdge.from.name}@${directEdge.from.version}`
|
54
|
+
);
|
55
|
+
} else {
|
56
|
+
// The nodes parent is a transitive dependency of the top-level dependency
|
57
|
+
return (
|
58
|
+
`${topLevelEdge.to.name}@${topLevelEdge.to.version} requires ${directEdge.to.name}@${directEdge.spec} ` +
|
59
|
+
`via a transitive dependency on ${directEdge.from.name}@${directEdge.from.version}`
|
60
|
+
);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
function findTopLevelEdges(edge, parents = []) {
|
65
|
+
edge.from.edgesIn.forEach((parent) => {
|
66
|
+
if (parent.from.edgesIn.size === 0) {
|
67
|
+
if (!parents.includes(parent)) {
|
68
|
+
parents.push(parent);
|
69
|
+
}
|
70
|
+
} else {
|
71
|
+
findTopLevelEdges(parent, parents);
|
72
|
+
}
|
73
|
+
});
|
74
|
+
|
75
|
+
return parents;
|
76
|
+
}
|
77
|
+
|
78
|
+
module.exports = { findConflictingDependencies };
|
@@ -0,0 +1,9 @@
|
|
1
|
+
const conflictingDependencyParser = require("./conflicting-dependency-parser");
|
2
|
+
const vulnerabilityAuditor = require("./vulnerability-auditor");
|
3
|
+
|
4
|
+
module.exports = {
|
5
|
+
findConflictingDependencies:
|
6
|
+
conflictingDependencyParser.findConflictingDependencies,
|
7
|
+
vulnerabilityAuditor:
|
8
|
+
vulnerabilityAuditor.findVulnerableDependencies,
|
9
|
+
};
|
@@ -0,0 +1,291 @@
|
|
1
|
+
/* Vulnerability auditor for npm
|
2
|
+
*
|
3
|
+
* Inputs:
|
4
|
+
* - path to directory containing a package.json and a package-lock.json
|
5
|
+
* - array of security advisories to audit for,
|
6
|
+
* [
|
7
|
+
* { dependency_name: string, affected_versions: [string, ...] },
|
8
|
+
* ...
|
9
|
+
* ]
|
10
|
+
*
|
11
|
+
* Outputs:
|
12
|
+
* - object indicating whether a fix is available and how to achieve it,
|
13
|
+
* {
|
14
|
+
* dependency_name: string,
|
15
|
+
* current_version: string,
|
16
|
+
* target_version: string,
|
17
|
+
* fix_available: boolean | object,
|
18
|
+
* fix_updates: [
|
19
|
+
* {
|
20
|
+
* dependency_name: string,
|
21
|
+
* current_version: string,
|
22
|
+
* target_version: string
|
23
|
+
* },
|
24
|
+
* ...
|
25
|
+
* ]
|
26
|
+
* }
|
27
|
+
*/
|
28
|
+
|
29
|
+
const Arborist = require('@npmcli/arborist')
|
30
|
+
const nock = require('nock')
|
31
|
+
const { promisify } = require('util');
|
32
|
+
const exec = promisify(require('child_process').exec)
|
33
|
+
|
34
|
+
async function findVulnerableDependencies(directory, advisories) {
|
35
|
+
const npmConfig = await loadNpmConfig()
|
36
|
+
const caCerts = loadCACerts(npmConfig)
|
37
|
+
const registryOpts = extractRegistryOptions(npmConfig)
|
38
|
+
const registryCreds = loadNpmConfigCredentials(directory)
|
39
|
+
|
40
|
+
const arb = new Arborist({
|
41
|
+
path: directory,
|
42
|
+
auditRegistry: 'http://localhost:9999',
|
43
|
+
ca: caCerts,
|
44
|
+
force: true,
|
45
|
+
dryRun: true,
|
46
|
+
ignoreScripts: true,
|
47
|
+
...registryOpts,
|
48
|
+
...registryCreds,
|
49
|
+
})
|
50
|
+
|
51
|
+
const scope = nock('http://localhost:9999')
|
52
|
+
.persist()
|
53
|
+
.post('/-/npm/v1/security/advisories/bulk')
|
54
|
+
.reply(200, convertAdvisoriesToRegistryBulkFormat(advisories))
|
55
|
+
|
56
|
+
if (!nock.isActive()) {
|
57
|
+
nock.activate()
|
58
|
+
}
|
59
|
+
|
60
|
+
try {
|
61
|
+
const name = advisories[0].dependency_name
|
62
|
+
const response = {
|
63
|
+
dependency_name: name,
|
64
|
+
fix_updates: [],
|
65
|
+
top_level_ancestors: [],
|
66
|
+
}
|
67
|
+
const auditReport = await arb.audit()
|
68
|
+
if (!auditReport.has(name)) {
|
69
|
+
if (auditReport.tree.children.has(name)) {
|
70
|
+
response.current_version = auditReport.tree.children.get(name).version
|
71
|
+
}
|
72
|
+
response.fix_available = false
|
73
|
+
return response
|
74
|
+
}
|
75
|
+
const vuln = auditReport.get(name)
|
76
|
+
const version = [...vuln.nodes][0].version
|
77
|
+
const fixAvailable = vuln.fixAvailable
|
78
|
+
|
79
|
+
response.current_version = version
|
80
|
+
response.fix_available = Boolean(fixAvailable)
|
81
|
+
|
82
|
+
if (!Boolean(fixAvailable)) {
|
83
|
+
return response
|
84
|
+
}
|
85
|
+
|
86
|
+
const chains = buildDependencyChains(auditReport, name)
|
87
|
+
|
88
|
+
// In order for the vuln dependency in question to be considered fixable,
|
89
|
+
// all dependency chains originating from it must be fixable.
|
90
|
+
if (chains.some((chain) => !Boolean(chain.fixAvailable))) {
|
91
|
+
response.fix_available = false
|
92
|
+
return response
|
93
|
+
}
|
94
|
+
|
95
|
+
const groupedFixUpdateChains = groupBy(chains, (chain) => chain.nodes[0].pkgid)
|
96
|
+
let topLevelAncestors = new Set()
|
97
|
+
|
98
|
+
for (const group of groupedFixUpdateChains.values()) {
|
99
|
+
const fixUpdateNode = group[0].nodes[0]
|
100
|
+
const groupTopLevelAncestors = group.reduce((ancestor, chain) => {
|
101
|
+
const topLevelNode = chain.nodes[chain.nodes.length - 1]
|
102
|
+
return ancestor.add(topLevelNode.name)
|
103
|
+
}, new Set())
|
104
|
+
|
105
|
+
// Add group's top-level ancestors to the set of all top-level ancestors of
|
106
|
+
// the vuln dependency in question.
|
107
|
+
topLevelAncestors = new Set([...topLevelAncestors, ...groupTopLevelAncestors])
|
108
|
+
|
109
|
+
// If a chain consists of only one node, chain.nodes[0].name == chain.nodes[chain.nodes.length-1].name.
|
110
|
+
// In such cases, don't include the fix update node as an ancestor of itself.
|
111
|
+
const fixUpdateNodeTopLevelAncestors =
|
112
|
+
[...groupTopLevelAncestors].filter((nodeName) => nodeName !== fixUpdateNode.name).sort()
|
113
|
+
|
114
|
+
response.fix_updates.push({
|
115
|
+
dependency_name: fixUpdateNode.name,
|
116
|
+
current_version: fixUpdateNode.version,
|
117
|
+
top_level_ancestors: fixUpdateNodeTopLevelAncestors,
|
118
|
+
})
|
119
|
+
}
|
120
|
+
|
121
|
+
response.top_level_ancestors = [...topLevelAncestors].sort()
|
122
|
+
|
123
|
+
const fixTree = await arb.audit({
|
124
|
+
fix: true,
|
125
|
+
})
|
126
|
+
|
127
|
+
response.target_version = fixTree.children.get(name)?.version
|
128
|
+
|
129
|
+
for (const update of response.fix_updates) {
|
130
|
+
update.target_version =
|
131
|
+
fixTree.children.get(update.dependency_name)?.version
|
132
|
+
}
|
133
|
+
|
134
|
+
return response
|
135
|
+
} finally {
|
136
|
+
nock.cleanAll()
|
137
|
+
nock.restore()
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
function convertAdvisoriesToRegistryBulkFormat(advisories) {
|
142
|
+
return advisories.reduce((formattedAdvisories, advisory) => {
|
143
|
+
if (!formattedAdvisories[advisory.dependency_name]) {
|
144
|
+
formattedAdvisories[advisory.dependency_name] = []
|
145
|
+
}
|
146
|
+
let formattedVersions =
|
147
|
+
advisory.affected_versions.reduce((memo, version) => {
|
148
|
+
memo.push({
|
149
|
+
id: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER),
|
150
|
+
vulnerable_versions: version
|
151
|
+
})
|
152
|
+
return memo
|
153
|
+
}, [])
|
154
|
+
formattedAdvisories[advisory.dependency_name].push(...formattedVersions)
|
155
|
+
return formattedAdvisories
|
156
|
+
}, {})
|
157
|
+
}
|
158
|
+
|
159
|
+
/* Returns an array of dependency chains rooted in the named dependency,
|
160
|
+
* [
|
161
|
+
* {
|
162
|
+
* fixAvailable: true | false | object,
|
163
|
+
* nodes: [
|
164
|
+
* ArboristNode {
|
165
|
+
* name: 'foo',
|
166
|
+
* version: '1.0.0',
|
167
|
+
* ...
|
168
|
+
* },
|
169
|
+
* ...
|
170
|
+
* ]
|
171
|
+
* },
|
172
|
+
* ...
|
173
|
+
* ]
|
174
|
+
*
|
175
|
+
* The first node in each chain is the innermost dependency affected by the vuln;
|
176
|
+
* the `fixAvailable` field applies to this dependency. The last node in each
|
177
|
+
* chain is always a top-level dependency.
|
178
|
+
*/
|
179
|
+
function buildDependencyChains(auditReport, name) {
|
180
|
+
const helper = (node, chain, visited) => {
|
181
|
+
if (!node) {
|
182
|
+
return []
|
183
|
+
}
|
184
|
+
if (visited.has(node.name)) {
|
185
|
+
// We've already seen this node; end path.
|
186
|
+
return []
|
187
|
+
}
|
188
|
+
if (auditReport.has(node.name)) {
|
189
|
+
const vuln = auditReport.get(node.name)
|
190
|
+
if (vuln.isVulnerable(node)) {
|
191
|
+
return [{ fixAvailable: vuln.fixAvailable, nodes: [node, ...chain.nodes] }]
|
192
|
+
} else if (node.name == name) {
|
193
|
+
// This is a non-vulnerable version of the advisory dependency; end path.
|
194
|
+
return []
|
195
|
+
}
|
196
|
+
}
|
197
|
+
if (!node.edgesOut.size) {
|
198
|
+
// This is a leaf node that is unaffected by the vuln; end path.
|
199
|
+
return []
|
200
|
+
}
|
201
|
+
return [...node.edgesOut.values()].reduce((chains, { to }) => {
|
202
|
+
// Only prepend current node to chain/visited if it's not the project root.
|
203
|
+
const newChain = node.isProjectRoot ? chain : { nodes: [node, ...chain.nodes] }
|
204
|
+
const newVisited = node.isProjectRoot ? visited : new Set([node.name, ...visited])
|
205
|
+
return chains.concat(helper(to, newChain, newVisited))
|
206
|
+
}, [])
|
207
|
+
}
|
208
|
+
return helper(auditReport.tree, { nodes: [] }, new Set())
|
209
|
+
}
|
210
|
+
|
211
|
+
function groupBy(elems, fn) {
|
212
|
+
const groups = new Map()
|
213
|
+
for (const [index, elem] of [...elems].entries()) {
|
214
|
+
const key = fn(elem, index, elems)
|
215
|
+
groups.set(key, (groups.get(key) || []).concat([elem]))
|
216
|
+
}
|
217
|
+
return groups
|
218
|
+
}
|
219
|
+
|
220
|
+
async function loadNpmConfig() {
|
221
|
+
const configOutput = await exec('npm config ls --json')
|
222
|
+
return JSON.parse(configOutput.stdout)
|
223
|
+
}
|
224
|
+
|
225
|
+
function extractRegistryOptions(npmConfig) {
|
226
|
+
const opts = []
|
227
|
+
for (const [key, value] of Object.entries(npmConfig)) {
|
228
|
+
if (key == "registry" || key.endsWith(":registry")) {
|
229
|
+
opts.push([key, value])
|
230
|
+
}
|
231
|
+
}
|
232
|
+
return Object.fromEntries(opts)
|
233
|
+
}
|
234
|
+
|
235
|
+
// loadNpmConfig doesn't return registry credentials so we need to manually extract them. If available,
|
236
|
+
// Dependabot will have written them to the project's .npmrc file.
|
237
|
+
const ini = require('ini')
|
238
|
+
const path = require('path')
|
239
|
+
|
240
|
+
const credKeys = ['token', '_authToken', '_auth']
|
241
|
+
|
242
|
+
function loadNpmConfigCredentials(projectDir) {
|
243
|
+
const projectNpmrc = maybeReadFile(path.join(projectDir, '.npmrc'))
|
244
|
+
if (!projectNpmrc) {
|
245
|
+
return {}
|
246
|
+
}
|
247
|
+
|
248
|
+
const credentials = []
|
249
|
+
const config = ini.parse(projectNpmrc)
|
250
|
+
for (const [key, value] of Object.entries(config)) {
|
251
|
+
if (credKeys.includes(key) || credKeys.some((credKey) => key.endsWith(':' + credKey))) {
|
252
|
+
credentials.push([key, value])
|
253
|
+
}
|
254
|
+
}
|
255
|
+
return Object.fromEntries(credentials)
|
256
|
+
}
|
257
|
+
|
258
|
+
// sourced from npm's cli/lib/utils/config/definitions.js for reading certs from the cafile option
|
259
|
+
const fs = require('fs')
|
260
|
+
const maybeReadFile = file => {
|
261
|
+
try {
|
262
|
+
return fs.readFileSync(file, 'utf8')
|
263
|
+
} catch (er) {
|
264
|
+
if (er.code !== 'ENOENT') {
|
265
|
+
throw er
|
266
|
+
}
|
267
|
+
return null
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
function loadCACerts(npmConfig) {
|
272
|
+
if (npmConfig.ca) {
|
273
|
+
return npmConfig.ca
|
274
|
+
}
|
275
|
+
|
276
|
+
if (!npmConfig.cafile) {
|
277
|
+
return
|
278
|
+
}
|
279
|
+
|
280
|
+
const raw = maybeReadFile(npmConfig.cafile)
|
281
|
+
if (!raw) {
|
282
|
+
return
|
283
|
+
}
|
284
|
+
|
285
|
+
const delim = '-----END CERTIFICATE-----'
|
286
|
+
return raw.replace(/\r\n/g, '\n').split(delim)
|
287
|
+
.filter(section => section.trim())
|
288
|
+
.map(section => section.trimStart() + delim)
|
289
|
+
}
|
290
|
+
|
291
|
+
module.exports = { findVulnerableDependencies }
|
@@ -0,0 +1,25 @@
|
|
1
|
+
function runAsync(obj, method, args) {
|
2
|
+
return new Promise((resolve, reject) => {
|
3
|
+
const cb = (err, ...returnValues) => {
|
4
|
+
if (err) {
|
5
|
+
reject(err);
|
6
|
+
} else {
|
7
|
+
resolve(returnValues);
|
8
|
+
}
|
9
|
+
};
|
10
|
+
method.apply(obj, [...args, cb]);
|
11
|
+
});
|
12
|
+
}
|
13
|
+
|
14
|
+
function muteStderr() {
|
15
|
+
const original = process.stderr.write;
|
16
|
+
process.stderr.write = () => {};
|
17
|
+
return () => {
|
18
|
+
process.stderr.write = original;
|
19
|
+
};
|
20
|
+
}
|
21
|
+
|
22
|
+
module.exports = {
|
23
|
+
runAsync,
|
24
|
+
muteStderr,
|
25
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
const updater = require("./updater");
|
2
|
+
const peerDependencyChecker = require("./peer-dependency-checker");
|
3
|
+
const subdependencyUpdater = require("./subdependency-updater");
|
4
|
+
|
5
|
+
module.exports = {
|
6
|
+
update: updater.updateDependencyFiles,
|
7
|
+
updateSubdependency: subdependencyUpdater.updateDependencyFile,
|
8
|
+
checkPeerDependencies: peerDependencyChecker.checkPeerDependencies,
|
9
|
+
};
|
@@ -0,0 +1,111 @@
|
|
1
|
+
/* PEER DEPENDENCY CHECKER
|
2
|
+
*
|
3
|
+
* Inputs:
|
4
|
+
* - directory containing a package.json and a yarn.lock
|
5
|
+
* - dependency name
|
6
|
+
* - new dependency version
|
7
|
+
* - requirements for this dependency
|
8
|
+
*
|
9
|
+
* Outputs:
|
10
|
+
* - successful completion, or an error if there are peer dependency warnings
|
11
|
+
*/
|
12
|
+
|
13
|
+
const npm = require("npm");
|
14
|
+
const installer = require("npm/lib/install");
|
15
|
+
const { muteStderr, runAsync } = require("./helpers.js");
|
16
|
+
|
17
|
+
function installArgsWithVersion(depName, desiredVersion, reqs) {
|
18
|
+
const source = (reqs.find((req) => req.source) || {}).source;
|
19
|
+
|
20
|
+
if (source && source.type === "git") {
|
21
|
+
return [`${depName}@${source.url}#${desiredVersion}`];
|
22
|
+
} else {
|
23
|
+
return [`${depName}@${desiredVersion}`];
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
async function checkPeerDependencies(
|
28
|
+
directory,
|
29
|
+
depName,
|
30
|
+
desiredVersion,
|
31
|
+
requirements,
|
32
|
+
topLevelDependencies
|
33
|
+
) {
|
34
|
+
// `force: true` ignores checks for platform (os, cpu) and engines
|
35
|
+
// in npm/lib/install/validate-args.js
|
36
|
+
// Platform is checked and raised from (EBADPLATFORM):
|
37
|
+
// https://github.com/npm/npm-install-checks
|
38
|
+
//
|
39
|
+
// `'prefer-offline': true` sets fetch() cache key to `force-cache`
|
40
|
+
// https://github.com/npm/npm-registry-fetch
|
41
|
+
//
|
42
|
+
// `'ignore-scripts': true` used to disable prepare and prepack scripts
|
43
|
+
// which are run when installing git dependencies
|
44
|
+
await runAsync(npm, npm.load, [
|
45
|
+
{
|
46
|
+
loglevel: "silent",
|
47
|
+
force: true,
|
48
|
+
audit: false,
|
49
|
+
"prefer-offline": true,
|
50
|
+
"ignore-scripts": true,
|
51
|
+
save: false,
|
52
|
+
},
|
53
|
+
]);
|
54
|
+
|
55
|
+
const dryRun = true;
|
56
|
+
|
57
|
+
// Returns dep name and version for npm install, example: ["react@16.6.0"]
|
58
|
+
let args = installArgsWithVersion(depName, desiredVersion, requirements);
|
59
|
+
|
60
|
+
// To check peer dependencies requirements in all top level dependencies we
|
61
|
+
// need to explicitly tell npm to fetch all manifests by specifying the
|
62
|
+
// existing dependency name and version in npm install
|
63
|
+
|
64
|
+
// For example, if we have "react@15.6.2" and "react-dom@15.6.2" installed
|
65
|
+
// and we want to install react@16.6.0, we need get the existing version of
|
66
|
+
// react-dom and pass this to npm install along with the new version react,
|
67
|
+
// this way npm fetches the manifest for react-dom and determines that we
|
68
|
+
// can't install react@16.6.0 due to the peer dependency requirement in
|
69
|
+
// react-dom
|
70
|
+
|
71
|
+
// If we only pass the new dep@version to npm install, e.g. "react@16.6.0" npm
|
72
|
+
// will only fetch the manifest for react and not know that react-dom enforces
|
73
|
+
// a peerDependency on react
|
74
|
+
|
75
|
+
// Returns dep name and version for npm install, example: ["react-dom@15.6.2"]
|
76
|
+
// - given react and react-dom in top level deps
|
77
|
+
const otherDeps = (topLevelDependencies || [])
|
78
|
+
.filter((dep) => dep.name !== depName && dep.version)
|
79
|
+
.map((dep) =>
|
80
|
+
installArgsWithVersion(dep.name, dep.version, dep.requirements)
|
81
|
+
)
|
82
|
+
.reduce((acc, dep) => acc.concat(dep), []);
|
83
|
+
|
84
|
+
args = args.concat(otherDeps);
|
85
|
+
|
86
|
+
const initialInstaller = new installer.Installer(directory, dryRun, args, {
|
87
|
+
packageLockOnly: true,
|
88
|
+
});
|
89
|
+
|
90
|
+
// Skip printing the success message
|
91
|
+
initialInstaller.printInstalled = (cb) => cb();
|
92
|
+
|
93
|
+
// There are some hard-to-prevent bits of output.
|
94
|
+
// This is horrible, but works.
|
95
|
+
const unmute = muteStderr();
|
96
|
+
try {
|
97
|
+
await runAsync(initialInstaller, initialInstaller.run, []);
|
98
|
+
} finally {
|
99
|
+
unmute();
|
100
|
+
}
|
101
|
+
|
102
|
+
const peerDependencyWarnings = initialInstaller.idealTree.warnings
|
103
|
+
.filter((warning) => warning.code === "EPEERINVALID")
|
104
|
+
.map((warning) => warning.message);
|
105
|
+
|
106
|
+
if (peerDependencyWarnings.length) {
|
107
|
+
throw new Error(peerDependencyWarnings.join("\n"));
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
module.exports = { checkPeerDependencies };
|
@@ -0,0 +1,22 @@
|
|
1
|
+
// Recursively removes all dependencies matching on name
|
2
|
+
function removeDependenciesFromLockfile(lockfile, dependencyNames) {
|
3
|
+
if (!lockfile.dependencies) return lockfile;
|
4
|
+
|
5
|
+
const dependencies = Object.entries(lockfile.dependencies).reduce(
|
6
|
+
(acc, [depName, packageValue]) => {
|
7
|
+
if (!dependencyNames.includes(depName)) {
|
8
|
+
acc[depName] = removeDependenciesFromLockfile(
|
9
|
+
packageValue,
|
10
|
+
dependencyNames
|
11
|
+
);
|
12
|
+
}
|
13
|
+
|
14
|
+
return acc;
|
15
|
+
},
|
16
|
+
{}
|
17
|
+
);
|
18
|
+
|
19
|
+
return Object.assign({}, lockfile, { dependencies });
|
20
|
+
}
|
21
|
+
|
22
|
+
module.exports = removeDependenciesFromLockfile;
|