dependabot-bun 0.296.2 → 0.296.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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;
|