@cyclonedx/cdxgen 9.5.0 → 9.6.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/README.md +32 -29
- package/analyzer.js +115 -73
- package/bin/cdxgen.js +19 -2
- package/bin/evinse.js +3 -1
- package/bin/repl.js +2 -0
- package/bin/verify.js +23 -1
- package/display.js +15 -2
- package/evinser.js +303 -92
- package/evinser.test.js +59 -1
- package/index.js +156 -77
- package/package.json +2 -2
- package/utils.js +232 -21
- package/utils.test.js +33 -2
package/README.md
CHANGED
|
@@ -282,39 +282,42 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
|
|
|
282
282
|
- pnpm-lock.yaml
|
|
283
283
|
- Maven (pom.xml)
|
|
284
284
|
- Gradle
|
|
285
|
+
- Scala SBT
|
|
285
286
|
- Python (requirements.txt, setup.py, pyproject.toml, poetry.lock)
|
|
286
287
|
|
|
287
288
|
## Environment variables
|
|
288
289
|
|
|
289
|
-
| Variable | Description
|
|
290
|
-
| ---------------------------- |
|
|
291
|
-
| CDXGEN_DEBUG_MODE | Set to `debug` to enable debug messages
|
|
292
|
-
| GITHUB_TOKEN | Specify GitHub token to prevent traffic shaping while querying license and repo information
|
|
293
|
-
| MVN_CMD | Set to override maven command
|
|
294
|
-
| MVN_ARGS | Set to pass additional arguments such as profile or settings to maven
|
|
295
|
-
| MAVEN_HOME | Specify maven home
|
|
296
|
-
| GRADLE_CACHE_DIR | Specify gradle cache directory. Useful for class name resolving
|
|
297
|
-
| GRADLE_MULTI_PROJECT_MODE | Unused. Automatically handled
|
|
298
|
-
| GRADLE_ARGS | Set to pass additional arguments such as profile or settings to gradle (all tasks). Eg: --configuration runtimeClassPath
|
|
299
|
-
| GRADLE_ARGS_PROPERTIES | Set to pass additional arguments only to the `gradle properties` task, used for collecting metadata about the project
|
|
300
|
-
| GRADLE_ARGS_DEPENDENCIES | Set to pass additional arguments only to the `gradle dependencies` task, used for listing actual project dependencies
|
|
301
|
-
| GRADLE_HOME | Specify gradle home
|
|
302
|
-
| GRADLE_CMD | Set to override gradle command
|
|
303
|
-
| GRADLE_DEPENDENCY_TASK | By default cdxgen use the task "dependencies" to collect packages. Set to override the task name.
|
|
304
|
-
| SBT_CACHE_DIR | Specify sbt cache directory. Useful for class name resolving
|
|
305
|
-
| FETCH_LICENSE | Set this variable to `true` or `1` to fetch license information from the registry. npm and golang
|
|
306
|
-
| USE_GOSUM | Set to `true` or `1` to generate BOMs for golang projects using go.sum as the dependency source of truth, instead of go.mod
|
|
307
|
-
| CDXGEN_TIMEOUT_MS | Default timeout for known execution involving maven, gradle or sbt
|
|
308
|
-
| CDXGEN_SERVER_TIMEOUT_MS | Default timeout in server mode
|
|
309
|
-
| BAZEL_TARGET | Bazel target to build. Default :all (Eg: //java-maven)
|
|
310
|
-
| CLJ_CMD | Set to override the clojure cli command
|
|
311
|
-
| LEIN_CMD | Set to override the leiningen command
|
|
312
|
-
| SBOM_SIGN_ALGORITHM | Signature algorithm. Some valid values are RS256, RS384, RS512, PS256, PS384, PS512, ES256 etc
|
|
313
|
-
| SBOM_SIGN_PRIVATE_KEY | Private key to use for signing
|
|
314
|
-
| SBOM_SIGN_PUBLIC_KEY | Optional. Public key to include in the SBoM signature
|
|
315
|
-
| CDX_MAVEN_PLUGIN | CycloneDX Maven plugin to use. Default "org.cyclonedx:cyclonedx-maven-plugin:2.7.8"
|
|
316
|
-
| CDX_MAVEN_GOAL | CycloneDX Maven plugin goal to use. Default makeAggregateBom. Other options: makeBom, makePackageBom
|
|
317
|
-
| CDX_MAVEN_INCLUDE_TEST_SCOPE | Whether test scoped dependencies should be included from Maven projects, Default: true
|
|
290
|
+
| Variable | Description |
|
|
291
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
292
|
+
| CDXGEN_DEBUG_MODE | Set to `debug` to enable debug messages |
|
|
293
|
+
| GITHUB_TOKEN | Specify GitHub token to prevent traffic shaping while querying license and repo information |
|
|
294
|
+
| MVN_CMD | Set to override maven command |
|
|
295
|
+
| MVN_ARGS | Set to pass additional arguments such as profile or settings to maven |
|
|
296
|
+
| MAVEN_HOME | Specify maven home |
|
|
297
|
+
| GRADLE_CACHE_DIR | Specify gradle cache directory. Useful for class name resolving |
|
|
298
|
+
| GRADLE_MULTI_PROJECT_MODE | Unused. Automatically handled |
|
|
299
|
+
| GRADLE_ARGS | Set to pass additional arguments such as profile or settings to gradle (all tasks). Eg: --configuration runtimeClassPath |
|
|
300
|
+
| GRADLE_ARGS_PROPERTIES | Set to pass additional arguments only to the `gradle properties` task, used for collecting metadata about the project |
|
|
301
|
+
| GRADLE_ARGS_DEPENDENCIES | Set to pass additional arguments only to the `gradle dependencies` task, used for listing actual project dependencies |
|
|
302
|
+
| GRADLE_HOME | Specify gradle home |
|
|
303
|
+
| GRADLE_CMD | Set to override gradle command |
|
|
304
|
+
| GRADLE_DEPENDENCY_TASK | By default cdxgen use the task "dependencies" to collect packages. Set to override the task name. |
|
|
305
|
+
| SBT_CACHE_DIR | Specify sbt cache directory. Useful for class name resolving |
|
|
306
|
+
| FETCH_LICENSE | Set this variable to `true` or `1` to fetch license information from the registry. npm and golang |
|
|
307
|
+
| USE_GOSUM | Set to `true` or `1` to generate BOMs for golang projects using go.sum as the dependency source of truth, instead of go.mod |
|
|
308
|
+
| CDXGEN_TIMEOUT_MS | Default timeout for known execution involving maven, gradle or sbt |
|
|
309
|
+
| CDXGEN_SERVER_TIMEOUT_MS | Default timeout in server mode |
|
|
310
|
+
| BAZEL_TARGET | Bazel target to build. Default :all (Eg: //java-maven) |
|
|
311
|
+
| CLJ_CMD | Set to override the clojure cli command |
|
|
312
|
+
| LEIN_CMD | Set to override the leiningen command |
|
|
313
|
+
| SBOM_SIGN_ALGORITHM | Signature algorithm. Some valid values are RS256, RS384, RS512, PS256, PS384, PS512, ES256 etc |
|
|
314
|
+
| SBOM_SIGN_PRIVATE_KEY | Private key to use for signing |
|
|
315
|
+
| SBOM_SIGN_PUBLIC_KEY | Optional. Public key to include in the SBoM signature |
|
|
316
|
+
| CDX_MAVEN_PLUGIN | CycloneDX Maven plugin to use. Default "org.cyclonedx:cyclonedx-maven-plugin:2.7.8" |
|
|
317
|
+
| CDX_MAVEN_GOAL | CycloneDX Maven plugin goal to use. Default makeAggregateBom. Other options: makeBom, makePackageBom |
|
|
318
|
+
| CDX_MAVEN_INCLUDE_TEST_SCOPE | Whether test scoped dependencies should be included from Maven projects, Default: true |
|
|
319
|
+
| ASTGEN_IGNORE_DIRS | Comma separated list of directories to ignore while analyzing using babel. The environment variable is also used by atom and astgen. |
|
|
320
|
+
| ASTGEN_IGNORE_FILE_PATTERN | Ignore regex to use |
|
|
318
321
|
|
|
319
322
|
## Plugins
|
|
320
323
|
|
package/analyzer.js
CHANGED
|
@@ -2,28 +2,37 @@ import { parse } from "@babel/parser";
|
|
|
2
2
|
import traverse from "@babel/traverse";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { readdirSync, statSync, readFileSync } from "fs";
|
|
5
|
-
import { basename, resolve, isAbsolute } from "path";
|
|
5
|
+
import { basename, resolve, isAbsolute, relative } from "path";
|
|
6
6
|
|
|
7
|
-
const IGNORE_DIRS =
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
const IGNORE_DIRS = process.env.ASTGEN_IGNORE_DIRS
|
|
8
|
+
? process.env.ASTGEN_IGNORE_DIRS.split(",")
|
|
9
|
+
: [
|
|
10
|
+
"node_modules",
|
|
11
|
+
"venv",
|
|
12
|
+
"docs",
|
|
13
|
+
"test",
|
|
14
|
+
"tests",
|
|
15
|
+
"e2e",
|
|
16
|
+
"examples",
|
|
17
|
+
"cypress",
|
|
18
|
+
"site-packages",
|
|
19
|
+
"typings",
|
|
20
|
+
"api_docs",
|
|
21
|
+
"dev_docs",
|
|
22
|
+
"types",
|
|
23
|
+
"mock",
|
|
24
|
+
"mocks",
|
|
25
|
+
"jest-cache",
|
|
26
|
+
"eslint-rules",
|
|
27
|
+
"codemods",
|
|
28
|
+
"flow-typed",
|
|
29
|
+
"i18n",
|
|
30
|
+
"__tests__"
|
|
31
|
+
];
|
|
24
32
|
|
|
25
33
|
const IGNORE_FILE_PATTERN = new RegExp(
|
|
26
|
-
|
|
34
|
+
process.env.ASTGEN_IGNORE_FILE_PATTERN ||
|
|
35
|
+
"(conf|config|test|spec|mock|\\.d)\\.(js|ts|tsx)$",
|
|
27
36
|
"i"
|
|
28
37
|
);
|
|
29
38
|
|
|
@@ -33,7 +42,7 @@ const getAllFiles = (dir, extn, files, result, regex) => {
|
|
|
33
42
|
regex = regex || new RegExp(`\\${extn}$`);
|
|
34
43
|
|
|
35
44
|
for (let i = 0; i < files.length; i++) {
|
|
36
|
-
if (IGNORE_FILE_PATTERN.test(files[i])) {
|
|
45
|
+
if (IGNORE_FILE_PATTERN.test(files[i]) || files[i].startsWith(".")) {
|
|
37
46
|
continue;
|
|
38
47
|
}
|
|
39
48
|
const file = join(dir, files[i]);
|
|
@@ -87,72 +96,95 @@ const babelParserOptions = {
|
|
|
87
96
|
* Filter only references to (t|jsx?) or (less|scss) files for now.
|
|
88
97
|
* Opt to use our relative paths.
|
|
89
98
|
*/
|
|
90
|
-
const setFileRef = (allImports, file,
|
|
99
|
+
const setFileRef = (allImports, src, file, pathnode, specifiers = []) => {
|
|
100
|
+
const pathway = pathnode.value || pathnode.name;
|
|
101
|
+
const sourceLoc = pathnode.loc?.start;
|
|
102
|
+
if (!pathway) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const fileRelativeLoc = relative(src, file);
|
|
91
106
|
// remove unexpected extension imports
|
|
92
107
|
if (/\.(svg|png|jpg|d\.ts)/.test(pathway)) {
|
|
93
108
|
return;
|
|
94
109
|
}
|
|
95
|
-
|
|
110
|
+
const importedModules = specifiers
|
|
111
|
+
.map((s) => s.imported?.name)
|
|
112
|
+
.filter((v) => v !== undefined);
|
|
113
|
+
const occurrence = {
|
|
114
|
+
importedAs: pathway,
|
|
115
|
+
importedModules,
|
|
116
|
+
isExternal: true,
|
|
117
|
+
fileName: fileRelativeLoc,
|
|
118
|
+
lineNumber: sourceLoc && sourceLoc.line ? sourceLoc.line : undefined,
|
|
119
|
+
columnNumber: sourceLoc && sourceLoc.column ? sourceLoc.column : undefined
|
|
120
|
+
};
|
|
96
121
|
// replace relative imports with full path
|
|
97
|
-
let
|
|
122
|
+
let moduleFullPath = pathway;
|
|
123
|
+
let wasAbsolute = false;
|
|
98
124
|
if (/\.\//g.test(pathway) || /\.\.\//g.test(pathway)) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
} else {
|
|
106
|
-
allImports[module] = 1;
|
|
125
|
+
moduleFullPath = resolve(file, "..", pathway);
|
|
126
|
+
if (isAbsolute(moduleFullPath)) {
|
|
127
|
+
moduleFullPath = relative(src, moduleFullPath);
|
|
128
|
+
wasAbsolute = true;
|
|
129
|
+
}
|
|
130
|
+
occurrence.isExternal = false;
|
|
107
131
|
}
|
|
108
|
-
|
|
132
|
+
allImports[moduleFullPath] = allImports[moduleFullPath] || new Set();
|
|
133
|
+
allImports[moduleFullPath].add(occurrence);
|
|
109
134
|
// Handle module package name
|
|
110
135
|
// Eg: zone.js/dist/zone will be referred to as zone.js in package.json
|
|
111
|
-
if (!
|
|
112
|
-
const modPkg =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
} else {
|
|
116
|
-
allImports[modPkg] = 1;
|
|
117
|
-
}
|
|
136
|
+
if (!wasAbsolute && moduleFullPath.includes("/")) {
|
|
137
|
+
const modPkg = moduleFullPath.split("/")[0];
|
|
138
|
+
allImports[modPkg] = allImports[modPkg] || new Set();
|
|
139
|
+
allImports[modPkg].add(occurrence);
|
|
118
140
|
}
|
|
119
141
|
};
|
|
120
142
|
|
|
143
|
+
const vueCleaningRegex = /<\/*script.*>|<style[\s\S]*style>|<\/*br>/gi;
|
|
144
|
+
const vueTemplateRegex = /(<template.*>)([\s\S]*)(<\/template>)/gi;
|
|
145
|
+
const vueCommentRegex = /<!--[\s\S]*?-->/gi;
|
|
146
|
+
const vueBindRegex = /(:\[)([\s\S]*?)(\])/gi;
|
|
147
|
+
const vuePropRegex = /\s([.:@])([a-zA-Z]*?=)/gi;
|
|
148
|
+
|
|
149
|
+
const fileToParseableCode = (file) => {
|
|
150
|
+
let code = readFileSync(file, "utf-8");
|
|
151
|
+
if (file.endsWith(".vue") || file.endsWith(".svelte")) {
|
|
152
|
+
code = code
|
|
153
|
+
.replace(vueCommentRegex, function (match) {
|
|
154
|
+
return match.replaceAll(/\S/g, " ");
|
|
155
|
+
})
|
|
156
|
+
.replace(vueCleaningRegex, function (match) {
|
|
157
|
+
return match.replaceAll(/\S/g, " ").substring(1) + ";";
|
|
158
|
+
})
|
|
159
|
+
.replace(vueBindRegex, function (match, grA, grB, grC) {
|
|
160
|
+
return grA.replaceAll(/\S/g, " ") + grB + grC.replaceAll(/\S/g, " ");
|
|
161
|
+
})
|
|
162
|
+
.replace(vuePropRegex, function (match, grA, grB) {
|
|
163
|
+
return " " + grA.replace(/[.:@]/g, " ") + grB;
|
|
164
|
+
})
|
|
165
|
+
.replace(vueTemplateRegex, function (match, grA, grB, grC) {
|
|
166
|
+
return grA + grB.replaceAll("{{", "{ ").replaceAll("}}", " }") + grC;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return code;
|
|
170
|
+
};
|
|
171
|
+
|
|
121
172
|
/**
|
|
122
173
|
* Check AST tree for any (j|tsx?) files and set a file
|
|
123
174
|
* references for any import, require or dynamic import files.
|
|
124
175
|
*/
|
|
125
|
-
const parseFileASTTree = (file, allImports) => {
|
|
126
|
-
const ast = parse(
|
|
176
|
+
const parseFileASTTree = (src, file, allImports) => {
|
|
177
|
+
const ast = parse(fileToParseableCode(file), babelParserOptions);
|
|
127
178
|
traverse.default(ast, {
|
|
128
|
-
Import: (path) => {
|
|
129
|
-
if (path && path.node) {
|
|
130
|
-
setFileRef(allImports, file, path.node.source.value);
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
ImportDefaultSpecifier: (path) => {
|
|
134
|
-
if (path && path.node) {
|
|
135
|
-
setFileRef(allImports, file, path.node.source.value);
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
ImportNamespaceSpecifier: (path) => {
|
|
139
|
-
if (path && path.node) {
|
|
140
|
-
setFileRef(allImports, file, path.node.source.value);
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
ImportAttribute: (path) => {
|
|
144
|
-
if (path && path.node) {
|
|
145
|
-
setFileRef(allImports, file, path.node.source.value);
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
ImportOrExportDeclaration: (path) => {
|
|
149
|
-
if (path && path.node) {
|
|
150
|
-
setFileRef(allImports, file, path.node.source.value);
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
179
|
ImportDeclaration: (path) => {
|
|
154
180
|
if (path && path.node) {
|
|
155
|
-
setFileRef(
|
|
181
|
+
setFileRef(
|
|
182
|
+
allImports,
|
|
183
|
+
src,
|
|
184
|
+
file,
|
|
185
|
+
path.node.source,
|
|
186
|
+
path.node.specifiers
|
|
187
|
+
);
|
|
156
188
|
}
|
|
157
189
|
},
|
|
158
190
|
// For require('') statements
|
|
@@ -163,23 +195,29 @@ const parseFileASTTree = (file, allImports) => {
|
|
|
163
195
|
path.node.name === "require" &&
|
|
164
196
|
path.parent.type === "CallExpression"
|
|
165
197
|
) {
|
|
166
|
-
setFileRef(allImports, file, path.parent.arguments[0]
|
|
198
|
+
setFileRef(allImports, src, file, path.parent.arguments[0]);
|
|
167
199
|
}
|
|
168
200
|
},
|
|
169
201
|
// Use for dynamic imports like routes.jsx
|
|
170
202
|
CallExpression: (path) => {
|
|
171
203
|
if (path && path.node && path.node.callee.type === "Import") {
|
|
172
|
-
setFileRef(allImports, file, path.node.arguments[0]
|
|
204
|
+
setFileRef(allImports, src, file, path.node.arguments[0]);
|
|
173
205
|
}
|
|
174
206
|
},
|
|
175
207
|
// Use for export barrells
|
|
176
208
|
ExportAllDeclaration: (path) => {
|
|
177
|
-
setFileRef(allImports, file, path.node.source
|
|
209
|
+
setFileRef(allImports, src, file, path.node.source);
|
|
178
210
|
},
|
|
179
211
|
ExportNamedDeclaration: (path) => {
|
|
180
212
|
// ensure there is a path export
|
|
181
213
|
if (path && path.node && path.node.source) {
|
|
182
|
-
setFileRef(
|
|
214
|
+
setFileRef(
|
|
215
|
+
allImports,
|
|
216
|
+
src,
|
|
217
|
+
file,
|
|
218
|
+
path.node.source,
|
|
219
|
+
path.node.specifiers
|
|
220
|
+
);
|
|
183
221
|
}
|
|
184
222
|
}
|
|
185
223
|
});
|
|
@@ -192,8 +230,12 @@ const getAllSrcJSAndTSFiles = (src) =>
|
|
|
192
230
|
Promise.all([
|
|
193
231
|
getAllFiles(src, ".js"),
|
|
194
232
|
getAllFiles(src, ".jsx"),
|
|
233
|
+
getAllFiles(src, ".cjs"),
|
|
234
|
+
getAllFiles(src, ".mjs"),
|
|
195
235
|
getAllFiles(src, ".ts"),
|
|
196
|
-
getAllFiles(src, ".tsx")
|
|
236
|
+
getAllFiles(src, ".tsx"),
|
|
237
|
+
getAllFiles(src, ".vue"),
|
|
238
|
+
getAllFiles(src, ".svelte")
|
|
197
239
|
]);
|
|
198
240
|
|
|
199
241
|
/**
|
|
@@ -207,7 +249,7 @@ export const findJSImports = async (src) => {
|
|
|
207
249
|
const srcFiles = promiseMap.flatMap((d) => d);
|
|
208
250
|
for (const file of srcFiles) {
|
|
209
251
|
try {
|
|
210
|
-
parseFileASTTree(file, allImports);
|
|
252
|
+
parseFileASTTree(src, file, allImports);
|
|
211
253
|
} catch (err) {
|
|
212
254
|
errFiles.push(file);
|
|
213
255
|
}
|
package/bin/cdxgen.js
CHANGED
|
@@ -313,13 +313,30 @@ const checkPermissions = (filePath) => {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
try {
|
|
316
|
+
// Sign the individual components
|
|
317
|
+
// Let's leave the services unsigned for now since it might require additional cleansing
|
|
318
|
+
const bomJsonUnsignedObj = JSON.parse(jsonPayload);
|
|
319
|
+
for (const comp of bomJsonUnsignedObj.components) {
|
|
320
|
+
const compSignature = jws.sign({
|
|
321
|
+
header: { alg },
|
|
322
|
+
payload: comp,
|
|
323
|
+
privateKey: privateKeyToUse
|
|
324
|
+
});
|
|
325
|
+
const compSignatureBlock = {
|
|
326
|
+
algorithm: alg,
|
|
327
|
+
value: compSignature
|
|
328
|
+
};
|
|
329
|
+
if (jwkPublicKey) {
|
|
330
|
+
compSignatureBlock.publicKey = jwkPublicKey;
|
|
331
|
+
}
|
|
332
|
+
comp.signature = compSignatureBlock;
|
|
333
|
+
}
|
|
316
334
|
const signature = jws.sign({
|
|
317
335
|
header: { alg },
|
|
318
|
-
payload:
|
|
336
|
+
payload: JSON.stringify(bomJsonUnsignedObj, null, 2),
|
|
319
337
|
privateKey: privateKeyToUse
|
|
320
338
|
});
|
|
321
339
|
if (signature) {
|
|
322
|
-
const bomJsonUnsignedObj = JSON.parse(jsonPayload);
|
|
323
340
|
const signatureBlock = {
|
|
324
341
|
algorithm: alg,
|
|
325
342
|
value: signature
|
package/bin/evinse.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
1
3
|
// Evinse (Evinse Verification Is Nearly SBoM Evidence)
|
|
2
4
|
import yargs from "yargs";
|
|
3
5
|
import { hideBin } from "yargs/helpers";
|
|
@@ -65,7 +67,7 @@ const args = yargs(hideBin(process.argv))
|
|
|
65
67
|
})
|
|
66
68
|
.option("annotate", {
|
|
67
69
|
description: "Include contents of atom slices as annotations",
|
|
68
|
-
default:
|
|
70
|
+
default: false,
|
|
69
71
|
type: "boolean"
|
|
70
72
|
})
|
|
71
73
|
.option("with-data-flow", {
|
package/bin/repl.js
CHANGED
package/bin/verify.js
CHANGED
|
@@ -5,6 +5,14 @@ import { hideBin } from "yargs/helpers";
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import jws from "jws";
|
|
7
7
|
import process from "node:process";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
|
|
11
|
+
let url = import.meta.url;
|
|
12
|
+
if (!url.startsWith("file://")) {
|
|
13
|
+
url = new URL(`file://${import.meta.url}`).toString();
|
|
14
|
+
}
|
|
15
|
+
const dirName = import.meta ? dirname(fileURLToPath(url)) : __dirname;
|
|
8
16
|
|
|
9
17
|
const args = yargs(hideBin(process.argv))
|
|
10
18
|
.option("input", {
|
|
@@ -20,8 +28,22 @@ const args = yargs(hideBin(process.argv))
|
|
|
20
28
|
.version()
|
|
21
29
|
.help("h").argv;
|
|
22
30
|
|
|
31
|
+
if (args.version) {
|
|
32
|
+
const packageJsonAsString = fs.readFileSync(
|
|
33
|
+
join(dirName, "..", "package.json"),
|
|
34
|
+
"utf-8"
|
|
35
|
+
);
|
|
36
|
+
const packageJson = JSON.parse(packageJsonAsString);
|
|
37
|
+
|
|
38
|
+
console.log(packageJson.version);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
const bomJson = JSON.parse(fs.readFileSync(args.input, "utf8"));
|
|
24
|
-
const bomSignature =
|
|
43
|
+
const bomSignature =
|
|
44
|
+
bomJson.signature && bomJson.signature.value
|
|
45
|
+
? bomJson.signature.value
|
|
46
|
+
: undefined;
|
|
25
47
|
if (!bomSignature) {
|
|
26
48
|
console.log("No signature was found!");
|
|
27
49
|
} else {
|
package/display.js
CHANGED
|
@@ -58,6 +58,19 @@ export const printServices = (bomJson) => {
|
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
const locationComparator = (a, b) => {
|
|
62
|
+
if (a && b && a.includes("#") && b.includes("#")) {
|
|
63
|
+
const tmpA = a.split("#");
|
|
64
|
+
const tmpB = b.split("#");
|
|
65
|
+
if (tmpA.length === 2 && tmpB.length === 2) {
|
|
66
|
+
if (tmpA[0] == tmpB[0]) {
|
|
67
|
+
return tmpA[1] - tmpB[1];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return a.localeCompare(b);
|
|
72
|
+
};
|
|
73
|
+
|
|
61
74
|
export const printOccurrences = (bomJson) => {
|
|
62
75
|
const data = [["Group", "Name", "Version", "Occurrences"]];
|
|
63
76
|
if (!bomJson || !bomJson.components) {
|
|
@@ -73,7 +86,7 @@ export const printOccurrences = (bomJson) => {
|
|
|
73
86
|
comp.version,
|
|
74
87
|
comp.evidence.occurrences
|
|
75
88
|
.map((l) => l.location)
|
|
76
|
-
.sort()
|
|
89
|
+
.sort(locationComparator)
|
|
77
90
|
.join("\n")
|
|
78
91
|
]);
|
|
79
92
|
}
|
|
@@ -106,7 +119,7 @@ export const printCallStack = (bomJson) => {
|
|
|
106
119
|
(c) => `${c.fullFilename}${c.line ? "#" + c.line : ""}`
|
|
107
120
|
)
|
|
108
121
|
)
|
|
109
|
-
).sort();
|
|
122
|
+
).sort(locationComparator);
|
|
110
123
|
let frameDisplay = [frames[0]];
|
|
111
124
|
if (frames.length > 1) {
|
|
112
125
|
for (let i = 1; i < frames.length - 1; i++) {
|