@trustify-da/trustify-da-javascript-client 0.3.0-ea.2ea1d77 → 0.3.0-ea.328523e
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 +272 -19
- package/dist/package.json +33 -28
- package/dist/src/analysis.d.ts +52 -22
- package/dist/src/analysis.js +77 -22
- package/dist/src/cli.js +238 -7
- package/dist/src/config.d.ts +120 -0
- package/dist/src/config.js +260 -0
- package/dist/src/cyclone_dx_sbom.d.ts +27 -1
- package/dist/src/cyclone_dx_sbom.js +82 -9
- package/dist/src/index.d.ts +97 -22
- package/dist/src/index.js +115 -42
- package/dist/src/license/index.d.ts +4 -4
- package/dist/src/license/index.js +13 -7
- package/dist/src/license/license_utils.js +4 -1
- package/dist/src/license/licenses_api.d.ts +3 -3
- package/dist/src/license/licenses_api.js +1 -1
- 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 +4 -3
- package/dist/src/provider.js +12 -2
- 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 +90 -15
- package/dist/src/providers/base_javascript.js +206 -24
- 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 +208 -120
- 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 +320 -22
- 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 +37 -9
- package/dist/src/providers/java_maven.js +269 -11
- 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 +13 -0
- package/dist/src/providers/javascript_pnpm.js +53 -3
- 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 +6 -6
- 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 +275 -0
- package/dist/src/providers/requirements_parser.js +1 -1
- package/dist/src/providers/rust_cargo.d.ts +6 -2
- package/dist/src/providers/rust_cargo.js +101 -33
- package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
- package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
- package/dist/src/remediate.d.ts +107 -0
- package/dist/src/remediate.js +244 -0
- package/dist/src/remediation.d.ts +81 -0
- package/dist/src/remediation.js +475 -0
- package/dist/src/remediation_report.d.ts +23 -0
- package/dist/src/remediation_report.js +180 -0
- package/dist/src/sbom.d.ts +25 -1
- package/dist/src/sbom.js +23 -2
- package/dist/src/tools.d.ts +37 -13
- package/dist/src/tools.js +92 -13
- 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 +11 -1
- package/dist/src/workspace.js +3 -2
- package/package.json +34 -29
package/dist/src/analysis.d.ts
CHANGED
|
@@ -1,9 +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 };
|
|
4
6
|
export { requestStackBatch };
|
|
5
7
|
export { requestImages };
|
|
6
8
|
export { validateToken };
|
|
9
|
+
export { appendAnalysisQueryParams };
|
|
7
10
|
}
|
|
8
11
|
export default _default;
|
|
9
12
|
/**
|
|
@@ -12,41 +15,62 @@ export default _default;
|
|
|
12
15
|
* @param {string} manifest - path for the manifest
|
|
13
16
|
* @param {string} url - the backend url to send the request to
|
|
14
17
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
|
|
15
|
-
* @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
|
|
18
|
+
* @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>}
|
|
16
19
|
*/
|
|
17
|
-
declare function requestComponent(provider:
|
|
20
|
+
declare function requestComponent(provider: any, manifest: string, url: string, opts?: any): Promise<import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport>;
|
|
18
21
|
/**
|
|
19
|
-
*
|
|
20
|
-
* @param {import('./provider').Provider} provider
|
|
21
|
-
* @param {string} manifest
|
|
22
|
-
* @param {string} url
|
|
23
|
-
* @param {
|
|
24
|
-
* @param {import("index.js").Options} [opts={}]
|
|
25
|
-
* @returns {Promise<string
|
|
22
|
+
* @overload
|
|
23
|
+
* @param {import('./provider').Provider} provider
|
|
24
|
+
* @param {string} manifest
|
|
25
|
+
* @param {string} url
|
|
26
|
+
* @param {true} [html]
|
|
27
|
+
* @param {import("index.js").Options} [opts={}]
|
|
28
|
+
* @returns {Promise<string>}
|
|
29
|
+
*/
|
|
30
|
+
declare function requestStack(provider: any, manifest: string, url: string, html?: true | undefined, opts?: any): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* @overload
|
|
33
|
+
* @param {import('./provider').Provider} provider
|
|
34
|
+
* @param {string} manifest
|
|
35
|
+
* @param {string} url
|
|
36
|
+
* @param {false} [html]
|
|
37
|
+
* @param {import("index.js").Options} [opts={}]
|
|
38
|
+
* @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
|
|
26
39
|
*/
|
|
27
|
-
declare function requestStack(provider:
|
|
40
|
+
declare function requestStack(provider: any, manifest: string, url: string, html?: false | undefined, opts?: any): Promise<import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport>;
|
|
28
41
|
/**
|
|
29
|
-
*
|
|
30
|
-
* @param {Object.<string, object>} sbomByPurl
|
|
31
|
-
* @param {string} url
|
|
32
|
-
* @param {
|
|
42
|
+
* @overload
|
|
43
|
+
* @param {Object.<string, object>} sbomByPurl
|
|
44
|
+
* @param {string} url
|
|
45
|
+
* @param {true} [html=false]
|
|
33
46
|
* @param {import("index.js").Options} [opts={}]
|
|
34
|
-
* @returns {Promise<string
|
|
47
|
+
* @returns {Promise<string>}
|
|
35
48
|
*/
|
|
36
49
|
declare function requestStackBatch(sbomByPurl: {
|
|
37
|
-
[x: string]:
|
|
38
|
-
}, url: string, html?:
|
|
39
|
-
|
|
50
|
+
[x: string]: object;
|
|
51
|
+
}, url: string, html?: true | undefined, opts?: any): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* @overload
|
|
54
|
+
* @param {Object.<string, object>} sbomByPurl
|
|
55
|
+
* @param {string} url
|
|
56
|
+
* @param {false} [html=false]
|
|
57
|
+
* @param {import("index.js").Options} [opts={}]
|
|
58
|
+
* @returns {Promise<Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
|
|
59
|
+
*/
|
|
60
|
+
declare function requestStackBatch(sbomByPurl: {
|
|
61
|
+
[x: string]: object;
|
|
62
|
+
}, url: string, html?: false | undefined, opts?: any): Promise<{
|
|
63
|
+
[x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport;
|
|
40
64
|
}>;
|
|
41
65
|
/**
|
|
42
66
|
*
|
|
43
67
|
* @param {Array<string>} imageRefs
|
|
44
68
|
* @param {string} url
|
|
45
69
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
|
|
46
|
-
* @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
|
|
70
|
+
* @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
|
|
47
71
|
*/
|
|
48
|
-
declare function requestImages(imageRefs: Array<string>, url: string, html?: boolean, opts?:
|
|
49
|
-
[x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
|
|
72
|
+
declare function requestImages(imageRefs: Array<string>, url: string, html?: boolean, opts?: any): Promise<string | {
|
|
73
|
+
[x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport;
|
|
50
74
|
}>;
|
|
51
75
|
/**
|
|
52
76
|
*
|
|
@@ -54,4 +78,10 @@ declare function requestImages(imageRefs: Array<string>, url: string, html?: boo
|
|
|
54
78
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass headers for t he validateToken Request
|
|
55
79
|
* @return {Promise<number>} return the HTTP status Code of the response from the validate token request.
|
|
56
80
|
*/
|
|
57
|
-
declare function validateToken(url: any, opts?:
|
|
81
|
+
declare function validateToken(url: any, opts?: any): Promise<number>;
|
|
82
|
+
/**
|
|
83
|
+
* Append common analysis query parameters (recommend, providers, sources) to a URL.
|
|
84
|
+
* @param {URL} url - the URL object to append query parameters to
|
|
85
|
+
* @param {import("index.js").Options} [opts={}] - options containing parameter overrides
|
|
86
|
+
*/
|
|
87
|
+
declare function appendAnalysisQueryParams(url: URL, opts?: any): void;
|
package/dist/src/analysis.js
CHANGED
|
@@ -4,7 +4,27 @@ 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 };
|
|
10
|
+
/**
|
|
11
|
+
* @overload
|
|
12
|
+
* @param {import('./provider').Provider} provider
|
|
13
|
+
* @param {string} manifest
|
|
14
|
+
* @param {string} url
|
|
15
|
+
* @param {true} [html]
|
|
16
|
+
* @param {import("index.js").Options} [opts={}]
|
|
17
|
+
* @returns {Promise<string>}
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* @overload
|
|
21
|
+
* @param {import('./provider').Provider} provider
|
|
22
|
+
* @param {string} manifest
|
|
23
|
+
* @param {string} url
|
|
24
|
+
* @param {false} [html]
|
|
25
|
+
* @param {import("index.js").Options} [opts={}]
|
|
26
|
+
* @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
|
|
27
|
+
*/
|
|
8
28
|
/**
|
|
9
29
|
* Send a stack analysis request and get the report as 'text/html' or 'application/json'.
|
|
10
30
|
* @param {import('./provider').Provider} provider - the provided data for constructing the request
|
|
@@ -12,12 +32,15 @@ export default { requestComponent, requestStack, requestStackBatch, requestImage
|
|
|
12
32
|
* @param {string} url - the backend url to send the request to
|
|
13
33
|
* @param {boolean} [html=false] - true will return 'text/html', false will return 'application/json'
|
|
14
34
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
|
|
15
|
-
* @returns {Promise<string|import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
|
|
35
|
+
* @returns {Promise<string|import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>}
|
|
16
36
|
*/
|
|
17
37
|
async function requestStack(provider, manifest, url, html = false, opts = {}) {
|
|
18
38
|
opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
|
|
19
39
|
opts["manifest-type"] = path.parse(manifest).base;
|
|
20
40
|
let provided = await provider.provideStack(manifest, opts); // throws error if content providing failed
|
|
41
|
+
if (provided.batch) {
|
|
42
|
+
return requestStackBatch(JSON.parse(provided.content), url, html, opts);
|
|
43
|
+
}
|
|
21
44
|
opts["source-manifest"] = "";
|
|
22
45
|
opts[TRUSTIFY_DA_OPERATION_TYPE_HEADER.toUpperCase().replaceAll("-", "_")] = "stack-analysis";
|
|
23
46
|
let startTime = new Date();
|
|
@@ -36,9 +59,7 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
|
|
|
36
59
|
body: provided.content
|
|
37
60
|
}, opts);
|
|
38
61
|
const finalUrl = new URL(`${url}/api/v5/analysis`);
|
|
39
|
-
|
|
40
|
-
finalUrl.searchParams.append('recommend', 'false');
|
|
41
|
-
}
|
|
62
|
+
appendAnalysisQueryParams(finalUrl, opts);
|
|
42
63
|
let resp = await fetch(finalUrl, fetchOptions);
|
|
43
64
|
let result;
|
|
44
65
|
if (resp.status === 200) {
|
|
@@ -72,11 +93,14 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
|
|
|
72
93
|
* @param {string} manifest - path for the manifest
|
|
73
94
|
* @param {string} url - the backend url to send the request to
|
|
74
95
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
|
|
75
|
-
* @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
|
|
96
|
+
* @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>}
|
|
76
97
|
*/
|
|
77
98
|
async function requestComponent(provider, manifest, url, opts = {}) {
|
|
78
99
|
opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
|
|
79
100
|
let provided = await provider.provideComponent(manifest, opts); // throws error if content providing failed
|
|
101
|
+
if (provided.batch) {
|
|
102
|
+
return requestStackBatch(JSON.parse(provided.content), url, false, opts);
|
|
103
|
+
}
|
|
80
104
|
opts["source-manifest"] = "";
|
|
81
105
|
opts[TRUSTIFY_DA_OPERATION_TYPE_HEADER.toUpperCase().replaceAll("-", "_")] = "component-analysis";
|
|
82
106
|
if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
|
|
@@ -93,9 +117,7 @@ async function requestComponent(provider, manifest, url, opts = {}) {
|
|
|
93
117
|
body: provided.content
|
|
94
118
|
}, opts);
|
|
95
119
|
const finalUrl = new URL(`${url}/api/v5/analysis`);
|
|
96
|
-
|
|
97
|
-
finalUrl.searchParams.append('recommend', 'false');
|
|
98
|
-
}
|
|
120
|
+
appendAnalysisQueryParams(finalUrl, opts);
|
|
99
121
|
let resp = await fetch(finalUrl, fetchOptions);
|
|
100
122
|
let result;
|
|
101
123
|
if (resp.status === 200) {
|
|
@@ -109,7 +131,9 @@ async function requestComponent(provider, manifest, url, opts = {}) {
|
|
|
109
131
|
console.log(JSON.stringify(result, null, 4));
|
|
110
132
|
console.log("Ending time of sending component analysis request to Trustify DA backend server= " + new Date());
|
|
111
133
|
}
|
|
112
|
-
const licenseCheckEnabled =
|
|
134
|
+
const licenseCheckEnabled = typeof opts.licenseCheck === 'boolean'
|
|
135
|
+
? opts.licenseCheck
|
|
136
|
+
: getCustom('TRUSTIFY_DA_LICENSE_CHECK', 'true', opts) !== 'false';
|
|
113
137
|
if (licenseCheckEnabled) {
|
|
114
138
|
try {
|
|
115
139
|
result.licenseSummary = await runLicenseCheck(provided.content, manifest, url, opts, result);
|
|
@@ -124,24 +148,38 @@ async function requestComponent(provider, manifest, url, opts = {}) {
|
|
|
124
148
|
}
|
|
125
149
|
return Promise.resolve(result);
|
|
126
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* @overload
|
|
153
|
+
* @param {Object.<string, object>} sbomByPurl
|
|
154
|
+
* @param {string} url
|
|
155
|
+
* @param {true} [html=false]
|
|
156
|
+
* @param {import("index.js").Options} [opts={}]
|
|
157
|
+
* @returns {Promise<string>}
|
|
158
|
+
*/
|
|
159
|
+
/**
|
|
160
|
+
* @overload
|
|
161
|
+
* @param {Object.<string, object>} sbomByPurl
|
|
162
|
+
* @param {string} url
|
|
163
|
+
* @param {false} [html=false]
|
|
164
|
+
* @param {import("index.js").Options} [opts={}]
|
|
165
|
+
* @returns {Promise<Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
|
|
166
|
+
*/
|
|
127
167
|
/**
|
|
128
168
|
* Send a batch stack analysis request for multiple manifests (SBOMs keyed by purl).
|
|
129
169
|
* @param {Object.<string, object>} sbomByPurl - Map of root purl to CycloneDX SBOM object
|
|
130
170
|
* @param {string} url - the backend url
|
|
131
171
|
* @param {boolean} [html=false] - true returns HTML, false returns JSON
|
|
132
172
|
* @param {import("index.js").Options} [opts={}]
|
|
133
|
-
* @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
|
|
173
|
+
* @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
|
|
134
174
|
*/
|
|
135
175
|
async function requestStackBatch(sbomByPurl, url, html = false, opts = {}) {
|
|
136
176
|
const finalUrl = new URL(`${url}/api/v5/batch-analysis`);
|
|
137
|
-
|
|
138
|
-
finalUrl.searchParams.append('recommend', 'false');
|
|
139
|
-
}
|
|
177
|
+
appendAnalysisQueryParams(finalUrl, opts);
|
|
140
178
|
const fetchOptions = addProxyAgent({
|
|
141
179
|
method: 'POST',
|
|
142
180
|
headers: {
|
|
143
181
|
'Accept': html ? 'text/html' : 'application/json',
|
|
144
|
-
'Content-Type':
|
|
182
|
+
'Content-Type': CYCLONEDX_JSON_MEDIA_TYPE,
|
|
145
183
|
...getTokenHeaders(opts)
|
|
146
184
|
},
|
|
147
185
|
body: JSON.stringify(sbomByPurl)
|
|
@@ -175,7 +213,7 @@ async function requestStackBatch(sbomByPurl, url, html = false, opts = {}) {
|
|
|
175
213
|
* @param {Array<string>} imageRefs
|
|
176
214
|
* @param {string} url
|
|
177
215
|
* @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
|
|
178
|
-
* @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
|
|
216
|
+
* @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
|
|
179
217
|
*/
|
|
180
218
|
async function requestImages(imageRefs, url, html = false, opts = {}) {
|
|
181
219
|
const imageSboms = {};
|
|
@@ -184,18 +222,17 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
|
|
|
184
222
|
imageSboms[parsedImageRef.getPackageURL().toString()] = generateImageSBOM(parsedImageRef, opts);
|
|
185
223
|
}
|
|
186
224
|
const finalUrl = new URL(`${url}/api/v5/batch-analysis`);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
const resp = await fetch(finalUrl, {
|
|
225
|
+
appendAnalysisQueryParams(finalUrl, opts);
|
|
226
|
+
const fetchOptions = addProxyAgent({
|
|
191
227
|
method: 'POST',
|
|
192
228
|
headers: {
|
|
193
229
|
'Accept': html ? 'text/html' : 'application/json',
|
|
194
|
-
'Content-Type':
|
|
230
|
+
'Content-Type': CYCLONEDX_JSON_MEDIA_TYPE,
|
|
195
231
|
...getTokenHeaders(opts)
|
|
196
232
|
},
|
|
197
233
|
body: JSON.stringify(imageSboms),
|
|
198
|
-
});
|
|
234
|
+
}, opts);
|
|
235
|
+
const resp = await fetch(finalUrl, fetchOptions);
|
|
199
236
|
if (resp.status === 200) {
|
|
200
237
|
let result;
|
|
201
238
|
if (!html) {
|
|
@@ -219,6 +256,24 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
|
|
|
219
256
|
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()}`);
|
|
220
257
|
}
|
|
221
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Append common analysis query parameters (recommend, providers, sources) to a URL.
|
|
261
|
+
* @param {URL} url - the URL object to append query parameters to
|
|
262
|
+
* @param {import("index.js").Options} [opts={}] - options containing parameter overrides
|
|
263
|
+
*/
|
|
264
|
+
function appendAnalysisQueryParams(url, opts = {}) {
|
|
265
|
+
if (getCustom('TRUSTIFY_DA_RECOMMEND', 'true', opts) === 'false') {
|
|
266
|
+
url.searchParams.append('recommend', 'false');
|
|
267
|
+
}
|
|
268
|
+
const providers = getCustom('TRUSTIFY_DA_PROVIDERS', null, opts);
|
|
269
|
+
if (providers) {
|
|
270
|
+
url.searchParams.append('providers', providers);
|
|
271
|
+
}
|
|
272
|
+
const sources = getCustom('TRUSTIFY_DA_SOURCES', null, opts);
|
|
273
|
+
if (sources) {
|
|
274
|
+
url.searchParams.append('sources', sources);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
222
277
|
/**
|
|
223
278
|
*
|
|
224
279
|
* @param url the backend url to send the request to
|
package/dist/src/cli.js
CHANGED
|
@@ -1,9 +1,47 @@
|
|
|
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';
|
|
6
|
+
import { resolveConfig } from './config.js';
|
|
5
7
|
import { getProjectLicense, getLicenseDetails } from './license/index.js';
|
|
6
|
-
import
|
|
8
|
+
import { runRemediation } from './remediate.js';
|
|
9
|
+
import { generateReport } from './remediation_report.js';
|
|
10
|
+
import client, { selectTrustifyDABackend, generateSbom } from './index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Builds a yargs middleware that loads `.trustify-da.yml` (discovered by walking
|
|
13
|
+
* up from the command's target path, or cwd when no path is available) and fills `--providers`, `--sources`, and
|
|
14
|
+
* `--group-by` with config file values when they were not supplied on the CLI or
|
|
15
|
+
* via environment variables. Precedence: CLI flag > env var > config file > default.
|
|
16
|
+
* @param {string} [pathKey] - positional argument holding the target path; defaults to the current working directory
|
|
17
|
+
* @param {{ groupBy?: boolean }} [options={}] - set `groupBy` when the command exposes `--group-by`
|
|
18
|
+
* @returns {(args: object) => object} yargs middleware
|
|
19
|
+
*/
|
|
20
|
+
function configMiddleware(pathKey, options = {}) {
|
|
21
|
+
return args => {
|
|
22
|
+
const startPath = pathKey === undefined ? process.cwd() : args[pathKey];
|
|
23
|
+
const envVars = options.groupBy
|
|
24
|
+
? process.env
|
|
25
|
+
: Object.fromEntries(Object.entries(process.env).filter(([key]) => key !== 'TRUSTIFY_DA_GROUP_BY'));
|
|
26
|
+
const merged = resolveConfig(startPath, { backendUrl: args.backendUrl, providers: args.providers, sources: args.sources, groupBy: args['group-by'] }, envVars);
|
|
27
|
+
if (merged.backendUrlSource === 'file' && process.env.TRUSTIFY_DA_TOKEN) {
|
|
28
|
+
throw new Error('Refusing to send TRUSTIFY_DA_TOKEN to a backend selected by project configuration. Set --backend-url or TRUSTIFY_DA_BACKEND_URL to explicitly trust it.');
|
|
29
|
+
}
|
|
30
|
+
if (args.providers !== undefined || merged.providers.length) {
|
|
31
|
+
args.providers = merged.providers.join(',');
|
|
32
|
+
}
|
|
33
|
+
if (args.sources !== undefined || merged.sources.length) {
|
|
34
|
+
args.sources = merged.sources.join(',');
|
|
35
|
+
}
|
|
36
|
+
if (merged.backendUrl != null) {
|
|
37
|
+
args.backendUrl = merged.backendUrl;
|
|
38
|
+
}
|
|
39
|
+
if (options.groupBy) {
|
|
40
|
+
args['group-by'] = merged.groupBy;
|
|
41
|
+
}
|
|
42
|
+
return args;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
7
45
|
// command for component analysis take manifest type and content
|
|
8
46
|
const component = {
|
|
9
47
|
command: 'component </path/to/manifest>',
|
|
@@ -18,11 +56,32 @@ const component = {
|
|
|
18
56
|
desc: 'Workspace root directory (for monorepos; lock file is expected here)',
|
|
19
57
|
type: 'string',
|
|
20
58
|
normalize: true,
|
|
59
|
+
},
|
|
60
|
+
backendUrl: {
|
|
61
|
+
desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
|
|
62
|
+
type: 'string',
|
|
63
|
+
},
|
|
64
|
+
providers: {
|
|
65
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
66
|
+
type: 'string',
|
|
67
|
+
},
|
|
68
|
+
sources: {
|
|
69
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
70
|
+
type: 'string',
|
|
21
71
|
}
|
|
22
|
-
}),
|
|
72
|
+
}).middleware(configMiddleware('/path/to/manifest')),
|
|
23
73
|
handler: async (args) => {
|
|
24
74
|
let manifestName = args['/path/to/manifest'];
|
|
25
75
|
const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
|
|
76
|
+
if (args.backendUrl !== undefined) {
|
|
77
|
+
opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
|
|
78
|
+
}
|
|
79
|
+
if (args.providers !== undefined) {
|
|
80
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
81
|
+
}
|
|
82
|
+
if (args.sources !== undefined) {
|
|
83
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
84
|
+
}
|
|
26
85
|
let res = await client.componentAnalysis(manifestName, opts);
|
|
27
86
|
console.log(JSON.stringify(res, null, 2));
|
|
28
87
|
}
|
|
@@ -71,8 +130,20 @@ const image = {
|
|
|
71
130
|
desc: 'For JSON report, get only the \'summary\'',
|
|
72
131
|
type: 'boolean',
|
|
73
132
|
conflicts: 'html'
|
|
133
|
+
},
|
|
134
|
+
backendUrl: {
|
|
135
|
+
desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
|
|
136
|
+
type: 'string',
|
|
137
|
+
},
|
|
138
|
+
providers: {
|
|
139
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
140
|
+
type: 'string',
|
|
141
|
+
},
|
|
142
|
+
sources: {
|
|
143
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
144
|
+
type: 'string',
|
|
74
145
|
}
|
|
75
|
-
}),
|
|
146
|
+
}).middleware(configMiddleware()),
|
|
76
147
|
handler: async (args) => {
|
|
77
148
|
let imageRefs = args['image-refs'];
|
|
78
149
|
if (!Array.isArray(imageRefs)) {
|
|
@@ -80,7 +151,17 @@ const image = {
|
|
|
80
151
|
}
|
|
81
152
|
let html = args['html'];
|
|
82
153
|
let summary = args['summary'];
|
|
83
|
-
|
|
154
|
+
const opts = {};
|
|
155
|
+
if (args.backendUrl !== undefined) {
|
|
156
|
+
opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
|
|
157
|
+
}
|
|
158
|
+
if (args.providers !== undefined) {
|
|
159
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
160
|
+
}
|
|
161
|
+
if (args.sources !== undefined) {
|
|
162
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
163
|
+
}
|
|
164
|
+
let res = await client.imageAnalysis(imageRefs, html, opts);
|
|
84
165
|
if (summary && !html) {
|
|
85
166
|
let summaries = {};
|
|
86
167
|
for (let [imageRef, report] of Object.entries(res)) {
|
|
@@ -131,13 +212,34 @@ const stack = {
|
|
|
131
212
|
desc: 'Workspace root directory (for monorepos; lock file is expected here)',
|
|
132
213
|
type: 'string',
|
|
133
214
|
normalize: true,
|
|
215
|
+
},
|
|
216
|
+
backendUrl: {
|
|
217
|
+
desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
|
|
218
|
+
type: 'string',
|
|
219
|
+
},
|
|
220
|
+
providers: {
|
|
221
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
222
|
+
type: 'string',
|
|
223
|
+
},
|
|
224
|
+
sources: {
|
|
225
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
226
|
+
type: 'string',
|
|
134
227
|
}
|
|
135
|
-
}),
|
|
228
|
+
}).middleware(configMiddleware('/path/to/manifest')),
|
|
136
229
|
handler: async (args) => {
|
|
137
230
|
let manifest = args['/path/to/manifest'];
|
|
138
231
|
let html = args['html'];
|
|
139
232
|
let summary = args['summary'];
|
|
140
233
|
const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
|
|
234
|
+
if (args.backendUrl !== undefined) {
|
|
235
|
+
opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
|
|
236
|
+
}
|
|
237
|
+
if (args.providers !== undefined) {
|
|
238
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
239
|
+
}
|
|
240
|
+
if (args.sources !== undefined) {
|
|
241
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
242
|
+
}
|
|
141
243
|
let theProvidersSummary = new Map();
|
|
142
244
|
let theProvidersObject = {};
|
|
143
245
|
let res = await client.stackAnalysis(manifest, html, opts);
|
|
@@ -200,13 +302,28 @@ const stackBatch = {
|
|
|
200
302
|
desc: 'Stop on first invalid package.json or SBOM error (env: TRUSTIFY_DA_CONTINUE_ON_ERROR=false)',
|
|
201
303
|
type: 'boolean',
|
|
202
304
|
default: false,
|
|
305
|
+
},
|
|
306
|
+
backendUrl: {
|
|
307
|
+
desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
|
|
308
|
+
type: 'string',
|
|
309
|
+
},
|
|
310
|
+
providers: {
|
|
311
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
312
|
+
type: 'string',
|
|
313
|
+
},
|
|
314
|
+
sources: {
|
|
315
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
316
|
+
type: 'string',
|
|
203
317
|
}
|
|
204
|
-
}),
|
|
318
|
+
}).middleware(configMiddleware('/path/to/workspace-root')),
|
|
205
319
|
handler: async (args) => {
|
|
206
320
|
const workspaceRoot = args['/path/to/workspace-root'];
|
|
207
321
|
const html = args['html'];
|
|
208
322
|
const summary = args['summary'];
|
|
209
323
|
const opts = {};
|
|
324
|
+
if (args.backendUrl !== undefined) {
|
|
325
|
+
opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
|
|
326
|
+
}
|
|
210
327
|
if (args.concurrency != null) {
|
|
211
328
|
opts.batchConcurrency = args.concurrency;
|
|
212
329
|
}
|
|
@@ -220,6 +337,12 @@ const stackBatch = {
|
|
|
220
337
|
if (args.failFast) {
|
|
221
338
|
opts.continueOnError = false;
|
|
222
339
|
}
|
|
340
|
+
if (args.providers !== undefined) {
|
|
341
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
342
|
+
}
|
|
343
|
+
if (args.sources !== undefined) {
|
|
344
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
345
|
+
}
|
|
223
346
|
let res = await client.stackAnalysisBatch(workspaceRoot, html, opts);
|
|
224
347
|
const batchAnalysis = res && typeof res === 'object' && res != null && 'analysis' in res ? res.analysis : res;
|
|
225
348
|
if (summary && !html && typeof batchAnalysis === 'object') {
|
|
@@ -325,15 +448,123 @@ const license = {
|
|
|
325
448
|
console.log(JSON.stringify(output, null, 2));
|
|
326
449
|
}
|
|
327
450
|
};
|
|
451
|
+
const sbom = {
|
|
452
|
+
command: 'sbom </path/to/manifest> [--output]',
|
|
453
|
+
desc: 'generate a CycloneDX SBOM from a manifest file',
|
|
454
|
+
builder: yargs => yargs.positional('/path/to/manifest', {
|
|
455
|
+
desc: 'manifest path for SBOM generation',
|
|
456
|
+
type: 'string',
|
|
457
|
+
normalize: true,
|
|
458
|
+
}).options({
|
|
459
|
+
output: {
|
|
460
|
+
alias: 'o',
|
|
461
|
+
desc: 'Write SBOM JSON to a file instead of stdout',
|
|
462
|
+
type: 'string',
|
|
463
|
+
normalize: true,
|
|
464
|
+
},
|
|
465
|
+
workspaceDir: {
|
|
466
|
+
alias: 'w',
|
|
467
|
+
desc: 'Workspace root directory (for monorepos; lock file is expected here)',
|
|
468
|
+
type: 'string',
|
|
469
|
+
normalize: true,
|
|
470
|
+
}
|
|
471
|
+
}),
|
|
472
|
+
handler: async (args) => {
|
|
473
|
+
let manifest = args['/path/to/manifest'];
|
|
474
|
+
const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
|
|
475
|
+
let result;
|
|
476
|
+
try {
|
|
477
|
+
result = await generateSbom(manifest, opts);
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
console.error(JSON.stringify({ error: `Failed to generate SBOM: ${err.message}` }, null, 2));
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
const json = JSON.stringify(result, null, 2);
|
|
484
|
+
if (args.output) {
|
|
485
|
+
try {
|
|
486
|
+
fs.writeFileSync(args.output, json);
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
console.error(JSON.stringify({ error: `Failed to write output file: ${err.message}` }, null, 2));
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
console.log(json);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const remediate = {
|
|
499
|
+
command: 'remediate <path>',
|
|
500
|
+
desc: 'Scan and apply vulnerability remediations to manifest files',
|
|
501
|
+
builder: yargs => yargs.positional('path', {
|
|
502
|
+
desc: 'Path to manifest file or directory',
|
|
503
|
+
type: 'string',
|
|
504
|
+
normalize: true,
|
|
505
|
+
}).options({
|
|
506
|
+
'dry-run': {
|
|
507
|
+
alias: 'd',
|
|
508
|
+
type: 'boolean',
|
|
509
|
+
desc: 'Preview changes without modifying files',
|
|
510
|
+
},
|
|
511
|
+
providers: {
|
|
512
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
513
|
+
type: 'string',
|
|
514
|
+
},
|
|
515
|
+
sources: {
|
|
516
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
517
|
+
type: 'string',
|
|
518
|
+
},
|
|
519
|
+
'group-by': {
|
|
520
|
+
type: 'string',
|
|
521
|
+
choices: ['dependency', 'bundle'],
|
|
522
|
+
desc: 'Report grouping strategy (default: dependency)',
|
|
523
|
+
},
|
|
524
|
+
backendUrl: {
|
|
525
|
+
desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
|
|
526
|
+
type: 'string',
|
|
527
|
+
},
|
|
528
|
+
}).middleware(configMiddleware('path', { groupBy: true })),
|
|
529
|
+
handler: async (args) => {
|
|
530
|
+
try {
|
|
531
|
+
const result = await runRemediation(args.path, {
|
|
532
|
+
dryRun: args['dry-run'],
|
|
533
|
+
providers: args.providers,
|
|
534
|
+
sources: args.sources,
|
|
535
|
+
backendUrl: args.backendUrl,
|
|
536
|
+
});
|
|
537
|
+
if (result.remediations.length === 0 && result.manifests.length === 0) {
|
|
538
|
+
console.log('No supported manifest files found.');
|
|
539
|
+
process.exit(result.exitCode);
|
|
540
|
+
}
|
|
541
|
+
if (!args['dry-run'] && result.appliedFiles.length > 0) {
|
|
542
|
+
console.log(`Updated ${result.appliedFiles.length} file(s):`);
|
|
543
|
+
for (const file of result.appliedFiles) {
|
|
544
|
+
console.log(` ${file}`);
|
|
545
|
+
}
|
|
546
|
+
console.log('');
|
|
547
|
+
}
|
|
548
|
+
console.log(generateReport(result.remediations, { groupBy: args['group-by'], dryRun: args['dry-run'] }));
|
|
549
|
+
process.exit(result.exitCode);
|
|
550
|
+
}
|
|
551
|
+
catch (err) {
|
|
552
|
+
console.error(err.message);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
};
|
|
328
557
|
// parse and invoke the command
|
|
329
558
|
yargs(hideBin(process.argv))
|
|
330
|
-
.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}`)
|
|
559
|
+
.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}`)
|
|
331
560
|
.command(stack)
|
|
332
561
|
.command(stackBatch)
|
|
333
562
|
.command(component)
|
|
334
563
|
.command(image)
|
|
335
564
|
.command(validateToken)
|
|
336
565
|
.command(license)
|
|
566
|
+
.command(sbom)
|
|
567
|
+
.command(remediate)
|
|
337
568
|
.scriptName('')
|
|
338
569
|
.version(false)
|
|
339
570
|
.demandCommand(1)
|