retire 5.6.0 → 5.7.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.7.0]
4
+
5
+ ### Improvements
6
+
7
+ - Added CycloneDX 1.7 output: `--outputformat cyclonedxJSON1_7` and `cyclonedxJSON1_7_VEX`.
8
+ - CycloneDX 1.7 reports every resolved license, each as its own SPDX expression. Earlier spec versions only allow a single expression per component, so 1.4/1.6 output still reports the first license only.
9
+ - The CycloneDX 1.6 and 1.7 reports now include:
10
+ - `$schema`, pointing at the schema for the emitted spec version.
11
+ - `metadata.tools` in the object form introduced in 1.5, replacing the deprecated legacy array.
12
+ - `metadata.component`, describing the scanned path as the subject of the BOM.
13
+ - `dependencies`, linking the scan target to every detected component.
14
+ - `evidence.identity`, reporting how the version was detected (`filecontent`, `filename`, `hash`, `ast`, ...) as a CycloneDX identity technique and confidence.
15
+ - `vulnerabilities[].detail`, when the repository supplies `details` (VEX variants only).
16
+
17
+ ### Bugfixes
18
+
19
+ - CycloneDX 1.6 output threw when a vulnerability in a custom `--jsrepo` had no `cwe`, and could throw on a vulnerability with no `identifiers`.
20
+
3
21
  ## [5.6.0]
4
22
 
5
23
  ### Improvements
package/README.md CHANGED
@@ -18,7 +18,8 @@ Options:
18
18
  --jsrepo <path|url> Local or internal version of repo. Can be multiple comma separated. Default: 'central')
19
19
  --cachedir <path> Path to use for local cache instead of /tmp/.retire-cache
