@trustify-da/trustify-da-javascript-client 0.3.0-ea.6549d2a → 0.3.0-ea.68905d3
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 +368 -14
- package/dist/package.json +39 -26
- package/dist/src/analysis.d.ts +28 -5
- package/dist/src/analysis.js +78 -13
- package/dist/src/batch_opts.d.ts +24 -0
- package/dist/src/batch_opts.js +35 -0
- package/dist/src/cli.js +287 -5
- package/dist/src/cyclone_dx_sbom.d.ts +27 -1
- package/dist/src/cyclone_dx_sbom.js +84 -10
- package/dist/src/index.d.ts +145 -8
- package/dist/src/index.js +362 -28
- package/dist/src/license/index.d.ts +5 -5
- package/dist/src/license/index.js +8 -5
- package/dist/src/license/license_utils.d.ts +40 -0
- package/dist/src/license/license_utils.js +137 -0
- package/dist/src/license/licenses_api.d.ts +2 -2
- package/dist/src/license/licenses_api.js +9 -2
- package/dist/src/license/project_license.d.ts +1 -6
- package/dist/src/license/project_license.js +4 -81
- package/dist/src/oci_image/images.d.ts +1 -1
- package/dist/src/oci_image/images.js +15 -1
- package/dist/src/oci_image/utils.d.ts +5 -5
- package/dist/src/oci_image/utils.js +11 -2
- package/dist/src/package_version.d.ts +8 -0
- package/dist/src/package_version.js +31 -0
- package/dist/src/provider.d.ts +8 -4
- package/dist/src/provider.js +18 -5
- package/dist/src/providers/base_java.d.ts +67 -12
- package/dist/src/providers/base_java.js +82 -61
- package/dist/src/providers/base_javascript.d.ts +91 -10
- package/dist/src/providers/base_javascript.js +220 -28
- package/dist/src/providers/base_pyproject.d.ts +158 -0
- package/dist/src/providers/base_pyproject.js +322 -0
- package/dist/src/providers/containerfile_parser.d.ts +5 -0
- package/dist/src/providers/containerfile_parser.js +20 -0
- package/dist/src/providers/golang_gomodules.d.ts +23 -13
- package/dist/src/providers/golang_gomodules.js +210 -121
- package/dist/src/providers/gomod_parser.d.ts +4 -0
- package/dist/src/providers/gomod_parser.js +16 -0
- package/dist/src/providers/java_gradle.d.ts +67 -0
- package/dist/src/providers/java_gradle.js +314 -21
- package/dist/src/providers/java_gradle_groovy.d.ts +1 -1
- package/dist/src/providers/java_gradle_kotlin.d.ts +1 -1
- package/dist/src/providers/java_maven.d.ts +38 -10
- package/dist/src/providers/java_maven.js +279 -20
- package/dist/src/providers/javascript_bun.d.ts +22 -0
- package/dist/src/providers/javascript_bun.js +141 -0
- package/dist/src/providers/javascript_npm.d.ts +19 -0
- package/dist/src/providers/javascript_npm.js +61 -1
- package/dist/src/providers/javascript_pnpm.d.ts +14 -1
- package/dist/src/providers/javascript_pnpm.js +55 -5
- package/dist/src/providers/javascript_yarn.d.ts +12 -0
- package/dist/src/providers/javascript_yarn.js +75 -2
- package/dist/src/providers/manifest.d.ts +2 -0
- package/dist/src/providers/manifest.js +28 -7
- package/dist/src/providers/marker_evaluator.d.ts +14 -0
- package/dist/src/providers/marker_evaluator.js +191 -0
- package/dist/src/providers/oci_dockerfile.d.ts +51 -0
- package/dist/src/providers/oci_dockerfile.js +177 -0
- package/dist/src/providers/processors/yarn_berry_processor.d.ts +6 -2
- package/dist/src/providers/processors/yarn_berry_processor.js +91 -7
- package/dist/src/providers/processors/yarn_classic_processor.d.ts +6 -2
- package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
- package/dist/src/providers/python_controller.d.ts +5 -1
- package/dist/src/providers/python_controller.js +69 -8
- package/dist/src/providers/python_pip.d.ts +5 -0
- package/dist/src/providers/python_pip.js +8 -7
- package/dist/src/providers/python_pip_pyproject.d.ts +61 -0
- package/dist/src/providers/python_pip_pyproject.js +146 -0
- package/dist/src/providers/python_poetry.d.ts +98 -0
- package/dist/src/providers/python_poetry.js +286 -0
- package/dist/src/providers/python_uv.d.ts +46 -0
- package/dist/src/providers/python_uv.js +269 -0
- package/dist/src/providers/requirements_parser.js +5 -4
- package/dist/src/providers/rust_cargo.d.ts +56 -0
- package/dist/src/providers/rust_cargo.js +682 -0
- package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
- package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
- package/dist/src/providers/tree-sitter-requirements.wasm +0 -0
- package/dist/src/remediate.d.ts +117 -0
- package/dist/src/remediate.js +249 -0
- package/dist/src/remediation.d.ts +57 -0
- package/dist/src/remediation.js +436 -0
- package/dist/src/remediation_report.d.ts +40 -0
- package/dist/src/remediation_report.js +159 -0
- package/dist/src/sbom.d.ts +25 -1
- package/dist/src/sbom.js +23 -2
- package/dist/src/tools.d.ts +28 -2
- package/dist/src/tools.js +82 -1
- package/dist/src/updaters/maven_updater.d.ts +58 -0
- package/dist/src/updaters/maven_updater.js +345 -0
- package/dist/src/updaters/toml_updater.d.ts +62 -0
- package/dist/src/updaters/toml_updater.js +276 -0
- package/dist/src/workspace.d.ts +70 -0
- package/dist/src/workspace.js +256 -0
- package/package.json +40 -27
- package/dist/src/license/compatibility.d.ts +0 -18
- package/dist/src/license/compatibility.js +0 -45
package/dist/src/analysis.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
/** Media type for CycloneDX JSON batch payloads (batch-analysis API). */
|
|
2
|
+
export const CYCLONEDX_JSON_MEDIA_TYPE: "application/vnd.cyclonedx+json";
|
|
1
3
|
declare namespace _default {
|
|
2
4
|
export { requestComponent };
|
|
3
5
|
export { requestStack };
|
|
6
|
+
export { requestStackBatch };
|
|
4
7
|
export { requestImages };
|
|
5
8
|
export { validateToken };
|
|
9
|
+
export { appendAnalysisQueryParams };
|
|
6
10
|
}
|
|
7
11
|
export default _default;
|
|
8
12
|
/**
|
|
@@ -13,7 +17,7 @@ export default _default;
|
|
|
13
17
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
|
|
14
18
|
* @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
|
|
15
19
|
*/
|
|
16
|
-
declare function requestComponent(provider:
|
|
20
|
+
declare function requestComponent(provider: any, manifest: string, url: string, opts?: any): Promise<any>;
|
|
17
21
|
/**
|
|
18
22
|
* Send a stack analysis request and get the report as 'text/html' or 'application/json'.
|
|
19
23
|
* @param {import('./provider').Provider} provider - the provided data for constructing the request
|
|
@@ -23,7 +27,20 @@ declare function requestComponent(provider: import("./provider").Provider, manif
|
|
|
23
27
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
|
|
24
28
|
* @returns {Promise<string|import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
|
|
25
29
|
*/
|
|
26
|
-
declare function requestStack(provider:
|
|
30
|
+
declare function requestStack(provider: any, manifest: string, url: string, html?: boolean, opts?: any): Promise<string | any>;
|
|
31
|
+
/**
|
|
32
|
+
* Send a batch stack analysis request for multiple manifests (SBOMs keyed by purl).
|
|
33
|
+
* @param {Object.<string, object>} sbomByPurl - Map of root purl to CycloneDX SBOM object
|
|
34
|
+
* @param {string} url - the backend url
|
|
35
|
+
* @param {boolean} [html=false] - true returns HTML, false returns JSON
|
|
36
|
+
* @param {import("index.js").Options} [opts={}]
|
|
37
|
+
* @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
|
|
38
|
+
*/
|
|
39
|
+
declare function requestStackBatch(sbomByPurl: {
|
|
40
|
+
[x: string]: object;
|
|
41
|
+
}, url: string, html?: boolean, opts?: any): Promise<string | {
|
|
42
|
+
[x: string]: any;
|
|
43
|
+
}>;
|
|
27
44
|
/**
|
|
28
45
|
*
|
|
29
46
|
* @param {Array<string>} imageRefs
|
|
@@ -31,8 +48,8 @@ declare function requestStack(provider: import("./provider").Provider, manifest:
|
|
|
31
48
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
|
|
32
49
|
* @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
|
|
33
50
|
*/
|
|
34
|
-
declare function requestImages(imageRefs: Array<string>, url: string, html?: boolean, opts?:
|
|
35
|
-
[x: string]:
|
|
51
|
+
declare function requestImages(imageRefs: Array<string>, url: string, html?: boolean, opts?: any): Promise<string | {
|
|
52
|
+
[x: string]: any;
|
|
36
53
|
}>;
|
|
37
54
|
/**
|
|
38
55
|
*
|
|
@@ -40,4 +57,10 @@ declare function requestImages(imageRefs: Array<string>, url: string, html?: boo
|
|
|
40
57
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass headers for t he validateToken Request
|
|
41
58
|
* @return {Promise<number>} return the HTTP status Code of the response from the validate token request.
|
|
42
59
|
*/
|
|
43
|
-
declare function validateToken(url: any, opts?:
|
|
60
|
+
declare function validateToken(url: any, opts?: any): Promise<number>;
|
|
61
|
+
/**
|
|
62
|
+
* Append common analysis query parameters (recommend, providers, sources) to a URL.
|
|
63
|
+
* @param {URL} url - the URL object to append query parameters to
|
|
64
|
+
* @param {import("index.js").Options} [opts={}] - options containing parameter overrides
|
|
65
|
+
*/
|
|
66
|
+
declare function appendAnalysisQueryParams(url: URL, opts?: any): void;
|
package/dist/src/analysis.js
CHANGED
|
@@ -4,7 +4,9 @@ import { EOL } from "os";
|
|
|
4
4
|
import { runLicenseCheck } from "./license/index.js";
|
|
5
5
|
import { generateImageSBOM, parseImageRef } from "./oci_image/utils.js";
|
|
6
6
|
import { addProxyAgent, getCustom, getTokenHeaders, TRUSTIFY_DA_OPERATION_TYPE_HEADER, TRUSTIFY_DA_PACKAGE_MANAGER_HEADER } from "./tools.js";
|
|
7
|
-
|
|
7
|
+
/** Media type for CycloneDX JSON batch payloads (batch-analysis API). */
|
|
8
|
+
export const CYCLONEDX_JSON_MEDIA_TYPE = 'application/vnd.cyclonedx+json';
|
|
9
|
+
export default { requestComponent, requestStack, requestStackBatch, requestImages, validateToken, appendAnalysisQueryParams };
|
|
8
10
|
/**
|
|
9
11
|
* Send a stack analysis request and get the report as 'text/html' or 'application/json'.
|
|
10
12
|
* @param {import('./provider').Provider} provider - the provided data for constructing the request
|
|
@@ -18,6 +20,9 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
|
|
|
18
20
|
opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
|
|
19
21
|
opts["manifest-type"] = path.parse(manifest).base;
|
|
20
22
|
let provided = await provider.provideStack(manifest, opts); // throws error if content providing failed
|
|
23
|
+
if (provided.batch) {
|
|
24
|
+
return requestStackBatch(JSON.parse(provided.content), url, html, opts);
|
|
25
|
+
}
|
|
21
26
|
opts["source-manifest"] = "";
|
|
22
27
|
opts[TRUSTIFY_DA_OPERATION_TYPE_HEADER.toUpperCase().replaceAll("-", "_")] = "stack-analysis";
|
|
23
28
|
let startTime = new Date();
|
|
@@ -36,9 +41,7 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
|
|
|
36
41
|
body: provided.content
|
|
37
42
|
}, opts);
|
|
38
43
|
const finalUrl = new URL(`${url}/api/v5/analysis`);
|
|
39
|
-
|
|
40
|
-
finalUrl.searchParams.append('recommend', 'false');
|
|
41
|
-
}
|
|
44
|
+
appendAnalysisQueryParams(finalUrl, opts);
|
|
42
45
|
let resp = await fetch(finalUrl, fetchOptions);
|
|
43
46
|
let result;
|
|
44
47
|
if (resp.status === 200) {
|
|
@@ -77,6 +80,9 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
|
|
|
77
80
|
async function requestComponent(provider, manifest, url, opts = {}) {
|
|
78
81
|
opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
|
|
79
82
|
let provided = await provider.provideComponent(manifest, opts); // throws error if content providing failed
|
|
83
|
+
if (provided.batch) {
|
|
84
|
+
return requestStackBatch(JSON.parse(provided.content), url, false, opts);
|
|
85
|
+
}
|
|
80
86
|
opts["source-manifest"] = "";
|
|
81
87
|
opts[TRUSTIFY_DA_OPERATION_TYPE_HEADER.toUpperCase().replaceAll("-", "_")] = "component-analysis";
|
|
82
88
|
if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
|
|
@@ -93,9 +99,7 @@ async function requestComponent(provider, manifest, url, opts = {}) {
|
|
|
93
99
|
body: provided.content
|
|
94
100
|
}, opts);
|
|
95
101
|
const finalUrl = new URL(`${url}/api/v5/analysis`);
|
|
96
|
-
|
|
97
|
-
finalUrl.searchParams.append('recommend', 'false');
|
|
98
|
-
}
|
|
102
|
+
appendAnalysisQueryParams(finalUrl, opts);
|
|
99
103
|
let resp = await fetch(finalUrl, fetchOptions);
|
|
100
104
|
let result;
|
|
101
105
|
if (resp.status === 200) {
|
|
@@ -124,6 +128,50 @@ async function requestComponent(provider, manifest, url, opts = {}) {
|
|
|
124
128
|
}
|
|
125
129
|
return Promise.resolve(result);
|
|
126
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Send a batch stack analysis request for multiple manifests (SBOMs keyed by purl).
|
|
133
|
+
* @param {Object.<string, object>} sbomByPurl - Map of root purl to CycloneDX SBOM object
|
|
134
|
+
* @param {string} url - the backend url
|
|
135
|
+
* @param {boolean} [html=false] - true returns HTML, false returns JSON
|
|
136
|
+
* @param {import("index.js").Options} [opts={}]
|
|
137
|
+
* @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
|
|
138
|
+
*/
|
|
139
|
+
async function requestStackBatch(sbomByPurl, url, html = false, opts = {}) {
|
|
140
|
+
const finalUrl = new URL(`${url}/api/v5/batch-analysis`);
|
|
141
|
+
appendAnalysisQueryParams(finalUrl, opts);
|
|
142
|
+
const fetchOptions = addProxyAgent({
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'Accept': html ? 'text/html' : 'application/json',
|
|
146
|
+
'Content-Type': CYCLONEDX_JSON_MEDIA_TYPE,
|
|
147
|
+
...getTokenHeaders(opts)
|
|
148
|
+
},
|
|
149
|
+
body: JSON.stringify(sbomByPurl)
|
|
150
|
+
}, opts);
|
|
151
|
+
const resp = await fetch(finalUrl, fetchOptions);
|
|
152
|
+
if (resp.status === 200) {
|
|
153
|
+
let result;
|
|
154
|
+
if (!html) {
|
|
155
|
+
result = await resp.json();
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
result = await resp.text();
|
|
159
|
+
}
|
|
160
|
+
if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
|
|
161
|
+
const exRequestId = resp.headers.get("ex-request-id");
|
|
162
|
+
if (exRequestId) {
|
|
163
|
+
console.log("Unique Identifier associated with this request - ex-request-id=" + exRequestId);
|
|
164
|
+
}
|
|
165
|
+
console.log("Response body received from Trustify DA backend server : " + EOL + EOL);
|
|
166
|
+
console.log(JSON.stringify(result, null, 4));
|
|
167
|
+
console.log("Ending time of sending batch stack analysis request to Trustify DA backend server= " + new Date());
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
throw new Error(`Got error response from Trustify DA backend - http return code : ${resp.status}, ex-request-id: ${resp.headers.get("ex-request-id")} error message => ${await resp.text()}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
127
175
|
/**
|
|
128
176
|
*
|
|
129
177
|
* @param {Array<string>} imageRefs
|
|
@@ -138,18 +186,17 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
|
|
|
138
186
|
imageSboms[parsedImageRef.getPackageURL().toString()] = generateImageSBOM(parsedImageRef, opts);
|
|
139
187
|
}
|
|
140
188
|
const finalUrl = new URL(`${url}/api/v5/batch-analysis`);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
const resp = await fetch(finalUrl, {
|
|
189
|
+
appendAnalysisQueryParams(finalUrl, opts);
|
|
190
|
+
const fetchOptions = addProxyAgent({
|
|
145
191
|
method: 'POST',
|
|
146
192
|
headers: {
|
|
147
193
|
'Accept': html ? 'text/html' : 'application/json',
|
|
148
|
-
'Content-Type':
|
|
194
|
+
'Content-Type': CYCLONEDX_JSON_MEDIA_TYPE,
|
|
149
195
|
...getTokenHeaders(opts)
|
|
150
196
|
},
|
|
151
197
|
body: JSON.stringify(imageSboms),
|
|
152
|
-
});
|
|
198
|
+
}, opts);
|
|
199
|
+
const resp = await fetch(finalUrl, fetchOptions);
|
|
153
200
|
if (resp.status === 200) {
|
|
154
201
|
let result;
|
|
155
202
|
if (!html) {
|
|
@@ -173,6 +220,24 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
|
|
|
173
220
|
throw new Error(`Got error response from Trustify DA backend - http return code : ${resp.status}, ex-request-id: ${resp.headers.get("ex-request-id")} error message => ${await resp.text()}`);
|
|
174
221
|
}
|
|
175
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Append common analysis query parameters (recommend, providers, sources) to a URL.
|
|
225
|
+
* @param {URL} url - the URL object to append query parameters to
|
|
226
|
+
* @param {import("index.js").Options} [opts={}] - options containing parameter overrides
|
|
227
|
+
*/
|
|
228
|
+
function appendAnalysisQueryParams(url, opts = {}) {
|
|
229
|
+
if (getCustom('TRUSTIFY_DA_RECOMMEND', 'true', opts) === 'false') {
|
|
230
|
+
url.searchParams.append('recommend', 'false');
|
|
231
|
+
}
|
|
232
|
+
const providers = getCustom('TRUSTIFY_DA_PROVIDERS', null, opts);
|
|
233
|
+
if (providers) {
|
|
234
|
+
url.searchParams.append('providers', providers);
|
|
235
|
+
}
|
|
236
|
+
const sources = getCustom('TRUSTIFY_DA_SOURCES', null, opts);
|
|
237
|
+
if (sources) {
|
|
238
|
+
url.searchParams.append('sources', sources);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
176
241
|
/**
|
|
177
242
|
*
|
|
178
243
|
* @param url the backend url to send the request to
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether to skip failed manifests and continue (default), or fail on first SBOM/validation error.
|
|
3
|
+
* `opts.continueOnError` overrides; env `TRUSTIFY_DA_CONTINUE_ON_ERROR=false` disables continuation.
|
|
4
|
+
*
|
|
5
|
+
* @param {{ continueOnError?: boolean, TRUSTIFY_DA_CONTINUE_ON_ERROR?: string, [key: string]: unknown }} [opts={}]
|
|
6
|
+
* @returns {boolean} true = collect errors (default), false = fail-fast
|
|
7
|
+
*/
|
|
8
|
+
export function resolveContinueOnError(opts?: {
|
|
9
|
+
continueOnError?: boolean;
|
|
10
|
+
TRUSTIFY_DA_CONTINUE_ON_ERROR?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* When true, `stackAnalysisBatch` returns `{ analysis, metadata }` instead of the backend response only.
|
|
15
|
+
* `opts.batchMetadata` overrides; env `TRUSTIFY_DA_BATCH_METADATA=true` enables.
|
|
16
|
+
*
|
|
17
|
+
* @param {{ batchMetadata?: boolean, TRUSTIFY_DA_BATCH_METADATA?: string, [key: string]: unknown }} [opts={}]
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
export function resolveBatchMetadata(opts?: {
|
|
21
|
+
batchMetadata?: boolean;
|
|
22
|
+
TRUSTIFY_DA_BATCH_METADATA?: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getCustom } from './tools.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether to skip failed manifests and continue (default), or fail on first SBOM/validation error.
|
|
4
|
+
* `opts.continueOnError` overrides; env `TRUSTIFY_DA_CONTINUE_ON_ERROR=false` disables continuation.
|
|
5
|
+
*
|
|
6
|
+
* @param {{ continueOnError?: boolean, TRUSTIFY_DA_CONTINUE_ON_ERROR?: string, [key: string]: unknown }} [opts={}]
|
|
7
|
+
* @returns {boolean} true = collect errors (default), false = fail-fast
|
|
8
|
+
*/
|
|
9
|
+
export function resolveContinueOnError(opts = {}) {
|
|
10
|
+
if (typeof opts.continueOnError === 'boolean') {
|
|
11
|
+
return opts.continueOnError;
|
|
12
|
+
}
|
|
13
|
+
const v = getCustom('TRUSTIFY_DA_CONTINUE_ON_ERROR', null, opts);
|
|
14
|
+
if (v != null && String(v).trim() !== '') {
|
|
15
|
+
return String(v).toLowerCase() !== 'false';
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* When true, `stackAnalysisBatch` returns `{ analysis, metadata }` instead of the backend response only.
|
|
21
|
+
* `opts.batchMetadata` overrides; env `TRUSTIFY_DA_BATCH_METADATA=true` enables.
|
|
22
|
+
*
|
|
23
|
+
* @param {{ batchMetadata?: boolean, TRUSTIFY_DA_BATCH_METADATA?: string, [key: string]: unknown }} [opts={}]
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function resolveBatchMetadata(opts = {}) {
|
|
27
|
+
if (typeof opts.batchMetadata === 'boolean') {
|
|
28
|
+
return opts.batchMetadata;
|
|
29
|
+
}
|
|
30
|
+
const v = getCustom('TRUSTIFY_DA_BATCH_METADATA', null, opts);
|
|
31
|
+
if (v != null && String(v).trim() !== '') {
|
|
32
|
+
return String(v).toLowerCase() === 'true';
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
package/dist/src/cli.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import * as path from "path";
|
|
3
4
|
import yargs from 'yargs';
|
|
4
5
|
import { hideBin } from 'yargs/helpers';
|
|
5
6
|
import { getProjectLicense, getLicenseDetails } from './license/index.js';
|
|
6
|
-
import
|
|
7
|
+
import { runRemediation } from './remediate.js';
|
|
8
|
+
import { generateReport } from './remediation_report.js';
|
|
9
|
+
import client, { selectTrustifyDABackend, generateSbom } from './index.js';
|
|
7
10
|
// command for component analysis take manifest type and content
|
|
8
11
|
const component = {
|
|
9
12
|
command: 'component </path/to/manifest>',
|
|
@@ -12,10 +15,32 @@ const component = {
|
|
|
12
15
|
desc: 'manifest path for analyzing',
|
|
13
16
|
type: 'string',
|
|
14
17
|
normalize: true,
|
|
18
|
+
}).options({
|
|
19
|
+
workspaceDir: {
|
|
20
|
+
alias: 'w',
|
|
21
|
+
desc: 'Workspace root directory (for monorepos; lock file is expected here)',
|
|
22
|
+
type: 'string',
|
|
23
|
+
normalize: true,
|
|
24
|
+
},
|
|
25
|
+
providers: {
|
|
26
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
27
|
+
type: 'string',
|
|
28
|
+
},
|
|
29
|
+
sources: {
|
|
30
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
31
|
+
type: 'string',
|
|
32
|
+
}
|
|
15
33
|
}),
|
|
16
34
|
handler: async (args) => {
|
|
17
35
|
let manifestName = args['/path/to/manifest'];
|
|
18
|
-
|
|
36
|
+
const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
|
|
37
|
+
if (args.providers) {
|
|
38
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
39
|
+
}
|
|
40
|
+
if (args.sources) {
|
|
41
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
42
|
+
}
|
|
43
|
+
let res = await client.componentAnalysis(manifestName, opts);
|
|
19
44
|
console.log(JSON.stringify(res, null, 2));
|
|
20
45
|
}
|
|
21
46
|
};
|
|
@@ -63,6 +88,14 @@ const image = {
|
|
|
63
88
|
desc: 'For JSON report, get only the \'summary\'',
|
|
64
89
|
type: 'boolean',
|
|
65
90
|
conflicts: 'html'
|
|
91
|
+
},
|
|
92
|
+
providers: {
|
|
93
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
94
|
+
type: 'string',
|
|
95
|
+
},
|
|
96
|
+
sources: {
|
|
97
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
98
|
+
type: 'string',
|
|
66
99
|
}
|
|
67
100
|
}),
|
|
68
101
|
handler: async (args) => {
|
|
@@ -72,7 +105,14 @@ const image = {
|
|
|
72
105
|
}
|
|
73
106
|
let html = args['html'];
|
|
74
107
|
let summary = args['summary'];
|
|
75
|
-
|
|
108
|
+
const opts = {};
|
|
109
|
+
if (args.providers) {
|
|
110
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
111
|
+
}
|
|
112
|
+
if (args.sources) {
|
|
113
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
114
|
+
}
|
|
115
|
+
let res = await client.imageAnalysis(imageRefs, html, opts);
|
|
76
116
|
if (summary && !html) {
|
|
77
117
|
let summaries = {};
|
|
78
118
|
for (let [imageRef, report] of Object.entries(res)) {
|
|
@@ -117,15 +157,36 @@ const stack = {
|
|
|
117
157
|
desc: 'For JSON report, get only the \'summary\'',
|
|
118
158
|
type: 'boolean',
|
|
119
159
|
conflicts: 'html'
|
|
160
|
+
},
|
|
161
|
+
workspaceDir: {
|
|
162
|
+
alias: 'w',
|
|
163
|
+
desc: 'Workspace root directory (for monorepos; lock file is expected here)',
|
|
164
|
+
type: 'string',
|
|
165
|
+
normalize: true,
|
|
166
|
+
},
|
|
167
|
+
providers: {
|
|
168
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
169
|
+
type: 'string',
|
|
170
|
+
},
|
|
171
|
+
sources: {
|
|
172
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
173
|
+
type: 'string',
|
|
120
174
|
}
|
|
121
175
|
}),
|
|
122
176
|
handler: async (args) => {
|
|
123
177
|
let manifest = args['/path/to/manifest'];
|
|
124
178
|
let html = args['html'];
|
|
125
179
|
let summary = args['summary'];
|
|
180
|
+
const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
|
|
181
|
+
if (args.providers) {
|
|
182
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
183
|
+
}
|
|
184
|
+
if (args.sources) {
|
|
185
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
186
|
+
}
|
|
126
187
|
let theProvidersSummary = new Map();
|
|
127
188
|
let theProvidersObject = {};
|
|
128
|
-
let res = await client.stackAnalysis(manifest, html);
|
|
189
|
+
let res = await client.stackAnalysis(manifest, html, opts);
|
|
129
190
|
if (summary) {
|
|
130
191
|
for (let provider in res.providers) {
|
|
131
192
|
if (res.providers[provider].sources !== undefined) {
|
|
@@ -143,6 +204,122 @@ const stack = {
|
|
|
143
204
|
console.log(html ? res : JSON.stringify(!html && summary ? theProvidersObject : res, null, 2));
|
|
144
205
|
}
|
|
145
206
|
};
|
|
207
|
+
// command for batch stack analysis (workspace)
|
|
208
|
+
const stackBatch = {
|
|
209
|
+
command: 'stack-batch </path/to/workspace-root> [--html|--summary] [--concurrency <n>] [--ignore <pattern>...] [--metadata] [--fail-fast]',
|
|
210
|
+
desc: 'produce stack report for all packages/crates in a workspace (Cargo or JS/TS)',
|
|
211
|
+
builder: yargs => yargs.positional('/path/to/workspace-root', {
|
|
212
|
+
desc: 'workspace root directory (containing Cargo.toml+Cargo.lock or package.json+lock file)',
|
|
213
|
+
type: 'string',
|
|
214
|
+
normalize: true,
|
|
215
|
+
}).options({
|
|
216
|
+
html: {
|
|
217
|
+
alias: 'r',
|
|
218
|
+
desc: 'Get the report as HTML instead of JSON',
|
|
219
|
+
type: 'boolean',
|
|
220
|
+
conflicts: 'summary'
|
|
221
|
+
},
|
|
222
|
+
summary: {
|
|
223
|
+
alias: 's',
|
|
224
|
+
desc: 'For JSON report, get only the \'summary\' per package',
|
|
225
|
+
type: 'boolean',
|
|
226
|
+
conflicts: 'html'
|
|
227
|
+
},
|
|
228
|
+
concurrency: {
|
|
229
|
+
alias: 'c',
|
|
230
|
+
desc: 'Max parallel SBOM generations (default: 10, env: TRUSTIFY_DA_BATCH_CONCURRENCY)',
|
|
231
|
+
type: 'number',
|
|
232
|
+
},
|
|
233
|
+
ignore: {
|
|
234
|
+
alias: 'i',
|
|
235
|
+
desc: 'Extra glob patterns excluded from workspace discovery (merged with defaults). Repeat flag per pattern. Env: TRUSTIFY_DA_WORKSPACE_DISCOVERY_IGNORE (comma-separated)',
|
|
236
|
+
type: 'string',
|
|
237
|
+
array: true,
|
|
238
|
+
},
|
|
239
|
+
metadata: {
|
|
240
|
+
alias: 'm',
|
|
241
|
+
desc: 'Return { analysis, metadata } with per-manifest errors (env: TRUSTIFY_DA_BATCH_METADATA=true)',
|
|
242
|
+
type: 'boolean',
|
|
243
|
+
default: false,
|
|
244
|
+
},
|
|
245
|
+
failFast: {
|
|
246
|
+
desc: 'Stop on first invalid package.json or SBOM error (env: TRUSTIFY_DA_CONTINUE_ON_ERROR=false)',
|
|
247
|
+
type: 'boolean',
|
|
248
|
+
default: false,
|
|
249
|
+
},
|
|
250
|
+
providers: {
|
|
251
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
252
|
+
type: 'string',
|
|
253
|
+
},
|
|
254
|
+
sources: {
|
|
255
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
256
|
+
type: 'string',
|
|
257
|
+
}
|
|
258
|
+
}),
|
|
259
|
+
handler: async (args) => {
|
|
260
|
+
const workspaceRoot = args['/path/to/workspace-root'];
|
|
261
|
+
const html = args['html'];
|
|
262
|
+
const summary = args['summary'];
|
|
263
|
+
const opts = {};
|
|
264
|
+
if (args.concurrency != null) {
|
|
265
|
+
opts.batchConcurrency = args.concurrency;
|
|
266
|
+
}
|
|
267
|
+
const extraIgnores = Array.isArray(args.ignore) ? args.ignore.filter(p => p != null && String(p).trim()) : [];
|
|
268
|
+
if (extraIgnores.length > 0) {
|
|
269
|
+
opts.workspaceDiscoveryIgnore = extraIgnores;
|
|
270
|
+
}
|
|
271
|
+
if (args.metadata) {
|
|
272
|
+
opts.batchMetadata = true;
|
|
273
|
+
}
|
|
274
|
+
if (args.failFast) {
|
|
275
|
+
opts.continueOnError = false;
|
|
276
|
+
}
|
|
277
|
+
if (args.providers) {
|
|
278
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
279
|
+
}
|
|
280
|
+
if (args.sources) {
|
|
281
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
282
|
+
}
|
|
283
|
+
let res = await client.stackAnalysisBatch(workspaceRoot, html, opts);
|
|
284
|
+
const batchAnalysis = res && typeof res === 'object' && res != null && 'analysis' in res ? res.analysis : res;
|
|
285
|
+
if (summary && !html && typeof batchAnalysis === 'object') {
|
|
286
|
+
const summaries = {};
|
|
287
|
+
for (const [purl, report] of Object.entries(batchAnalysis)) {
|
|
288
|
+
if (report?.providers) {
|
|
289
|
+
for (const provider of Object.keys(report.providers)) {
|
|
290
|
+
const sources = report.providers[provider]?.sources;
|
|
291
|
+
if (sources) {
|
|
292
|
+
for (const [source, data] of Object.entries(sources)) {
|
|
293
|
+
if (data?.summary) {
|
|
294
|
+
if (!summaries[purl]) {
|
|
295
|
+
summaries[purl] = {};
|
|
296
|
+
}
|
|
297
|
+
if (!summaries[purl][provider]) {
|
|
298
|
+
summaries[purl][provider] = {};
|
|
299
|
+
}
|
|
300
|
+
summaries[purl][provider][source] = data.summary;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (res && typeof res === 'object' && res != null && 'metadata' in res) {
|
|
308
|
+
res = { analysis: summaries, metadata: res.metadata };
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
res = summaries;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (html) {
|
|
315
|
+
const htmlContent = res && typeof res === 'object' && 'analysis' in res ? res.analysis : res;
|
|
316
|
+
console.log(htmlContent);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
console.log(JSON.stringify(res, null, 2));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
146
323
|
// command for license checking
|
|
147
324
|
const license = {
|
|
148
325
|
command: 'license </path/to/manifest>',
|
|
@@ -208,14 +385,119 @@ const license = {
|
|
|
208
385
|
console.log(JSON.stringify(output, null, 2));
|
|
209
386
|
}
|
|
210
387
|
};
|
|
388
|
+
const sbom = {
|
|
389
|
+
command: 'sbom </path/to/manifest> [--output]',
|
|
390
|
+
desc: 'generate a CycloneDX SBOM from a manifest file',
|
|
391
|
+
builder: yargs => yargs.positional('/path/to/manifest', {
|
|
392
|
+
desc: 'manifest path for SBOM generation',
|
|
393
|
+
type: 'string',
|
|
394
|
+
normalize: true,
|
|
395
|
+
}).options({
|
|
396
|
+
output: {
|
|
397
|
+
alias: 'o',
|
|
398
|
+
desc: 'Write SBOM JSON to a file instead of stdout',
|
|
399
|
+
type: 'string',
|
|
400
|
+
normalize: true,
|
|
401
|
+
},
|
|
402
|
+
workspaceDir: {
|
|
403
|
+
alias: 'w',
|
|
404
|
+
desc: 'Workspace root directory (for monorepos; lock file is expected here)',
|
|
405
|
+
type: 'string',
|
|
406
|
+
normalize: true,
|
|
407
|
+
}
|
|
408
|
+
}),
|
|
409
|
+
handler: async (args) => {
|
|
410
|
+
let manifest = args['/path/to/manifest'];
|
|
411
|
+
const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
|
|
412
|
+
let result;
|
|
413
|
+
try {
|
|
414
|
+
result = await generateSbom(manifest, opts);
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
console.error(JSON.stringify({ error: `Failed to generate SBOM: ${err.message}` }, null, 2));
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
const json = JSON.stringify(result, null, 2);
|
|
421
|
+
if (args.output) {
|
|
422
|
+
try {
|
|
423
|
+
fs.writeFileSync(args.output, json);
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
console.error(JSON.stringify({ error: `Failed to write output file: ${err.message}` }, null, 2));
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
console.log(json);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
const remediate = {
|
|
436
|
+
command: 'remediate <path>',
|
|
437
|
+
desc: 'Scan and apply vulnerability remediations to manifest files',
|
|
438
|
+
builder: yargs => yargs.positional('path', {
|
|
439
|
+
desc: 'Path to manifest file or directory',
|
|
440
|
+
type: 'string',
|
|
441
|
+
normalize: true,
|
|
442
|
+
}).options({
|
|
443
|
+
'dry-run': {
|
|
444
|
+
alias: 'd',
|
|
445
|
+
type: 'boolean',
|
|
446
|
+
desc: 'Preview changes without modifying files',
|
|
447
|
+
},
|
|
448
|
+
providers: {
|
|
449
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
450
|
+
type: 'string',
|
|
451
|
+
},
|
|
452
|
+
sources: {
|
|
453
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
454
|
+
type: 'string',
|
|
455
|
+
},
|
|
456
|
+
'group-by': {
|
|
457
|
+
type: 'string',
|
|
458
|
+
choices: ['dependency', 'bundle'],
|
|
459
|
+
default: 'dependency',
|
|
460
|
+
desc: 'Report grouping strategy',
|
|
461
|
+
},
|
|
462
|
+
}),
|
|
463
|
+
handler: async (args) => {
|
|
464
|
+
try {
|
|
465
|
+
const result = await runRemediation(args.path, {
|
|
466
|
+
dryRun: args['dry-run'],
|
|
467
|
+
providers: args.providers,
|
|
468
|
+
sources: args.sources,
|
|
469
|
+
});
|
|
470
|
+
if (result.remediations.length === 0 && result.manifests.length === 0) {
|
|
471
|
+
console.log('No supported manifest files found.');
|
|
472
|
+
process.exit(result.exitCode);
|
|
473
|
+
}
|
|
474
|
+
if (!args['dry-run'] && result.appliedFiles.length > 0) {
|
|
475
|
+
console.log(`Updated ${result.appliedFiles.length} file(s):`);
|
|
476
|
+
for (const file of result.appliedFiles) {
|
|
477
|
+
console.log(` ${file}`);
|
|
478
|
+
}
|
|
479
|
+
console.log('');
|
|
480
|
+
}
|
|
481
|
+
console.log(generateReport(result.remediations, { groupBy: args['group-by'], dryRun: args['dry-run'] }));
|
|
482
|
+
process.exit(result.exitCode);
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
console.error(err.message);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
};
|
|
211
490
|
// parse and invoke the command
|
|
212
491
|
yargs(hideBin(process.argv))
|
|
213
|
-
.usage(`Usage: ${process.argv[0].includes("node") ? path.parse(process.argv[1]).base : path.parse(process.argv[0]).base} {component|stack|image|validate-token|license}`)
|
|
492
|
+
.usage(`Usage: ${process.argv[0].includes("node") ? path.parse(process.argv[1]).base : path.parse(process.argv[0]).base} {component|stack|stack-batch|image|validate-token|license|sbom|remediate}`)
|
|
214
493
|
.command(stack)
|
|
494
|
+
.command(stackBatch)
|
|
215
495
|
.command(component)
|
|
216
496
|
.command(image)
|
|
217
497
|
.command(validateToken)
|
|
218
498
|
.command(license)
|
|
499
|
+
.command(sbom)
|
|
500
|
+
.command(remediate)
|
|
219
501
|
.scriptName('')
|
|
220
502
|
.version(false)
|
|
221
503
|
.demandCommand(1)
|