From fc82dd3410db6d27876a28a36186872e666ed143 Mon Sep 17 00:00:00 2001 From: Richard Lau Date: Fri, 19 Nov 2021 12:25:04 +0000 Subject: [PATCH] fix: update security advisory data endpoint The previous way of fetching https://npmjs.com/advisories/ URLs with a `X-Spiferack: 1` header no longer returns JSON, most probably due to such URLs now redirecting to the GitHub Advisory database. Switch instead to use the endpoint: `https://registry.npmjs.org/-/npm/v1/security/advisories/{id}` --- src/plugins/audit.js | 34 +++++++++-------------- test/plugins/audit.test.js | 55 ++++++++++---------------------------- 2 files changed, 27 insertions(+), 62 deletions(-) diff --git a/src/plugins/audit.js b/src/plugins/audit.js index d0243bc..07b3fee 100644 --- a/src/plugins/audit.js +++ b/src/plugins/audit.js @@ -1,4 +1,4 @@ -const { differenceInDays } = require('date-fns'); +const { differenceInDays, min } = require('date-fns'); const { fetch } = require('../lib/network'); const { getAuditInfo } = require('../lib/npm'); @@ -7,18 +7,12 @@ const { createError, createWarning } = require('../lib/result'); const ONE_MONTH = 30; // days -const isSourceVulnerability = (item) => item.url !== undefined; - -const formatAuditEvents = (vulnerability) => { - const events = vulnerability.events.reduce((state, event) => { - state[event.type] = event.created; - return state; - }, {}); - return events; -}; +const isSourceVulnerability = (item) => item.source !== undefined; const formatAudit = (v) => ({ name: v.name, + created: v.created, + updated: v.updated, severity: v.severity, via: v.via, effects: v.effects, @@ -53,12 +47,8 @@ const auditPlugin = async () => { const auditPromises = audit.map((vulnerability) => (async () => { // Getting extra advisory data from NPM - const data = await fetch(vulnerability.via.url, { - headers: { 'X-Spiferack': 1 } - }); - - const advisory = { cve: data.advisoryData.cves[0], events: data.events }; - + const data = await fetch(`https://registry.npmjs.org/-/npm/v1/security/advisories/${vulnerability.via.source}`); + const advisory = { cve: data.cves[0], created: data.created, updated: data.updated }; return { ...vulnerability, ...advisory @@ -69,10 +59,8 @@ const auditPlugin = async () => { const auditAsyncResult = await Promise.all(auditPromises); const auditResult = auditAsyncResult.map((vulnerability) => { - const events = formatAuditEvents(vulnerability); return { - ...formatAudit(vulnerability), - events + ...formatAudit(vulnerability) }; }); @@ -85,7 +73,9 @@ const auditPlugin = async () => { // Error if there is a high or above vulnerability that has not been fixed after 1 month. const highRisk = auditResult.filter((v) => { const now = new Date(); - const publishDate = new Date(v.events.published || v.events.reported); + // In some cases, e.g. https://registry.npmjs.org/-/npm/v1/security/advisories/1002643 + // the updated date is earlier than created. Pick the earlier one. + const publishDate = min([new Date(v.created), new Date(v.updated)]); return ( (v.severity === 'high' || v.severity === 'critical') && differenceInDays(now, publishDate) > ONE_MONTH @@ -107,7 +97,9 @@ const auditPlugin = async () => { // Warn if there is a medium vulnerability that has not been fixed after 4 months. (report the number of these) const mediumRisk = auditResult.filter((v) => { const now = new Date(); - const publishDate = new Date(v.events.published || v.events.reported); + // In some cases, e.g. https://registry.npmjs.org/-/npm/v1/security/advisories/1002643 + // the updated date is earlier than created. Pick the earlier one. + const publishDate = min([new Date(v.created), new Date(v.updated)]); return ( v.severity === 'moderate' && differenceInDays(now, publishDate) > ONE_MONTH * 4 diff --git a/test/plugins/audit.test.js b/test/plugins/audit.test.js index 050ee9f..e22c383 100644 --- a/test/plugins/audit.test.js +++ b/test/plugins/audit.test.js @@ -54,29 +54,19 @@ it('should return an error if a critical vulnerability is active for more than 1 // mocking http request network.fetch.mockImplementation(() => { return Promise.resolve({ - advisoryData: { - id: 1756, - cves: ['CVE-2021-25945'] - }, - events: [ - { - id: 3241, - advisory_id: 1756, - created: '2021-06-08T23:17:06.692', - type: 'published' - } - ] + id: 1756, + cves: ['CVE-2021-25945'], + created: '2021-06-08T23:17:06.692', + updated: '2021-06-08T23:17:06.692' }); }); const result = await auditPlugin(); - expect(result.length).toBe(1); expect(result[0].type).toBe('error'); expect(network.fetch).toHaveBeenCalled(); expect(network.fetch).toHaveBeenCalledWith( - 'https://npmjs.com/advisories/1756', - { headers: { 'X-Spiferack': 1 } } + 'https://registry.npmjs.org/-/npm/v1/security/advisories/1756' ); expect(failure).toHaveBeenCalled(); }); @@ -103,18 +93,10 @@ it('should return a warning if a moderate vulnerability is active for more than // mocking http request network.fetch.mockImplementation(() => { return Promise.resolve({ - advisoryData: { - id: 1756, - cves: ['CVE-2021-25945'] - }, - events: [ - { - id: 3241, - advisory_id: 1756, - created: '2021-02-08T23:17:06.692', - type: 'published' - } - ] + id: 1756, + cves: ['CVE-2021-25945'], + created: '2021-06-08T23:16:33.057', + updated: '2021-02-08T23:17:06.691' }); }); @@ -124,8 +106,7 @@ it('should return a warning if a moderate vulnerability is active for more than expect(result[0].type).toBe('warning'); expect(network.fetch).toHaveBeenCalled(); expect(network.fetch).toHaveBeenCalledWith( - 'https://npmjs.com/advisories/1756', - { headers: { 'X-Spiferack': 1 } } + 'https://registry.npmjs.org/-/npm/v1/security/advisories/1756' ); expect(warning).toHaveBeenCalled(); }); @@ -161,18 +142,10 @@ it("should return a warning if they're more than 10 low risk vulnerabilities", a */ network.fetch.mockImplementation(() => { return Promise.resolve({ - advisoryData: { - id: 1756, - cves: ['CVE-2021-25945'] - }, - events: [ - { - id: 3241, - advisory_id: 1756, - created: '2021-02-08T23:17:06.692', - type: 'published' - } - ] + id: 1756, + cves: ['CVE-2021-25945'], + created: '2021-06-08T23:16:33.057', + updated: '2021-02-08T23:17:06.691' }); });