20
20
  --proxy <url> Proxy url (http://some.host:8080)
21
- --outputformat <format> Valid formats: text, json, jsonsimple, depcheck (experimental), cyclonedx and cyclonedxJSON
21
+ --outputformat <format> Valid formats: text, json, jsonsimple, depcheck (experimental), cyclonedx, cyclonedxJSON, cyclonedxJSON1_6,
22
+ cyclonedxJSON1_6_VEX, cyclonedxJSON1_7 and cyclonedxJSON1_7_VEX
22
23
  --outputpath <path> File to which output should be written
23
24
  --ignore <paths> Comma delimited list of paths to ignore
24
25
  --ignorefile <path> Custom ignore file, defaults to .retireignore / .retireignore.json
@@ -35,7 +36,9 @@ Options:
35
36
  ```
36
37
 
37
38
  The `depcheck` output format mimics the output of OWASP Dependency Check, but lacks some information compared to OWASP Dependency Check, because that information is not in the repo.
38
- The `cyclonedx` output format is based on based on the https://github.com/CycloneDX spec.
39
+ The `cyclonedx` output format is based on the https://github.com/CycloneDX spec. `cyclonedx` and `cyclonedxJSON` emit CycloneDX 1.4
40
+ (XML and JSON respectively). `cyclonedxJSON1_6` and `cyclonedxJSON1_7` emit JSON for the corresponding spec versions, and the
41
+ `_VEX` variants of those additionally include a `vulnerabilities` section.
39
42
 
40
43
  ## .retireignore
41
44
 
package/lib/cli.js CHANGED
@@ -70,7 +70,7 @@ const prg = commander_1.program
70
70
  .option('--jsrepo <path|url>', "Local or internal version of repo. Can be multiple comma separated. Default: 'central')")
71
71
  .option('--cachedir <path>', 'Path to use for local cache instead of /tmp/.retire-cache')
72
72
  .option('--proxy <url>', 'Proxy url (http://some.host:8080)')
73
- .option('--outputformat <format>', 'Valid formats: text, json, jsonsimple, depcheck (experimental), cyclonedx, cyclonedxJSON, cyclonedxJSON1_6, cyclonedxJSON1_6_VEX')
73
+ .option('--outputformat <format>', 'Valid formats: text, json, jsonsimple, depcheck (experimental), cyclonedx, cyclonedxJSON, cyclonedxJSON1_6, cyclonedxJSON1_6_VEX, cyclonedxJSON1_7, cyclonedxJSON1_7_VEX')
74
74
  .option('--outputpath <path>', 'File to which output should be written')
75
75
  .option('--ignore <paths>', 'Comma delimited list of paths to ignore')
76
76
  .option('--ignorefile <path>', 'Custom ignore file, defaults to .retireignore / .retireignore.json')
@@ -1,3 +1,2 @@
1
- import { ConfigurableLogger } from '../reporting';
2
- declare const _default: ConfigurableLogger;
1
+ declare const _default: import("../reporting").ConfigurableLogger;
3
2
  export default _default;
@@ -1,209 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
- const retire = __importStar(require("../retire"));
37
- const fs = __importStar(require("fs"));
38
- const utils_1 = require("./utils");
39
- const path = __importStar(require("path"));
40
- const crypto = __importStar(require("crypto"));
41
- function configureCycloneDXJSONLogger(logger, writer, config, hash) {
42
- let vulnsFound = false;
43
- const finalResults = {
44
- version: retire.version,
45
- start: new Date().toISOString(),
46
- data: [],
47
- messages: [],
48
- errors: [],
49
- };
50
- logger.info = finalResults.messages.push;
51
- logger.debug = config.verbose
52
- ? finalResults.messages.push
53
- : function () {
54
- return;
55
- };
56
- logger.warn = logger.error = finalResults.errors.push;
57
- logger.logVulnerableDependency = function (finding) {
58
- vulnsFound = true;
59
- finalResults.data.push(finding);
60
- };
61
- logger.logDependency = function (finding) {
62
- if (finding.results.length > 0) {
63
- finalResults.data.push(finding);
64
- }
65
- };
66
- logger.close = function (callback) {
67
- const write = vulnsFound ? writer.err : writer.out;
68
- const seen = new Map();
69
- const vulnerabilitiesCyclone = new Map();
70
- const includeVEX = config.outputformat === 'cyclonedxJSON1_6_VEX';
71
- const vulnerabilityRepositoriesList = (0, utils_1.vulnerabilityRepositories)(config.jsRepo);
72
- // Only attributable to a single URL when exactly one repository was used
73
- const retireSource = {
74
- name: 'Retire.js',
75
- url: vulnerabilityRepositoriesList.length === 1 ? vulnerabilityRepositoriesList[0] : undefined,
76
- };
77
- const properties = vulnerabilityRepositoriesList.map((repo) => ({ name: 'retirejs:vulnerability-repository', value: repo }));
78
- if (config.insecure) {
79
- properties.push({ name: 'retirejs:ignore-repository-certificate-errors', value: 'true' });
80
- }
81
- const components = finalResults.data
82
- .filter((d) => d.results)
83
- .map((r) => r.results
84
- .map((dep) => {
85
- dep.version = (dep.version.split('.').length >= 3 ? dep.version : dep.version + '.0').replace(/-/g, '.');
86
- let hashes;
87
- const filepath = r.file;
88
- const evidence = { occurrences: [] };
89
- if (filepath) {
90
- const file = fs.readFileSync(filepath);
91
- const relativePath = path.relative(process.cwd(), filepath);
92
- evidence.occurrences.push({ location: relativePath });
93
- hashes = [
94
- { alg: 'MD5', content: hash.md5(file) },
95
- { alg: 'SHA-1', content: hash.sha1(file) },
96
- { alg: 'SHA-256', content: hash.sha256(file) },
97
- { alg: 'SHA-512', content: hash.sha512(file) },
98
- ];
99
- }
100
- const purl = (0, utils_1.generatePURL)(dep);
101
- const existing = seen.get(purl);
102
- if (existing) {
103
- const missing = evidence.occurrences.filter((x) => !existing.evidence.occurrences.some((y) => y.location == x.location));
104
- existing.evidence.occurrences.push(...missing);
105
- return undefined;
106
- }
107
- const nameParts = dep.component.split('/').reverse();
108
- const bomRef = purl;
109
- dep.vulnerabilities?.forEach((vuln) => {
110
- // Pick valid identifiers for VEX
111
- const ids = vuln.identifiers?.CVE ?? (vuln.identifiers?.githubID ? [vuln.identifiers.githubID] : undefined);
112
- if (!ids)
113
- return;
114
- ids.forEach((id) => {
115
- if (!vulnerabilitiesCyclone.has(id)) {
116
- const { references, advisories } = mapUrls(vuln);
117
- vulnerabilitiesCyclone.set(id, {
118
- id,
119
- source: retireSource,
120
- cwes: vuln.cwe.map((c) => parseInt(c.split("-")[1])),
121
- description: vuln.identifiers.summary,
122
- advisories: advisories,
123
- references: references,
124
- ratings: [
125
- {
126
- source: retireSource,
127
- severity: vuln.severity,
128
- },
129
- ],
130
- affects: [],
131
- });
132
- }
133
- vulnerabilitiesCyclone.get(id).affects.push({
134
- ref: bomRef,
135
- versions: [
136
- {
137
- // "vers:npm/1.2.3|>=2.0.0|<5.0.0"
138
- range: 'vers:npm/' + (vuln.atOrAbove ? '>=' + vuln.atOrAbove + '|' : '') + '<' + vuln.below,
139
- status: 'affected',
140
- },
141
- ],
142
- });
143
- });
144
- });
145
- const result = {
146
- "bom-ref": bomRef,
147
- type: 'library',
148
- name: nameParts[0],
149
- group: nameParts[1],
150
- version: dep.version,
151
- purl: purl,
152
- hashes: hashes,
153
- evidence,
154
- licenses: mapLicenses(dep.licenses),
155
- };
156
- seen.set(purl, result);
157
- return result;
158
- })
159
- .filter((x) => x != undefined))
160
- .reduce((a, b) => a.concat(b), []);
161
- write(JSON.stringify({
162
- bomFormat: 'CycloneDX',
163
- specVersion: '1.6',
164
- serialNumber: `urn:uuid:${crypto.randomUUID()}`,
165
- version: 1,
166
- metadata: {
167
- timestamp: finalResults.start,
168
- tools: [
169
- {
170
- vendor: 'RetireJS',
171
- name: 'retire',
172
- version: retire.version,
173
- },
174
- ],
175
- properties: properties.length ? properties : undefined,
176
- },
177
- components: components,
178
- vulnerabilities: includeVEX ? Array.from(vulnerabilitiesCyclone.values()) : undefined,
179
- }, undefined, 2));
180
- writer.close(callback);
181
- };
182
- }
183
- function mapLicenses(licenses) {
184
- if (!licenses)
185
- return [];
186
- if (licenses.length == 0)
187
- return [];
188
- if (licenses[0] == 'commercial')
189
- return [{ license: { name: 'Commercial' } }];
190
- return [{ expression: licenses[0] }];
191
- }
192
- function mapUrls(vulnerability) {
193
- const references = [];
194
- if (vulnerability.identifiers.CVE) {
195
- const nvdlink = `https://nvd.nist.gov/vuln/detail/${vulnerability.identifiers.CVE[0]}`;
196
- references.push({ id: vulnerability.identifiers.CVE[0], source: { name: 'NVD', url: nvdlink } });
197
- }
198
- if (vulnerability.identifiers.githubID) {
199
- const ghsa = `https://github.com/advisories/${vulnerability.identifiers.githubID}`;
200
- references.push({ id: vulnerability.identifiers.githubID, source: { name: 'GitHub Advisories', url: ghsa } });
201
- }
202
- const advisories = vulnerability.info
203
- .filter((url) => !url.startsWith('https://nvd.nist.gov/vuln/detail/') && !url.startsWith('https://github.com/advisories/'))
204
- .map((u) => ({ url: u }));
205
- return { references, advisories };
206
- }
207
- exports.default = {
208
- configure: configureCycloneDXJSONLogger,
209
- };
3
+ const cyclonedx_json_builder_1 = require("./cyclonedx-json-builder");
4
+ exports.default = (0, cyclonedx_json_builder_1.cycloneDXJSONLogger)('1.6');
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../reporting").ConfigurableLogger;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const cyclonedx_json_builder_1 = require("./cyclonedx-json-builder");
4
+ exports.default = (0, cyclonedx_json_builder_1.cycloneDXJSONLogger)('1.7');
@@ -0,0 +1,7 @@
1
+ import { ConfigurableLogger } from '../reporting';
2
+ /**
3
+ * Spec versions sharing this implementation. The 1.4 reporters are kept separate, as the
4
+ * modern object form of metadata.tools was only introduced in 1.5.
5
+ */
6
+ export type CycloneDXSpecVersion = '1.6' | '1.7';
7
+ export declare function cycloneDXJSONLogger(specVersion: CycloneDXSpecVersion): ConfigurableLogger;
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.cycloneDXJSONLogger = cycloneDXJSONLogger;
37
+ const retire = __importStar(require("../retire"));
38
+ const fs = __importStar(require("fs"));
39
+ const utils_1 = require("./utils");
40
+ const path = __importStar(require("path"));
41
+ const crypto = __importStar(require("crypto"));
42
+ const TOOL_BOM_REF = 'retirejs-tool';
43
+ const SCAN_TARGET_BOM_REF = 'retirejs:scan-target';
44
+ /**
45
+ * Retire.js has no native confidence score, so the mapping from the detection method to a
46
+ * CycloneDX identity technique and confidence is a heuristic. Both technique and confidence
47
+ * are required by the schema.
48
+ */
49
+ const identityTechniques = {
50
+ hash: { technique: 'hash-comparison', confidence: 1 },
51
+ ast: { technique: 'ast-fingerprint', confidence: 0.9 },
52
+ filecontent: { technique: 'source-code-analysis', confidence: 0.8 },
53
+ filecontentreplace: { technique: 'source-code-analysis', confidence: 0.8 },
54
+ func: { technique: 'dynamic-analysis', confidence: 0.8 },
55
+ filename: { technique: 'filename', confidence: 0.5 },
56
+ uri: { technique: 'filename', confidence: 0.5 },
57
+ };
58
+ function configureCycloneDXJSONLogger(specVersion, logger, writer, config, hash) {
59
+ let vulnsFound = false;
60
+ const finalResults = {
61
+ version: retire.version,
62
+ start: new Date().toISOString(),
63
+ data: [],
64
+ messages: [],
65
+ errors: [],
66
+ };
67
+ logger.info = finalResults.messages.push;
68
+ logger.debug = config.verbose
69
+ ? finalResults.messages.push
70
+ : function () {
71
+ return;
72
+ };
73
+ logger.warn = logger.error = finalResults.errors.push;
74
+ logger.logVulnerableDependency = function (finding) {
75
+ vulnsFound = true;
76
+ finalResults.data.push(finding);
77
+ };
78
+ logger.logDependency = function (finding) {
79
+ if (finding.results.length > 0) {
80
+ finalResults.data.push(finding);
81
+ }
82
+ };
83
+ logger.close = function (callback) {
84
+ const write = vulnsFound ? writer.err : writer.out;
85
+ const seen = new Map();
86
+ const vulnerabilitiesCyclone = new Map();
87
+ const includeVEX = config.outputformat?.endsWith('_VEX') ?? false;
88
+ const vulnerabilityRepositoriesList = (0, utils_1.vulnerabilityRepositories)(config.jsRepo);
89
+ // Only attributable to a single URL when exactly one repository was used
90
+ const retireSource = {
91
+ name: 'Retire.js',
92
+ url: vulnerabilityRepositoriesList.length === 1 ? vulnerabilityRepositoriesList[0] : undefined,
93
+ };
94
+ const properties = vulnerabilityRepositoriesList.map((repo) => ({
95
+ name: 'retirejs:vulnerability-repository',
96
+ value: repo,
97
+ }));
98
+ if (config.insecure) {
99
+ properties.push({ name: 'retirejs:ignore-repository-certificate-errors', value: 'true' });
100
+ }
101
+ const components = finalResults.data
102
+ .filter((d) => d.results)
103
+ .map((r) => r.results
104
+ .map((dep) => {
105
+ dep.version = (dep.version.split('.').length >= 3 ? dep.version : dep.version + '.0').replace(/-/g, '.');
106
+ let hashes;
107
+ const filepath = r.file;
108
+ const evidence = { occurrences: [] };
109
+ if (filepath) {
110
+ const file = fs.readFileSync(filepath);
111
+ const relativePath = path.relative(process.cwd(), filepath);
112
+ evidence.occurrences.push({ location: relativePath });
113
+ hashes = [
114
+ { alg: 'MD5', content: hash.md5(file) },
115
+ { alg: 'SHA-1', content: hash.sha1(file) },
116
+ { alg: 'SHA-256', content: hash.sha256(file) },
117
+ { alg: 'SHA-512', content: hash.sha512(file) },
118
+ ];
119
+ }
120
+ const purl = (0, utils_1.generatePURL)(dep);
121
+ const existing = seen.get(purl);
122
+ if (existing) {
123
+ const missing = evidence.occurrences.filter((x) => !existing.evidence.occurrences.some((y) => y.location == x.location));
124
+ existing.evidence.occurrences.push(...missing);
125
+ return undefined;
126
+ }
127
+ const identity = mapIdentity(dep);
128
+ if (identity)
129
+ evidence.identity = [identity];
130
+ const nameParts = dep.component.split('/').reverse();
131
+ const bomRef = purl;
132
+ dep.vulnerabilities?.forEach((vuln) => {
133
+ // Pick valid identifiers for VEX
134
+ const ids = vuln.identifiers?.CVE ?? (vuln.identifiers?.githubID ? [vuln.identifiers.githubID] : undefined);
135
+ if (!ids)
136
+ return;
137
+ ids.forEach((id) => {
138
+ if (!vulnerabilitiesCyclone.has(id)) {
139
+ const { references, advisories } = mapUrls(vuln);
140
+ const cwes = vuln.cwe?.map((c) => parseInt(c.split('-')[1]));
141
+ vulnerabilitiesCyclone.set(id, {
142
+ id,
143
+ source: retireSource,
144
+ cwes: cwes?.length ? cwes : undefined,
145
+ description: vuln.identifiers?.summary,
146
+ detail: vuln.details,
147
+ advisories: advisories,
148
+ references: references,
149
+ ratings: [
150
+ {
151
+ source: retireSource,
152
+ severity: vuln.severity,
153
+ },
154
+ ],
155
+ affects: [],
156
+ });
157
+ }
158
+ vulnerabilitiesCyclone.get(id).affects.push({
159
+ ref: bomRef,
160
+ versions: [
161
+ {
162
+ // "vers:npm/1.2.3|>=2.0.0|<5.0.0"
163
+ range: 'vers:npm/' + (vuln.atOrAbove ? '>=' + vuln.atOrAbove + '|' : '') + '<' + vuln.below,
164
+ status: 'affected',
165
+ },
166
+ ],
167
+ });
168
+ });
169
+ });
170
+ const result = {
171
+ 'bom-ref': bomRef,
172
+ type: 'library',
173
+ name: nameParts[0],
174
+ group: nameParts[1],
175
+ version: dep.version,
176
+ purl: purl,
177
+ hashes: hashes,
178
+ evidence,
179
+ licenses: mapLicenses(dep.licenses, specVersion),
180
+ };
181
+ seen.set(purl, result);
182
+ return result;
183
+ })
184
+ .filter((x) => x != undefined))
185
+ .reduce((a, b) => a.concat(b), []);
186
+ const dependencies = [
187
+ { ref: SCAN_TARGET_BOM_REF, dependsOn: components.map((c) => c['bom-ref']) },
188
+ ...components.map((c) => ({ ref: c['bom-ref'], dependsOn: [] })),
189
+ ];
190
+ write(JSON.stringify({
191
+ $schema: `http://cyclonedx.org/schema/bom-${specVersion}.schema.json`,
192
+ bomFormat: 'CycloneDX',
193
+ specVersion,
194
+ serialNumber: `urn:uuid:${crypto.randomUUID()}`,
195
+ version: 1,
196
+ metadata: {
197
+ timestamp: finalResults.start,
198
+ tools: {
199
+ components: [
200
+ {
201
+ type: 'application',
202
+ 'bom-ref': TOOL_BOM_REF,
203
+ group: 'RetireJS',
204
+ name: 'retire',
205
+ version: retire.version,
206
+ },
207
+ ],
208
+ },
209
+ component: {
210
+ type: 'application',
211
+ 'bom-ref': SCAN_TARGET_BOM_REF,
212
+ name: scanTargetName(config.path),
213
+ },
214
+ properties: properties.length ? properties : undefined,
215
+ },
216
+ components: components,
217
+ dependencies,
218
+ vulnerabilities: includeVEX ? Array.from(vulnerabilitiesCyclone.values()) : undefined,
219
+ }, undefined, 2));
220
+ writer.close(callback);
221
+ };
222
+ }
223
+ function scanTargetName(scanPath) {
224
+ return path.basename(path.resolve(scanPath ?? '.'));
225
+ }
226
+ function mapIdentity(component) {
227
+ if (!component.detection)
228
+ return undefined;
229
+ const mapped = identityTechniques[component.detection];
230
+ if (!mapped)
231
+ return undefined;
232
+ return {
233
+ field: 'version',
234
+ confidence: mapped.confidence,
235
+ concludedValue: component.version,
236
+ methods: [{ technique: mapped.technique, confidence: mapped.confidence, value: component.detection }],
237
+ };
238
+ }
239
+ function mapLicense(license) {
240
+ return license == 'commercial' ? { license: { name: 'Commercial' } } : { expression: license };
241
+ }
242
+ function mapLicenses(licenses, specVersion) {
243
+ if (!licenses)
244
+ return [];
245
+ if (licenses.length == 0)
246
+ return [];
247
+ // Before 1.7, the licenses array could hold at most one SPDX expression, and expressions
248
+ // could not be mixed with named licenses.
249
+ if (specVersion === '1.6')
250
+ return [mapLicense(licenses[0])];
251
+ return licenses.map(mapLicense);
252
+ }
253
+ function mapUrls(vulnerability) {
254
+ const references = [];
255
+ if (vulnerability.identifiers?.CVE) {
256
+ const nvdlink = `https://nvd.nist.gov/vuln/detail/${vulnerability.identifiers.CVE[0]}`;
257
+ references.push({ id: vulnerability.identifiers.CVE[0], source: { name: 'NVD', url: nvdlink } });
258
+ }
259
+ if (vulnerability.identifiers?.githubID) {
260
+ const ghsa = `https://github.com/advisories/${vulnerability.identifiers.githubID}`;
261
+ references.push({ id: vulnerability.identifiers.githubID, source: { name: 'GitHub Advisories', url: ghsa } });
262
+ }
263
+ const advisories = vulnerability.info
264
+ .filter((url) => !url.startsWith('https://nvd.nist.gov/vuln/detail/') && !url.startsWith('https://github.com/advisories/'))
265
+ .map((u) => ({ url: u }));
266
+ return { references, advisories };
267
+ }
268
+ function cycloneDXJSONLogger(specVersion) {
269
+ return {
270
+ configure: (logger, writer, config, hash) => configureCycloneDXJSONLogger(specVersion, logger, writer, config, hash),
271
+ };
272
+ }
package/lib/reporting.js CHANGED
@@ -46,6 +46,7 @@ const depcheck_1 = __importDefault(require("./reporters/depcheck"));
46
46
  const cyclonedx_1 = __importDefault(require("./reporters/cyclonedx"));
