feat(node-audit): migrate npm audit to bulk advisories endoint#8471
feat(node-audit): migrate npm audit to bulk advisories endoint#8471BerSomBen wants to merge 2 commits intodependency-check:mainfrom
Conversation
|
In #8422 we floated the idea of using the |
|
if we were going to have a direct API integration and maintain it I wonder if we'd be putting it in open-vulnerability-clients instead. Personally I'm not really in favour of this approach (unless there is no other choice), especially as NPM have shown they don't care about any clients other than npm CLI itself in how they have treated the removal of the quick API. (undocumented deprecation, no warning unannounced rolling brownouts, no public announcement of EOL date) |
There was a problem hiding this comment.
Pull request overview
This PR updates the Node/NPM audit integration to use npm’s bulk advisories API endpoint (used by npm v7+), and adjusts related configuration and tests to align with the new behavior.
Changes:
- Switch default/configured Node Audit URL to
/-/npm/v1/security/advisories/bulkacross runtime, docs, and test resources. - Add bulk request payload builder and a new bulk response parser; update analyzers/searcher to use the new flow.
- Make test updates for localization/assumptions and add a unit test for bulk payload sorting/deduping.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/src/test/resources/dependencycheck.properties | Updates test resource URL to bulk endpoint. |
| utils/src/test/java/org/owasp/dependencycheck/utils/XmlUtilsTest.java | Makes schema validation assertions tolerant to localized quoting. |
| maven/src/site/markdown/configuration.md | Updates documented default Node Audit URL. |
| core/src/test/resources/dependencycheck.properties | Updates core test resource URL to bulk endpoint. |
| core/src/test/java/org/owasp/dependencycheck/data/nodeaudit/NpmPayloadBuilderTest.java | Adds unit test for bulk payload dedupe/sort. |
| core/src/test/java/org/owasp/dependencycheck/analyzer/YarnAuditAnalyzerIT.java | Skips IT when yarn initialization fails instead of failing setup. |
| core/src/main/resources/dependencycheck.properties | Updates runtime default Node Audit URL to bulk endpoint. |
| core/src/main/java/org/owasp/dependencycheck/data/nodeaudit/NpmPayloadBuilder.java | Adds buildBulkPayload and version normalization helper. |
| core/src/main/java/org/owasp/dependencycheck/data/nodeaudit/NpmBulkAuditParser.java | New parser for bulk advisories response mapped to installed versions. |
| core/src/main/java/org/owasp/dependencycheck/data/nodeaudit/NodeAuditSearch.java | Submits bulk payload; parses with bulk parser; handles 410 Gone messaging. |
| core/src/main/java/org/owasp/dependencycheck/analyzer/YarnAuditAnalyzer.java | Switches Yarn Classic path to bulk submission using dependency map. |
| core/src/main/java/org/owasp/dependencycheck/analyzer/NodeAuditAnalyzer.java | Switches Node audit analyzer to bulk submission using dependency map. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public List<Advisory> parse(JSONObject bulkResponse, MultiValuedMap<String, String> dependencyMap) | ||
| throws JSONException { | ||
| final List<Advisory> out = new ArrayList<>(); | ||
| final Iterator<?> pkgKeys = bulkResponse.keys(); | ||
| while (pkgKeys.hasNext()) { | ||
| final String moduleName = (String) pkgKeys.next(); | ||
| final JSONArray advisoriesForPkg = bulkResponse.optJSONArray(moduleName); | ||
| if (advisoriesForPkg == null) { | ||
| continue; | ||
| } | ||
| for (int i = 0; i < advisoriesForPkg.length(); i++) { | ||
| final JSONObject raw = advisoriesForPkg.getJSONObject(i); | ||
| for (String installedVersion : new LinkedHashSet<>(dependencyMap.get(moduleName))) { | ||
| final String normalized = NpmPayloadBuilder.normalizeVersion(installedVersion); | ||
| if (normalized == null || normalized.isEmpty()) { | ||
| continue; | ||
| } | ||
| final String range = raw.optString("vulnerable_versions", ""); | ||
| if (range.isEmpty()) { | ||
| continue; | ||
| } | ||
| if (!versionSatisfiesRange(normalized, range)) { | ||
| continue; | ||
| } | ||
| out.add(toAdvisory(moduleName, normalized, raw)); | ||
| } | ||
| } | ||
| } | ||
| return out; | ||
| } |
| // Walk the lockfile to populate dependencyMap (used for the bulk advisory request body and response mapping) | ||
| NpmPayloadBuilder.build(lockJson, packageJson, dependencyMap, | ||
| getSettings().getBoolean(Settings.KEYS.ANALYZER_NODE_AUDIT_SKIPDEV, false)); | ||
|
|
||
| // Submits the package payload to the nsp check service | ||
| return getSearcher().submitPackage(payload); | ||
| // Submits package versions to the npm bulk advisory API | ||
| return getSearcher().submitPackage(dependencyMap); |
| NpmPayloadBuilder.build(lockJson, packageJson, dependencyMap, skipDevDependencies); | ||
|
|
||
| // Submits the package payload to the nsp check service | ||
| return getSearcher().submitPackage(payload); | ||
| // Submits package versions to the npm bulk advisory API | ||
| return getSearcher().submitPackage(dependencyMap); |
| public static JsonObject buildBulkPayload(MultiValuedMap<String, String> dependencyMap) { | ||
| final JsonObjectBuilder root = Json.createObjectBuilder(); | ||
| for (String name : new TreeSet<>(dependencyMap.keySet())) { | ||
| final Set<String> versions = new TreeSet<>(); | ||
| for (String raw : dependencyMap.get(name)) { | ||
| final String v = normalizeVersion(raw); | ||
| if (v == null || v.isEmpty()) { | ||
| continue; | ||
| } | ||
| if (NodePackageAnalyzer.shouldSkipDependency(name, v)) { | ||
| continue; | ||
| } | ||
| versions.add(v); | ||
| } |
| private Advisory toAdvisory(String moduleName, String resolvedVersion, JSONObject object) throws JSONException { | ||
| final Advisory advisory = new Advisory(); | ||
| if (object.has("github_advisory_id")) { | ||
| advisory.setGhsaId(object.getString("github_advisory_id")); | ||
| } else if (object.has("id")) { | ||
| advisory.setGhsaId(String.valueOf(object.get("id"))); | ||
| } |
|
With a quick look through, this PR looks pretty "AI sloppy" - includes unrelated changes (why is it touching XmlUtils? Why is it disabling yarn tests?), overly complex string hacking for "normalizing versions", incomplete CVSS handling, alongside various approaches inconsistent with the rest of the codebase. @BerSomBen are you planning to document your design decisions, approach and testing performed? |
ough.... I did not notice this. For sure, this is the bettter way since npm does not care about its own endpoints. |
| /** | ||
| * Trims and strips JSON-string-style surrounding quotes from lockfile-derived versions. | ||
| */ | ||
| static String normalizeVersion(String raw) { |
There was a problem hiding this comment.
This looks very brittle/dodgy.
| try { | ||
| analyzer = createAndPrepareYarnAnalyzer(engine); | ||
| } catch (InitializationException e) { | ||
| assumeTrue(false, "Yarn not on PATH or Yarn Audit analyzer failed to initialize (install yarn to run this IT): " + e.getMessage()); |
| assertThat(t.getMessage(), containsString("cvc-elt.1.a")); | ||
| assertThat(t.getMessage(), containsString("'items'")); | ||
| // XSD error text is localized; element name may be quoted with ' or " depending on JVM locale | ||
| assertThat(t.getMessage(), anyOf(containsString("'items'"), containsString("\"items\""))); |
There was a problem hiding this comment.
Why is this necessary?
There was a problem hiding this comment.
builds on non english os's could get a different quotation style. on my german system, I got ''" and a failing test.
There was a problem hiding this comment.
Ok but that's separate to an npm change. Better to create separate PR or at least mention in your description why you changed seemingly unrelated things in your PR.
@chadlwilson since my CI is crying all the day, I was pushing an approach fixing the issue. But as U wrote prior: it would be much easier, cleaner and better to maintain if we use the npm cli. |
I think we probably need to decide in general on approach. There are some downsides to just using the npm cli directly (again, I am assuming it is possible and has an appropriate JSON output from Probably close to zero if you start including support for workspaces and other things like that. |
Description of Change
use the bulk adversery endpoint for npm audit
Related issues
fixes #7292
Have test cases been added to cover the new functionality?
yes