cra-audit 1.0.0 → 2.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 +61 -0
- package/README.md +60 -10
- package/package.json +16 -2
- package/src/cli.js +10 -2
- package/src/commands/audit.js +6 -3
- package/src/commands/sbom.js +17 -6
- package/src/commands/visualize.js +8 -4
- package/src/core/auditor.js +63 -10
- package/src/core/installed-metadata.js +179 -0
- package/src/core/kev.js +44 -0
- package/src/core/license-checker.js +2 -27
- package/src/core/lockfile-parser.js +306 -67
- package/src/core/osv.js +211 -0
- package/src/core/policy.js +8 -0
- package/src/core/sbom-generator.js +330 -146
- package/src/core/sbom-validator.js +228 -124
- package/src/core/vuln-scanner.js +161 -11
- package/src/index.js +1 -1
- package/src/reporters/console.js +55 -13
- package/src/reporters/json.js +1 -1
- package/src/utils/http.js +11 -4
|
@@ -1,146 +1,330 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const crypto = require('node:crypto');
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { readJson } = require('../utils/fs');
|
|
6
|
+
const { parseLockfile, buildPurl, componentKey } = require('./lockfile-parser');
|
|
7
|
+
const {
|
|
8
|
+
readInstalledManifest, licenseFromManifest, creatorFromManifest, repositoryUrl,
|
|
9
|
+
} = require('./installed-metadata');
|
|
10
|
+
|
|
11
|
+
const TOOL_NAME = 'cra-audit';
|
|
12
|
+
const TOOL_VERSION = require('../../package.json').version;
|
|
13
|
+
const TOOL_URL = 'https://github.com/migohe14/cra-audit';
|
|
14
|
+
const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generates a Software Bill of Materials from the project lockfile.
|
|
18
|
+
* Supported formats: CycloneDX 1.6 (JSON) and SPDX 2.3 (JSON).
|
|
19
|
+
* Required by CRA Annex I and BSI TR-03183 Part 2.
|
|
20
|
+
*
|
|
21
|
+
* The CycloneDX output follows the field mapping of TR-03183-2 v2.1.0
|
|
22
|
+
* (appendix 8.5): SBOM creator in `metadata.manufacturer`, component creator
|
|
23
|
+
* in `manufacturer`, SHA-512 of the deployable tarball on the `distribution`
|
|
24
|
+
* reference, the `bsi:component:*` properties, a full `dependencies` graph and
|
|
25
|
+
* its completeness in `compositions`.
|
|
26
|
+
*
|
|
27
|
+
* Fields a lockfile does not carry (licenses for Yarn/pnpm, component
|
|
28
|
+
* creators) are read from the installed packages in node_modules.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} projectRoot
|
|
31
|
+
* @param {{ format?: string, creator?: string }} [options]
|
|
32
|
+
* `creator`: email or URL of the SBOM creator. Defaults to the project's
|
|
33
|
+
* package.json (`author`, `homepage`, `repository`).
|
|
34
|
+
*/
|
|
35
|
+
function generateSbom(projectRoot, { format = 'cyclonedx', creator } = {}) {
|
|
36
|
+
const parsed = parseLockfile(projectRoot);
|
|
37
|
+
if (!parsed.ok) {
|
|
38
|
+
return { ok: false, error: parsed.error };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const rootPkg = readJson(path.join(projectRoot, 'package.json')) || {};
|
|
42
|
+
const product = {
|
|
43
|
+
...parsed.root,
|
|
44
|
+
purl: buildPurl(parsed.root.name, parsed.root.version),
|
|
45
|
+
creator: creatorFromManifest(rootPkg),
|
|
46
|
+
license: licenseFromManifest(rootPkg),
|
|
47
|
+
sourceUrl: repositoryUrl(rootPkg.repository),
|
|
48
|
+
};
|
|
49
|
+
const sbomCreator = parseCreatorOption(creator) || product.creator;
|
|
50
|
+
const components = parsed.components.map((c) => describeComponent(projectRoot, c));
|
|
51
|
+
|
|
52
|
+
const normalizedFormat = String(format).toLowerCase();
|
|
53
|
+
let document;
|
|
54
|
+
if (normalizedFormat === 'spdx') {
|
|
55
|
+
document = buildSpdx(product, components, sbomCreator);
|
|
56
|
+
} else if (normalizedFormat === 'cyclonedx' || normalizedFormat === 'cdx') {
|
|
57
|
+
document = buildCycloneDx(product, components, sbomCreator, parsed.dependencyGraphComplete);
|
|
58
|
+
} else {
|
|
59
|
+
return { ok: false, error: `Unsupported SBOM format: "${format}". Use "cyclonedx" or "spdx".` };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
ok: true,
|
|
64
|
+
format: normalizedFormat === 'spdx' ? 'spdx' : 'cyclonedx',
|
|
65
|
+
componentCount: components.length,
|
|
66
|
+
document,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Adds the TR-03183 fields that come from the installed package. */
|
|
71
|
+
function describeComponent(projectRoot, c) {
|
|
72
|
+
const manifest = readInstalledManifest(projectRoot, c);
|
|
73
|
+
return {
|
|
74
|
+
...c,
|
|
75
|
+
key: componentKey(c.name, c.version),
|
|
76
|
+
license: c.license || licenseFromManifest(manifest),
|
|
77
|
+
creator: creatorFromManifest(manifest),
|
|
78
|
+
sourceUrl: manifest ? repositoryUrl(manifest.repository) : null,
|
|
79
|
+
downloadUrl: c.resolved && /^https?:\/\//.test(c.resolved) ? c.resolved.split('#')[0] : registryTarballUrl(c),
|
|
80
|
+
filename: tarballFilename(c),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Accepts an email address or an http(s) URL for the SBOM creator. */
|
|
85
|
+
function parseCreatorOption(value) {
|
|
86
|
+
if (!value || typeof value !== 'string') return null;
|
|
87
|
+
const v = value.trim();
|
|
88
|
+
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) return { name: null, email: v, url: null };
|
|
89
|
+
if (/^https?:\/\//i.test(v)) return { name: null, email: null, url: v };
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- CycloneDX 1.6 ---------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
function buildCycloneDx(product, components, sbomCreator, graphComplete) {
|
|
96
|
+
const refByKey = new Map(components.map((c) => [c.key, bomRef(c)]));
|
|
97
|
+
const rootRef = product.purl || `${product.name}@${product.version}`;
|
|
98
|
+
let complete = graphComplete;
|
|
99
|
+
|
|
100
|
+
const refsFor = (keys) => keys.map((key) => {
|
|
101
|
+
const ref = refByKey.get(key);
|
|
102
|
+
if (!ref) complete = false; // an edge points outside the SBOM
|
|
103
|
+
return ref;
|
|
104
|
+
}).filter(Boolean);
|
|
105
|
+
|
|
106
|
+
const rootComponent = {
|
|
107
|
+
type: 'application',
|
|
108
|
+
'bom-ref': rootRef,
|
|
109
|
+
name: product.name,
|
|
110
|
+
version: product.version,
|
|
111
|
+
purl: product.purl || undefined,
|
|
112
|
+
manufacturer: cdxEntity(product.creator),
|
|
113
|
+
licenses: cdxLicenses(product.license),
|
|
114
|
+
externalReferences: product.sourceUrl
|
|
115
|
+
? [{ type: 'source-distribution', url: product.sourceUrl }]
|
|
116
|
+
: undefined,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const cdxComponents = components.map((c) => {
|
|
120
|
+
const hashes = c.hashAlgorithm && c.hashValue ? [{ alg: c.hashAlgorithm, content: c.hashValue }] : undefined;
|
|
121
|
+
const externalReferences = [];
|
|
122
|
+
if (c.downloadUrl) externalReferences.push({ type: 'distribution', url: c.downloadUrl, hashes });
|
|
123
|
+
if (c.sourceUrl) externalReferences.push({ type: 'source-distribution', url: c.sourceUrl });
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
type: 'library',
|
|
127
|
+
'bom-ref': bomRef(c),
|
|
128
|
+
name: c.name,
|
|
129
|
+
version: c.version || undefined,
|
|
130
|
+
scope: c.scope,
|
|
131
|
+
purl: c.purl || undefined,
|
|
132
|
+
manufacturer: cdxEntity(c.creator),
|
|
133
|
+
hashes,
|
|
134
|
+
licenses: cdxLicenses(c.license),
|
|
135
|
+
externalReferences: externalReferences.length ? externalReferences : undefined,
|
|
136
|
+
// An npm package is a gzipped tarball: a structured, non-executable archive.
|
|
137
|
+
properties: [
|
|
138
|
+
{ name: 'bsi:component:filename', value: c.filename },
|
|
139
|
+
{ name: 'bsi:component:executable', value: 'non-executable' },
|
|
140
|
+
{ name: 'bsi:component:archive', value: 'archive' },
|
|
141
|
+
{ name: 'bsi:component:structured', value: 'structured' },
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Components without dependencies MUST still appear with an empty dependsOn.
|
|
147
|
+
const dependencies = [
|
|
148
|
+
{ ref: rootRef, dependsOn: refsFor(product.dependsOn) },
|
|
149
|
+
...components.map((c) => ({ ref: bomRef(c), dependsOn: refsFor(c.dependsOn) })),
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
bomFormat: 'CycloneDX',
|
|
154
|
+
specVersion: '1.6',
|
|
155
|
+
serialNumber: `urn:uuid:${crypto.randomUUID()}`,
|
|
156
|
+
version: 1,
|
|
157
|
+
metadata: {
|
|
158
|
+
timestamp: new Date().toISOString(),
|
|
159
|
+
tools: {
|
|
160
|
+
components: [{
|
|
161
|
+
type: 'application',
|
|
162
|
+
name: TOOL_NAME,
|
|
163
|
+
version: TOOL_VERSION,
|
|
164
|
+
externalReferences: [{ type: 'vcs', url: TOOL_URL }],
|
|
165
|
+
}],
|
|
166
|
+
},
|
|
167
|
+
manufacturer: cdxEntity(sbomCreator),
|
|
168
|
+
component: rootComponent,
|
|
169
|
+
},
|
|
170
|
+
components: cdxComponents,
|
|
171
|
+
dependencies,
|
|
172
|
+
compositions: [{
|
|
173
|
+
aggregate: complete ? 'complete' : 'incomplete',
|
|
174
|
+
dependencies: dependencies.map((d) => d.ref),
|
|
175
|
+
}],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function bomRef(c) {
|
|
180
|
+
return c.purl || c.key;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** TR-03183 asks for an email address, or a URL when there is none. */
|
|
184
|
+
function cdxEntity(creator) {
|
|
185
|
+
if (!creator) return undefined;
|
|
186
|
+
const entity = {};
|
|
187
|
+
if (creator.name) entity.name = creator.name;
|
|
188
|
+
if (creator.email) entity.contact = [{ email: creator.email }];
|
|
189
|
+
else if (creator.url) entity.url = [creator.url];
|
|
190
|
+
else return undefined;
|
|
191
|
+
return entity;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* License choice per TR-03183 appendix 8.5: the licence assigned by the
|
|
196
|
+
* component creator ("declared", the original licence) and the one it is
|
|
197
|
+
* distributed under ("concluded"). For npm packages both are the package.json
|
|
198
|
+
* license. SPDX expressions can only be given once, as the concluded licence.
|
|
199
|
+
*/
|
|
200
|
+
function cdxLicenses(license) {
|
|
201
|
+
if (!license) return undefined;
|
|
202
|
+
const value = String(license).trim();
|
|
203
|
+
if (/\s(AND|OR|WITH)\s|[()]/i.test(value)) {
|
|
204
|
+
return [{ expression: value, acknowledgement: 'concluded' }];
|
|
205
|
+
}
|
|
206
|
+
// Unknown ids go in `name`: CycloneDX validates `id` against the SPDX list.
|
|
207
|
+
const license_ = SPDX_IDS.has(value) ? { id: value } : { name: value };
|
|
208
|
+
return [
|
|
209
|
+
{ license: { ...license_, acknowledgement: 'declared' } },
|
|
210
|
+
{ license: { ...license_, acknowledgement: 'concluded' } },
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// --- SPDX 2.3 --------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
function buildSpdx(product, components, sbomCreator) {
|
|
217
|
+
const rootSpdxId = 'SPDXRef-Package-root';
|
|
218
|
+
const idByKey = new Map(components.map((c, index) => [c.key, `SPDXRef-Package-${index}`]));
|
|
219
|
+
|
|
220
|
+
const packages = components.map((c) => {
|
|
221
|
+
const pkg = {
|
|
222
|
+
name: c.name,
|
|
223
|
+
SPDXID: idByKey.get(c.key),
|
|
224
|
+
versionInfo: c.version || 'NOASSERTION',
|
|
225
|
+
packageFileName: c.filename,
|
|
226
|
+
originator: spdxActor(c.creator),
|
|
227
|
+
downloadLocation: c.downloadUrl || 'NOASSERTION',
|
|
228
|
+
filesAnalyzed: false,
|
|
229
|
+
licenseConcluded: c.license || 'NOASSERTION',
|
|
230
|
+
licenseDeclared: c.license || 'NOASSERTION',
|
|
231
|
+
copyrightText: 'NOASSERTION',
|
|
232
|
+
externalRefs: c.purl
|
|
233
|
+
? [{
|
|
234
|
+
referenceCategory: 'PACKAGE-MANAGER',
|
|
235
|
+
referenceType: 'purl',
|
|
236
|
+
referenceLocator: c.purl,
|
|
237
|
+
}]
|
|
238
|
+
: undefined,
|
|
239
|
+
};
|
|
240
|
+
if (c.hashAlgorithm && c.hashValue) {
|
|
241
|
+
pkg.checksums = [{ algorithm: c.hashAlgorithm.replace('-', ''), checksumValue: c.hashValue }];
|
|
242
|
+
}
|
|
243
|
+
return pkg;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
packages.unshift({
|
|
247
|
+
name: product.name,
|
|
248
|
+
SPDXID: rootSpdxId,
|
|
249
|
+
versionInfo: product.version,
|
|
250
|
+
originator: spdxActor(product.creator),
|
|
251
|
+
downloadLocation: 'NOASSERTION',
|
|
252
|
+
filesAnalyzed: false,
|
|
253
|
+
licenseConcluded: product.license || 'NOASSERTION',
|
|
254
|
+
licenseDeclared: product.license || 'NOASSERTION',
|
|
255
|
+
copyrightText: 'NOASSERTION',
|
|
256
|
+
externalRefs: product.purl
|
|
257
|
+
? [{ referenceCategory: 'PACKAGE-MANAGER', referenceType: 'purl', referenceLocator: product.purl }]
|
|
258
|
+
: undefined,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const relationships = [{
|
|
262
|
+
spdxElementId: 'SPDXRef-DOCUMENT',
|
|
263
|
+
relationshipType: 'DESCRIBES',
|
|
264
|
+
relatedSpdxElement: rootSpdxId,
|
|
265
|
+
}];
|
|
266
|
+
const addEdges = (fromId, keys) => {
|
|
267
|
+
for (const key of keys) {
|
|
268
|
+
const to = idByKey.get(key);
|
|
269
|
+
if (to) relationships.push({ spdxElementId: fromId, relationshipType: 'DEPENDS_ON', relatedSpdxElement: to });
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
addEdges(rootSpdxId, product.dependsOn);
|
|
273
|
+
for (const c of components) addEdges(idByKey.get(c.key), c.dependsOn);
|
|
274
|
+
|
|
275
|
+
const creators = [`Tool: ${TOOL_NAME}-${TOOL_VERSION}`];
|
|
276
|
+
const creatorActor = spdxActor(sbomCreator);
|
|
277
|
+
if (creatorActor) creators.unshift(creatorActor);
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
spdxVersion: 'SPDX-2.3',
|
|
281
|
+
dataLicense: 'CC0-1.0',
|
|
282
|
+
SPDXID: 'SPDXRef-DOCUMENT',
|
|
283
|
+
name: `${product.name}@${product.version}`,
|
|
284
|
+
documentNamespace: `https://spdx.org/spdxdocs/${product.name}-${crypto.randomUUID()}`,
|
|
285
|
+
creationInfo: {
|
|
286
|
+
created: new Date().toISOString(),
|
|
287
|
+
creators,
|
|
288
|
+
},
|
|
289
|
+
documentDescribes: [rootSpdxId],
|
|
290
|
+
packages,
|
|
291
|
+
relationships,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** SPDX 2.3 actor: "Organization: name (email)". */
|
|
296
|
+
function spdxActor(creator) {
|
|
297
|
+
if (!creator) return undefined;
|
|
298
|
+
const label = creator.name || creator.url || creator.email;
|
|
299
|
+
return `Organization: ${label} (${creator.email || ''})`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// --- npm tarball helpers ---------------------------------------------------
|
|
303
|
+
|
|
304
|
+
/** The actual filename of the deployable component (TR-03183 §5.2.2). */
|
|
305
|
+
function tarballFilename(c) {
|
|
306
|
+
if (c.resolved && /^https?:\/\//.test(c.resolved)) {
|
|
307
|
+
const base = c.resolved.split(/[?#]/)[0].split('/').pop();
|
|
308
|
+
if (base) return base;
|
|
309
|
+
}
|
|
310
|
+
return `${c.name.split('/').pop()}-${c.version}.tgz`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Canonical npm registry URL, used when the lockfile does not record one. */
|
|
314
|
+
function registryTarballUrl(c) {
|
|
315
|
+
if (!c.name || !c.version) return null;
|
|
316
|
+
return `${NPM_REGISTRY}/${c.name}/-/${c.name.split('/').pop()}-${c.version}.tgz`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Common SPDX license ids that are safe to emit as CycloneDX `license.id`. */
|
|
320
|
+
const SPDX_IDS = new Set([
|
|
321
|
+
'0BSD', 'AFL-2.1', 'AFL-3.0', 'AGPL-3.0', 'AGPL-3.0-only', 'AGPL-3.0-or-later', 'Apache-1.1',
|
|
322
|
+
'Apache-2.0', 'Artistic-2.0', 'BlueOak-1.0.0', 'BSD-2-Clause', 'BSD-3-Clause', 'BSL-1.0',
|
|
323
|
+
'CC-BY-3.0', 'CC-BY-4.0', 'CC-BY-SA-4.0', 'CC0-1.0', 'CDDL-1.0', 'CDDL-1.1', 'EPL-1.0', 'EPL-2.0',
|
|
324
|
+
'EUPL-1.1', 'EUPL-1.2', 'GPL-2.0', 'GPL-2.0-only', 'GPL-2.0-or-later', 'GPL-3.0', 'GPL-3.0-only',
|
|
325
|
+
'GPL-3.0-or-later', 'ISC', 'LGPL-2.1', 'LGPL-2.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0',
|
|
326
|
+
'LGPL-3.0-only', 'LGPL-3.0-or-later', 'MIT', 'MIT-0', 'MPL-1.1', 'MPL-2.0', 'OFL-1.1', 'Python-2.0',
|
|
327
|
+
'Unicode-3.0', 'Unicode-DFS-2016', 'Unlicense', 'UPL-1.0', 'W3C', 'WTFPL', 'Zlib',
|
|
328
|
+
]);
|
|
329
|
+
|
|
330
|
+
module.exports = { generateSbom };
|