retire 5.5.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,29 @@
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
+
21
+ ## [5.6.0]
22
+
23
+ ### Improvements
24
+
25
+ - Report when repository certificate errors are ignored (`--insecure`): as `ignoreRepositoryCertificateErrors` in the JSON report, and as a `retirejs:ignore-repository-certificate-errors` entry under `metadata.properties` in all CycloneDX reports. The entry is only present when the option is set.
26
+
3
27
  ## [5.5.0]
4
28
 
5
29
  ### 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')
@@ -96,6 +96,7 @@ const log = reporting.open({
96
96
  colors: !!prg.colors,
97
97
  colorwarn,
98
98
  jsRepo: jsrepolocation,
99
+ insecure: prg.insecure,
99
100
  outputformat: prg.outputformat,
100
101
  outputpath: prg.outputpath,
101
102
  path: scanpath,
@@ -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,210 +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 repositories = (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: repositories.length === 1 ? repositories[0] : undefined,
76
- };
77
- const components = finalResults.data
78
- .filter((d) => d.results)
79
- .map((r) => r.results
80
- .map((dep) => {
81
- dep.version = (dep.version.split('.').length >= 3 ? dep.version : dep.version + '.0').replace(/-/g, '.');
82
- let hashes;
83
- const filepath = r.file;
84
- const evidence = { occurrences: [] };
85
- if (filepath) {
86
- const file = fs.readFileSync(filepath);
87
- const relativePath = path.relative(process.cwd(), filepath);
88
- evidence.occurrences.push({ location: relativePath });
89
- hashes = [
90
- { alg: 'MD5', content: hash.md5(file) },
91
- { alg: 'SHA-1', content: hash.sha1(file) },
92
- { alg: 'SHA-256', content: hash.sha256(file) },
93
- { alg: 'SHA-512', content: hash.sha512(file) },
94
- ];
95
- }
96
- const purl = (0, utils_1.generatePURL)(dep);
97
- const existing = seen.get(purl);
98
- if (existing) {
99
- const missing = evidence.occurrences.filter((x) => !existing.evidence.occurrences.some((y) => y.location == x.location));
100
- existing.evidence.occurrences.push(...missing);
101
- return undefined;
102
- }
103
- const nameParts = dep.component.split('/').reverse();
104
- const bomRef = purl;
105
- dep.vulnerabilities?.forEach((vuln) => {
106
- // Pick valid identifiers for VEX
107
- const ids = vuln.identifiers?.CVE ?? (vuln.identifiers?.githubID ? [vuln.identifiers.githubID] : undefined);
108
- if (!ids)
109
- return;
110
- ids.forEach((id) => {
111
- if (!vulnerabilitiesCyclone.has(id)) {
112
- const { references, advisories } = mapUrls(vuln);
113
- vulnerabilitiesCyclone.set(id, {
114
- id,
115
- source: retireSource,
116
- cwes: vuln.cwe.map((c) => parseInt(c.split("-")[1])),
117
- description: vuln.identifiers.summary,
118
- advisories: advisories,
119
- references: references,
120
- ratings: [
121
- {
122
- source: retireSource,
123
- severity: vuln.severity,
124
- },
125
- ],
126
- affects: [],
127
- });
128
- }
129
- vulnerabilitiesCyclone.get(id).affects.push({
130
- ref: bomRef,
131
- versions: [
132
- {
133
- // "vers:npm/1.2.3|>=2.0.0|<5.0.0"
134
- range: 'vers:npm/' + (vuln.atOrAbove ? '>=' + vuln.atOrAbove + '|' : '') + '<' + vuln.below,
135
- status: 'affected',
136
- },
137
- ],
138
- });
139
- });
140
- });
141
- const result = {
142
- "bom-ref": bomRef,
143
- type: 'library',
144
- name: nameParts[0],
145
- group: nameParts[1],
146
- version: dep.version,
147
- purl: purl,
148
- hashes: hashes,
149
- evidence,
150
- licenses: mapLicenses(dep.licenses),
151
- };
152
- seen.set(purl, result);
153
- return result;
154
- })
155
- .filter((x) => x != undefined))
156
- .reduce((a, b) => a.concat(b), []);
157
- write(JSON.stringify({
158
- bomFormat: 'CycloneDX',
159
- specVersion: '1.6',
160
- serialNumber: `urn:uuid:${crypto.randomUUID()}`,
161
- version: 1,
162
- metadata: {
163
- timestamp: finalResults.start,
164
- tools: [
165
- {
166
- vendor: 'RetireJS',
167
- name: 'retire',
168
- version: retire.version,
169
- },
170
- ],
171
- properties: repositories.length
172
- ? repositories.map((repo) => ({
173
- name: 'retirejs:vulnerability-repository',
174
- value: repo,
175
- }))
176
- : undefined,
177
- },
178
- components: components,
179
- vulnerabilities: includeVEX ? Array.from(vulnerabilitiesCyclone.values()) : undefined,
180
- }, undefined, 2));
181
- writer.close(callback);
182
- };
183
- }
184
- function mapLicenses(licenses) {
185
- if (!licenses)
186
- return [];
187
- if (licenses.length == 0)
188
- return [];
189
- if (licenses[0] == 'commercial')
190
- return [{ license: { name: 'Commercial' } }];
191
- return [{ expression: licenses[0] }];
192
- }
193
- function mapUrls(vulnerability) {
194
- const references = [];
195
- if (vulnerability.identifiers.CVE) {
196
- const nvdlink = `https://nvd.nist.gov/vuln/detail/${vulnerability.identifiers.CVE[0]}`;
197
- references.push({ id: vulnerability.identifiers.CVE[0], source: { name: 'NVD', url: nvdlink } });
198
- }
199
- if (vulnerability.identifiers.githubID) {
200
- const ghsa = `https://github.com/advisories/${vulnerability.identifiers.githubID}`;
201
- references.push({ id: vulnerability.identifiers.githubID, source: { name: 'GitHub Advisories', url: ghsa } });
202
- }
203
- const advisories = vulnerability.info
204
- .filter((url) => !url.startsWith('https://nvd.nist.gov/vuln/detail/') && !url.startsWith('https://github.com/advisories/'))
205
- .map((u) => ({ url: u }));
206
- return { references, advisories };
207
- }
208
- exports.default = {
209
- configure: configureCycloneDXJSONLogger,
210
- };
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
+ }
@@ -65,7 +65,10 @@ function configureCycloneDXJSONLogger(logger, writer, config, hash) {
65
65
  };
