retire 5.6.0 → 6.0.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 +43 -0
- package/README.md +5 -2
- package/lib/cli.js +73 -103
- package/lib/depsdev.js +20 -20
- package/lib/http.d.ts +14 -0
- package/lib/http.js +122 -0
- package/lib/license.js +1 -1
- package/lib/parseIgnoreFile.d.ts +2 -0
- package/lib/parseIgnoreFile.js +36 -0
- package/lib/repo.js +34 -34
- package/lib/reporters/cyclonedx-1_6-json.d.ts +1 -2
- package/lib/reporters/cyclonedx-1_6-json.js +2 -207
- package/lib/reporters/cyclonedx-1_7-json.d.ts +2 -0
- package/lib/reporters/cyclonedx-1_7-json.js +4 -0
- package/lib/reporters/cyclonedx-json-builder.d.ts +7 -0
- package/lib/reporters/cyclonedx-json-builder.js +281 -0
- package/lib/reporters/cyclonedx-json.js +8 -2
- package/lib/reporters/cyclonedx.js +9 -8
- package/lib/reporting.js +3 -0
- package/lib/resolve.js +1 -1
- package/lib/retire.js +1 -1
- package/lib/scanner.d.ts +2 -2
- package/lib/scanner.js +21 -15
- package/lib/types.d.ts +27 -0
- package/lib/types.js +19 -1
- package/package.json +11 -11
|
@@ -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
|
|
37
|
-
|
|
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,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,281 @@
|
|
|
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 = {
|
|
106
|
+
...dep,
|
|
107
|
+
version: (dep.version.split('.').length >= 3 ? dep.version : dep.version + '.0').replace(/-/g, '.'),
|
|
108
|
+
};
|
|
109
|
+
let hashes;
|
|
110
|
+
const filepath = r.file;
|
|
111
|
+
const evidence = { occurrences: [] };
|
|
112
|
+
if (filepath) {
|
|
113
|
+
const file = fs.readFileSync(filepath);
|
|
114
|
+
const relativePath = path.relative(process.cwd(), filepath);
|
|
115
|
+
evidence.occurrences.push({ location: relativePath });
|
|
116
|
+
hashes = [
|
|
117
|
+
{ alg: 'MD5', content: hash.md5(file) },
|
|
118
|
+
{ alg: 'SHA-1', content: hash.sha1(file) },
|
|
119
|
+
{ alg: 'SHA-256', content: hash.sha256(file) },
|
|
120
|
+
{ alg: 'SHA-512', content: hash.sha512(file) },
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
const purl = (0, utils_1.generatePURL)(dep);
|
|
124
|
+
const existing = seen.get(purl);
|
|
125
|
+
const identity = mapIdentity(dep);
|
|
126
|
+
if (identity)
|
|
127
|
+
evidence.identity = [identity];
|
|
128
|
+
const nameParts = dep.component.split('/').reverse();
|
|
129
|
+
const bomRef = purl;
|
|
130
|
+
dep.vulnerabilities?.forEach((vuln) => {
|
|
131
|
+
// Pick valid identifiers for VEX
|
|
132
|
+
const ids = vuln.identifiers?.CVE ?? (vuln.identifiers?.githubID ? [vuln.identifiers.githubID] : undefined);
|
|
133
|
+
if (!ids)
|
|
134
|
+
return;
|
|
135
|
+
ids.forEach((id) => {
|
|
136
|
+
if (!vulnerabilitiesCyclone.has(id)) {
|
|
137
|
+
const { references, advisories } = mapUrls(vuln);
|
|
138
|
+
const cwes = vuln.cwe?.map((c) => parseInt(c.split('-')[1]));
|
|
139
|
+
vulnerabilitiesCyclone.set(id, {
|
|
140
|
+
id,
|
|
141
|
+
source: retireSource,
|
|
142
|
+
cwes: cwes?.length ? cwes : undefined,
|
|
143
|
+
description: vuln.identifiers?.summary,
|
|
144
|
+
detail: vuln.details,
|
|
145
|
+
advisories: advisories,
|
|
146
|
+
references: references,
|
|
147
|
+
ratings: [
|
|
148
|
+
{
|
|
149
|
+
source: retireSource,
|
|
150
|
+
severity: vuln.severity,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
affects: [],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const affects = vulnerabilitiesCyclone.get(id).affects;
|
|
157
|
+
let affected = affects.find((entry) => entry.ref === bomRef);
|
|
158
|
+
if (!affected) {
|
|
159
|
+
affected = { ref: bomRef, versions: [] };
|
|
160
|
+
affects.push(affected);
|
|
161
|
+
}
|
|
162
|
+
const range = 'vers:npm/' + (vuln.atOrAbove ? '>=' + vuln.atOrAbove + '|' : '') + '<' + vuln.below;
|
|
163
|
+
if (!affected.versions.some((entry) => entry.range === range)) {
|
|
164
|
+
affected.versions.push({ range, status: 'affected' });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
if (existing) {
|
|
169
|
+
const missing = evidence.occurrences.filter((x) => !existing.evidence.occurrences.some((y) => y.location == x.location));
|
|
170
|
+
existing.evidence.occurrences.push(...missing);
|
|
171
|
+
if (identity) {
|
|
172
|
+
const identities = existing.evidence.identity ?? (existing.evidence.identity = []);
|
|
173
|
+
if (!identities.some((entry) => entry.methods[0].value === identity.methods[0].value)) {
|
|
174
|
+
identities.push(identity);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const result = {
|
|
180
|
+
'bom-ref': bomRef,
|
|
181
|
+
type: 'library',
|
|
182
|
+
name: nameParts[0],
|
|
183
|
+
group: nameParts[1],
|
|
184
|
+
version: dep.version,
|
|
185
|
+
purl: purl,
|
|
186
|
+
hashes: hashes,
|
|
187
|
+
evidence,
|
|
188
|
+
licenses: mapLicenses(dep.licenses, specVersion),
|
|
189
|
+
};
|
|
190
|
+
seen.set(purl, result);
|
|
191
|
+
return result;
|
|
192
|
+
})
|
|
193
|
+
.filter((x) => x != undefined))
|
|
194
|
+
.reduce((a, b) => a.concat(b), []);
|
|
195
|
+
const dependencies = [
|
|
196
|
+
{ ref: SCAN_TARGET_BOM_REF, dependsOn: components.map((c) => c['bom-ref']) },
|
|
197
|
+
...components.map((c) => ({ ref: c['bom-ref'], dependsOn: [] })),
|
|
198
|
+
];
|
|
199
|
+
write(JSON.stringify({
|
|
200
|
+
$schema: `http://cyclonedx.org/schema/bom-${specVersion}.schema.json`,
|
|
201
|
+
bomFormat: 'CycloneDX',
|
|
202
|
+
specVersion,
|
|
203
|
+
serialNumber: `urn:uuid:${crypto.randomUUID()}`,
|
|
204
|
+
version: 1,
|
|
205
|
+
metadata: {
|
|
206
|
+
timestamp: finalResults.start,
|
|
207
|
+
tools: {
|
|
208
|
+
components: [
|
|
209
|
+
{
|
|
210
|
+
type: 'application',
|
|
211
|
+
'bom-ref': TOOL_BOM_REF,
|
|
212
|
+
group: 'RetireJS',
|
|
213
|
+
name: 'retire',
|
|
214
|
+
version: retire.version,
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
component: {
|
|
219
|
+
type: 'application',
|
|
220
|
+
'bom-ref': SCAN_TARGET_BOM_REF,
|
|
221
|
+
name: scanTargetName(config.path),
|
|
222
|
+
},
|
|
223
|
+
properties: properties.length ? properties : undefined,
|
|
224
|
+
},
|
|
225
|
+
components: components,
|
|
226
|
+
dependencies,
|
|
227
|
+
vulnerabilities: includeVEX ? Array.from(vulnerabilitiesCyclone.values()) : undefined,
|
|
228
|
+
}, undefined, 2));
|
|
229
|
+
writer.close(callback);
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function scanTargetName(scanPath) {
|
|
233
|
+
return path.basename(path.resolve(scanPath ?? '.'));
|
|
234
|
+
}
|
|
235
|
+
function mapIdentity(component) {
|
|
236
|
+
if (!component.detection)
|
|
237
|
+
return undefined;
|
|
238
|
+
const mapped = identityTechniques[component.detection];
|
|
239
|
+
if (!mapped)
|
|
240
|
+
return undefined;
|
|
241
|
+
return {
|
|
242
|
+
field: 'version',
|
|
243
|
+
confidence: mapped.confidence,
|
|
244
|
+
concludedValue: component.version,
|
|
245
|
+
methods: [{ technique: mapped.technique, confidence: mapped.confidence, value: component.detection }],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function mapLicense(license) {
|
|
249
|
+
return license == 'commercial' ? { license: { name: 'Commercial' } } : { expression: license };
|
|
250
|
+
}
|
|
251
|
+
function mapLicenses(licenses, specVersion) {
|
|
252
|
+
if (!licenses)
|
|
253
|
+
return [];
|
|
254
|
+
if (licenses.length == 0)
|
|
255
|
+
return [];
|
|
256
|
+
// Before 1.7, the licenses array could hold at most one SPDX expression, and expressions
|
|
257
|
+
// could not be mixed with named licenses.
|
|
258
|
+
if (specVersion === '1.6')
|
|
259
|
+
return [mapLicense(licenses[0])];
|
|
260
|
+
return licenses.map(mapLicense);
|
|
261
|
+
}
|
|
262
|
+
function mapUrls(vulnerability) {
|
|
263
|
+
const references = [];
|
|
264
|
+
if (vulnerability.identifiers?.CVE) {
|
|
265
|
+
const nvdlink = `https://nvd.nist.gov/vuln/detail/${vulnerability.identifiers.CVE[0]}`;
|
|
266
|
+
references.push({ id: vulnerability.identifiers.CVE[0], source: { name: 'NVD', url: nvdlink } });
|
|
267
|
+
}
|
|
268
|
+
if (vulnerability.identifiers?.githubID) {
|
|
269
|
+
const ghsa = `https://github.com/advisories/${vulnerability.identifiers.githubID}`;
|
|
270
|
+
references.push({ id: vulnerability.identifiers.githubID, source: { name: 'GitHub Advisories', url: ghsa } });
|
|
271
|
+
}
|
|
272
|
+
const advisories = vulnerability.info
|
|
273
|
+
.filter((url) => !url.startsWith('https://nvd.nist.gov/vuln/detail/') && !url.startsWith('https://github.com/advisories/'))
|
|
274
|
+
.map((u) => ({ url: u }));
|
|
275
|
+
return { references, advisories };
|
|
276
|
+
}
|
|
277
|
+
function cycloneDXJSONLogger(specVersion) {
|
|
278
|
+
return {
|
|
279
|
+
configure: (logger, writer, config, hash) => configureCycloneDXJSONLogger(specVersion, logger, writer, config, hash),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
@@ -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 properties = (0, utils_1.vulnerabilityRepositories)(config.jsRepo).map((repo) => ({
|
|
68
|
+
const properties = (0, utils_1.vulnerabilityRepositories)(config.jsRepo).map((repo) => ({
|
|
69
|
+
name: 'retirejs:vulnerability-repository',
|
|
70
|
+
value: repo,
|
|
71
|
+
}));
|
|
69
72
|
if (config.insecure) {
|
|
70
73
|
properties.push({ name: 'retirejs:ignore-repository-certificate-errors', value: 'true' });
|
|
71
74
|
}
|
|
@@ -74,7 +77,10 @@ function configureCycloneDXJSONLogger(logger, writer, config, hash) {
|
|
|
74
77
|
.filter((d) => d.results)
|
|
75
78
|
.map((r) => r.results
|
|
76
79
|
.map((dep) => {
|
|
77
|
-
dep
|
|
80
|
+
dep = {
|
|
81
|
+
...dep,
|
|
82
|
+
version: (dep.version.split('.').length >= 3 ? dep.version : dep.version + '.0').replace(/-/g, '.'),
|
|
83
|
+
};
|
|
78
84
|
let hashes;
|
|
79
85
|
const filepath = r.file;
|
|
80
86
|
const properties = [];
|
|
@@ -81,7 +81,10 @@ function configureCycloneDXLogger(logger, writer, config, hash) {
|
|
|
81
81
|
.filter((d) => d.results.length > 0)
|
|
82
82
|
.map((r) => r.results
|
|
83
83
|
.map((dep) => {
|
|
84
|
-
dep
|
|
84
|
+
dep = {
|
|
85
|
+
...dep,
|
|
86
|
+
version: (dep.version.split('.').length >= 3 ? dep.version : dep.version + '.0').replace(/-/g, '.'),
|
|
87
|
+
};
|
|
85
88
|
const filepath = r.file;
|
|
86
89
|
let hashes = '';
|
|
87
90
|
if (filepath) {
|
|
@@ -120,9 +123,11 @@ function configureCycloneDXLogger(logger, writer, config, hash) {
|
|
|
120
123
|
<name>retire.js</name>
|
|
121
124
|
<version>${retire.version}</version>
|
|
122
125
|
</tool>
|
|
123
|
-
</tools>${properties
|
|
126
|
+
</tools>${properties
|
|
127
|
+
? `
|
|
124
128
|
<properties>${properties}
|
|
125
|
-
</properties>`
|
|
129
|
+
</properties>`
|
|
130
|
+
: ''}
|
|
126
131
|
</metadata>
|
|
127
132
|
<components>${components}
|
|
128
133
|
</components>
|
|
@@ -131,11 +136,7 @@ function configureCycloneDXLogger(logger, writer, config, hash) {
|
|
|
131
136
|
};
|
|
132
137
|
}
|
|
133
138
|
function escapeXml(value) {
|
|
134
|
-
return value
|
|
135
|
-
.replace(/&/g, '&')
|
|
136
|
-
.replace(/</g, '<')
|
|
137
|
-
.replace(/>/g, '>')
|
|
138
|
-
.replace(/"/g, '"');
|
|
139
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
139
140
|
}
|
|
140
141
|
function mapLicenses(licenses) {
|
|
141
142
|
if (!licenses)
|
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/resolve.js
CHANGED
package/lib/retire.js
CHANGED
package/lib/scanner.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Finding, Options, Repository } from './types';
|
|
2
|
-
export declare function scanJsFile(file: string, repo: Repository, options: Options): void
|
|
3
|
-
export declare function scanBowerFile(file: string, repo: Repository, options: Options): void
|
|
2
|
+
export declare function scanJsFile(file: string, repo: Repository, options: Options): Promise<void>;
|
|
3
|
+
export declare function scanBowerFile(file: string, repo: Repository, options: Options): Promise<void>;
|
|
4
4
|
export declare function on(event: 'vulnerable-dependency-found' | 'dependency-found', handler: (finding: Finding) => void): void;
|