47
47
  const cyclonedx_json_1 = __importDefault(require("./reporters/cyclonedx-json"));
48
48
  const cyclonedx_1_6_json_1 = __importDefault(require("./reporters/cyclonedx-1_6-json"));
49
+ const cyclonedx_1_7_json_1 = __importDefault(require("./reporters/cyclonedx-1_7-json"));
49
50
  const loggers = {
50
51
  console: console_1.default,
51
52
  text: console_1.default,
@@ -55,6 +56,8 @@ const loggers = {
55
56
  cyclonedxJSON: cyclonedx_json_1.default,
56
57
  cyclonedxJSON1_6: cyclonedx_1_6_json_1.default,
57
58
  cyclonedxJSON1_6_VEX: cyclonedx_1_6_json_1.default,
59
+ cyclonedxJSON1_7: cyclonedx_1_7_json_1.default,
60
+ cyclonedxJSON1_7_VEX: cyclonedx_1_7_json_1.default,
58
61
  clean: console_1.default,
59
62
  jsonsimple: json_1.default,
60
63
  };
package/lib/retire.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  var exports = exports || {};
7
- exports.version = '5.6.0';
7
+ exports.version = '5.7.0';
8
8
 
9
9
  function isDefined(o) {
10
10
  return typeof o !== 'undefined';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "Erlend Oftedal <erlend@oftedal.no>",
3
3
  "name": "retire",
4
4
  "description": "Retire is a tool for detecting use of vulnerable libraries",
5
- "version": "5.6.0",
5
+ "version": "5.7.0",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",