66
66
  logger.close = function (callback) {
67
67
  const write = vulnsFound ? writer.err : writer.out;
68
- const repositories = (0, utils_1.vulnerabilityRepositories)(config.jsRepo);
68
+ const properties = (0, utils_1.vulnerabilityRepositories)(config.jsRepo).map((repo) => ({ name: 'retirejs:vulnerability-repository', value: repo }));
69
+ if (config.insecure) {
70
+ properties.push({ name: 'retirejs:ignore-repository-certificate-errors', value: 'true' });
71
+ }
69
72
  const seen = new Map();
70
73
  const components = finalResults.data
71
74
  .filter((d) => d.results)
@@ -123,12 +126,7 @@ function configureCycloneDXJSONLogger(logger, writer, config, hash) {
123
126
  version: retire.version,
124
127
  },
125
128
  ],
126
- properties: repositories.length
127
- ? repositories.map((repo) => ({
128
- name: 'retirejs:vulnerability-repository',
129
- value: repo,
130
- }))
131
- : undefined,
129
+ properties: properties.length ? properties : undefined,
132
130
  },
133
131
  components: components,
134
132
  }, undefined, 2));
@@ -65,9 +65,16 @@ function configureCycloneDXLogger(logger, writer, config, hash) {
65
65
  };
66
66
  logger.close = function (callback) {
67
67
  const write = vulnsFound ? writer.err : writer.out;
68
- const repositories = (0, utils_1.vulnerabilityRepositories)(config.jsRepo)
69
- .map((repo) => `
70
- <property name="retirejs:vulnerability-repository">${escapeXml(repo)}</property>`)
68
+ const propertyList = (0, utils_1.vulnerabilityRepositories)(config.jsRepo).map((repo) => ({
69
+ name: 'retirejs:vulnerability-repository',
70
+ value: repo,
71
+ }));
72
+ if (config.insecure) {
73
+ propertyList.push({ name: 'retirejs:ignore-repository-certificate-errors', value: 'true' });
74
+ }
75
+ const properties = propertyList
76
+ .map((property) => `
77
+ <property name="${escapeXml(property.name)}">${escapeXml(property.value)}</property>`)
71
78
  .join('');
72
79
  const seen = new Set();
73
80
  const components = finalResults.data
@@ -113,8 +120,8 @@ function configureCycloneDXLogger(logger, writer, config, hash) {
113
120
  <name>retire.js</name>
114
121
  <version>${retire.version}</version>
115
122
  </tool>
116
- </tools>${repositories ? `
117
- <properties>${repositories}
123
+ </tools>${properties ? `
124
+ <properties>${properties}
118
125
  </properties>` : ''}
119
126
  </metadata>
120
127
  <components>${components}
@@ -47,6 +47,7 @@ exports.default = {
47
47
  errors: [],
48
48
  time: undefined,
49
49
  vulnerabilityRepositories: (0, utils_1.vulnerabilityRepositories)(config.jsRepo),
50
+ ignoreRepositoryCertificateErrors: config.insecure,
50
51
  };
51
52
  logger.info = finalResults.messages.push;
52
53
  logger.debug = config.verbose
@@ -5,6 +5,7 @@ export type LoggerOptions = {
5
5
  colors: boolean;
6
6
  outputformat: string;
7
7
  jsRepo: string[];
8
+ insecure?: boolean;
8
9
  path: string;
9
10
  colorwarn: (s: string) => string;
10
11
  };
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.5.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.5.0",
5
+ "version": "5.7.0",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",