@trustify-da/trustify-da-javascript-client 0.3.0-ea.1d590b2 → 0.3.0-ea.243eaef
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 +177 -3
- package/dist/package.json +30 -23
- package/dist/src/analysis.d.ts +14 -7
- package/dist/src/analysis.js +29 -13
- package/dist/src/cli.js +106 -2
- package/dist/src/cyclone_dx_sbom.d.ts +13 -0
- package/dist/src/cyclone_dx_sbom.js +50 -4
- package/dist/src/index.d.ts +18 -12
- package/dist/src/index.js +34 -30
- package/dist/src/license/index.d.ts +3 -3
- package/dist/src/license/index.js +4 -1
- package/dist/src/license/license_utils.js +4 -1
- package/dist/src/license/licenses_api.d.ts +2 -2
- 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/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 +6 -2
- package/dist/src/providers/base_java.d.ts +67 -3
- package/dist/src/providers/base_java.js +80 -23
- package/dist/src/providers/base_javascript.d.ts +74 -9
- package/dist/src/providers/base_javascript.js +115 -6
- package/dist/src/providers/base_pyproject.d.ts +10 -5
- package/dist/src/providers/base_pyproject.js +7 -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 +11 -1
- package/dist/src/providers/golang_gomodules.js +114 -15
- package/dist/src/providers/java_gradle.d.ts +48 -0
- package/dist/src/providers/java_gradle.js +198 -20
- 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 +29 -9
- package/dist/src/providers/java_maven.js +176 -10
- 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 -1
- package/dist/src/providers/javascript_npm.js +40 -1
- package/dist/src/providers/javascript_pnpm.d.ts +13 -0
- package/dist/src/providers/javascript_pnpm.js +47 -1
- package/dist/src/providers/javascript_yarn.d.ts +12 -0
- package/dist/src/providers/javascript_yarn.js +75 -2
- package/dist/src/providers/manifest.js +6 -3
- 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 +3 -2
- 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.js +61 -4
- package/dist/src/providers/python_pip.d.ts +1 -0
- package/dist/src/providers/python_pip.js +1 -1
- package/dist/src/providers/python_pip_pyproject.js +3 -1
- package/dist/src/providers/python_poetry.d.ts +49 -9
- package/dist/src/providers/python_poetry.js +132 -21
- package/dist/src/providers/python_uv.d.ts +18 -14
- package/dist/src/providers/python_uv.js +122 -2
- 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/remediate.d.ts +21 -0
- package/dist/src/remediate.js +149 -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 +11 -0
- package/dist/src/sbom.js +10 -0
- package/dist/src/tools.d.ts +2 -2
- package/dist/src/tools.js +24 -1
- package/dist/src/updaters/maven_updater.d.ts +26 -0
- package/dist/src/updaters/maven_updater.js +322 -0
- package/dist/src/updaters/toml_updater.d.ts +33 -0
- package/dist/src/updaters/toml_updater.js +244 -0
- package/dist/src/workspace.d.ts +9 -0
- package/dist/src/workspace.js +1 -1
- package/package.json +31 -24
package/dist/src/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from "path";
|
|
|
4
4
|
import yargs from 'yargs';
|
|
5
5
|
import { hideBin } from 'yargs/helpers';
|
|
6
6
|
import { getProjectLicense, getLicenseDetails } from './license/index.js';
|
|
7
|
+
import { runRemediation } from './remediate.js';
|
|
7
8
|
import client, { selectTrustifyDABackend, generateSbom } from './index.js';
|
|
8
9
|
// command for component analysis take manifest type and content
|
|
9
10
|
const component = {
|
|
@@ -19,11 +20,25 @@ const component = {
|
|
|
19
20
|
desc: 'Workspace root directory (for monorepos; lock file is expected here)',
|
|
20
21
|
type: 'string',
|
|
21
22
|
normalize: true,
|
|
23
|
+
},
|
|
24
|
+
providers: {
|
|
25
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
26
|
+
type: 'string',
|
|
27
|
+
},
|
|
28
|
+
sources: {
|
|
29
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
30
|
+
type: 'string',
|
|
22
31
|
}
|
|
23
32
|
}),
|
|
24
33
|
handler: async (args) => {
|
|
25
34
|
let manifestName = args['/path/to/manifest'];
|
|
26
35
|
const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
|
|
36
|
+
if (args.providers) {
|
|
37
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
38
|
+
}
|
|
39
|
+
if (args.sources) {
|
|
40
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
41
|
+
}
|
|
27
42
|
let res = await client.componentAnalysis(manifestName, opts);
|
|
28
43
|
console.log(JSON.stringify(res, null, 2));
|
|
29
44
|
}
|
|
@@ -72,6 +87,14 @@ const image = {
|
|
|
72
87
|
desc: 'For JSON report, get only the \'summary\'',
|
|
73
88
|
type: 'boolean',
|
|
74
89
|
conflicts: 'html'
|
|
90
|
+
},
|
|
91
|
+
providers: {
|
|
92
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
93
|
+
type: 'string',
|
|
94
|
+
},
|
|
95
|
+
sources: {
|
|
96
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
97
|
+
type: 'string',
|
|
75
98
|
}
|
|
76
99
|
}),
|
|
77
100
|
handler: async (args) => {
|
|
@@ -81,7 +104,14 @@ const image = {
|
|
|
81
104
|
}
|
|
82
105
|
let html = args['html'];
|
|
83
106
|
let summary = args['summary'];
|
|
84
|
-
|
|
107
|
+
const opts = {};
|
|
108
|
+
if (args.providers) {
|
|
109
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
110
|
+
}
|
|
111
|
+
if (args.sources) {
|
|
112
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
113
|
+
}
|
|
114
|
+
let res = await client.imageAnalysis(imageRefs, html, opts);
|
|
85
115
|
if (summary && !html) {
|
|
86
116
|
let summaries = {};
|
|
87
117
|
for (let [imageRef, report] of Object.entries(res)) {
|
|
@@ -132,6 +162,14 @@ const stack = {
|
|
|
132
162
|
desc: 'Workspace root directory (for monorepos; lock file is expected here)',
|
|
133
163
|
type: 'string',
|
|
134
164
|
normalize: true,
|
|
165
|
+
},
|
|
166
|
+
providers: {
|
|
167
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
168
|
+
type: 'string',
|
|
169
|
+
},
|
|
170
|
+
sources: {
|
|
171
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
172
|
+
type: 'string',
|
|
135
173
|
}
|
|
136
174
|
}),
|
|
137
175
|
handler: async (args) => {
|
|
@@ -139,6 +177,12 @@ const stack = {
|
|
|
139
177
|
let html = args['html'];
|
|
140
178
|
let summary = args['summary'];
|
|
141
179
|
const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
|
|
180
|
+
if (args.providers) {
|
|
181
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
182
|
+
}
|
|
183
|
+
if (args.sources) {
|
|
184
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
185
|
+
}
|
|
142
186
|
let theProvidersSummary = new Map();
|
|
143
187
|
let theProvidersObject = {};
|
|
144
188
|
let res = await client.stackAnalysis(manifest, html, opts);
|
|
@@ -201,6 +245,14 @@ const stackBatch = {
|
|
|
201
245
|
desc: 'Stop on first invalid package.json or SBOM error (env: TRUSTIFY_DA_CONTINUE_ON_ERROR=false)',
|
|
202
246
|
type: 'boolean',
|
|
203
247
|
default: false,
|
|
248
|
+
},
|
|
249
|
+
providers: {
|
|
250
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
251
|
+
type: 'string',
|
|
252
|
+
},
|
|
253
|
+
sources: {
|
|
254
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
255
|
+
type: 'string',
|
|
204
256
|
}
|
|
205
257
|
}),
|
|
206
258
|
handler: async (args) => {
|
|
@@ -221,6 +273,12 @@ const stackBatch = {
|
|
|
221
273
|
if (args.failFast) {
|
|
222
274
|
opts.continueOnError = false;
|
|
223
275
|
}
|
|
276
|
+
if (args.providers) {
|
|
277
|
+
opts.TRUSTIFY_DA_PROVIDERS = args.providers;
|
|
278
|
+
}
|
|
279
|
+
if (args.sources) {
|
|
280
|
+
opts.TRUSTIFY_DA_SOURCES = args.sources;
|
|
281
|
+
}
|
|
224
282
|
let res = await client.stackAnalysisBatch(workspaceRoot, html, opts);
|
|
225
283
|
const batchAnalysis = res && typeof res === 'object' && res != null && 'analysis' in res ? res.analysis : res;
|
|
226
284
|
if (summary && !html && typeof batchAnalysis === 'object') {
|
|
@@ -373,9 +431,54 @@ const sbom = {
|
|
|
373
431
|
}
|
|
374
432
|
}
|
|
375
433
|
};
|
|
434
|
+
const remediate = {
|
|
435
|
+
command: 'remediate <path>',
|
|
436
|
+
desc: 'Scan and apply vulnerability remediations to manifest files',
|
|
437
|
+
builder: yargs => yargs.positional('path', {
|
|
438
|
+
desc: 'Path to manifest file or directory',
|
|
439
|
+
type: 'string',
|
|
440
|
+
normalize: true,
|
|
441
|
+
}).options({
|
|
442
|
+
'dry-run': {
|
|
443
|
+
alias: 'd',
|
|
444
|
+
type: 'boolean',
|
|
445
|
+
desc: 'Preview changes without modifying files',
|
|
446
|
+
},
|
|
447
|
+
providers: {
|
|
448
|
+
desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
|
|
449
|
+
type: 'string',
|
|
450
|
+
},
|
|
451
|
+
sources: {
|
|
452
|
+
desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
|
|
453
|
+
type: 'string',
|
|
454
|
+
},
|
|
455
|
+
'group-by': {
|
|
456
|
+
type: 'string',
|
|
457
|
+
choices: ['dependency', 'bundle'],
|
|
458
|
+
default: 'dependency',
|
|
459
|
+
desc: 'Report grouping strategy',
|
|
460
|
+
},
|
|
461
|
+
}),
|
|
462
|
+
handler: async (args) => {
|
|
463
|
+
try {
|
|
464
|
+
const result = await runRemediation(args.path, {
|
|
465
|
+
dryRun: args['dry-run'],
|
|
466
|
+
providers: args.providers,
|
|
467
|
+
sources: args.sources,
|
|
468
|
+
groupBy: args['group-by'],
|
|
469
|
+
});
|
|
470
|
+
console.log(result.output);
|
|
471
|
+
process.exit(result.exitCode);
|
|
472
|
+
}
|
|
473
|
+
catch (err) {
|
|
474
|
+
console.error(err.message);
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
};
|
|
376
479
|
// parse and invoke the command
|
|
377
480
|
yargs(hideBin(process.argv))
|
|
378
|
-
.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}`)
|
|
481
|
+
.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}`)
|
|
379
482
|
.command(stack)
|
|
380
483
|
.command(stackBatch)
|
|
381
484
|
.command(component)
|
|
@@ -383,6 +486,7 @@ yargs(hideBin(process.argv))
|
|
|
383
486
|
.command(validateToken)
|
|
384
487
|
.command(license)
|
|
385
488
|
.command(sbom)
|
|
489
|
+
.command(remediate)
|
|
386
490
|
.scriptName('')
|
|
387
491
|
.version(false)
|
|
388
492
|
.demandCommand(1)
|
|
@@ -26,6 +26,19 @@ export default class CycloneDxSbom {
|
|
|
26
26
|
alg: string;
|
|
27
27
|
content: string;
|
|
28
28
|
}>): CycloneDxSbom;
|
|
29
|
+
/**
|
|
30
|
+
* Attach hashes to already-added components by matching their PURL. This is a
|
|
31
|
+
* post-processing step so ecosystem-specific hash sources (e.g. Maven reading
|
|
32
|
+
* the local .m2 cache) can enrich the SBOM without threading their concern
|
|
33
|
+
* through the shared dependency-tree parser. Components without a matching
|
|
34
|
+
* entry, or whose hashes are already set, are left untouched.
|
|
35
|
+
* @param {Map<string, Array<{alg: string, content: string}>>} hashMap - PURL→hashes map
|
|
36
|
+
* @return {CycloneDxSbom} the updated SBOM
|
|
37
|
+
*/
|
|
38
|
+
attachHashes(hashMap: Map<string, Array<{
|
|
39
|
+
alg: string;
|
|
40
|
+
content: string;
|
|
41
|
+
}>>): CycloneDxSbom;
|
|
29
42
|
/** @param {{}} opts - various options, settings and configuration of application.
|
|
30
43
|
* @return String CycloneDx Sbom json object in a string format
|
|
31
44
|
*/
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { EOL } from "os";
|
|
2
2
|
import { PackageURL } from "packageurl-js";
|
|
3
|
+
import { getPackageVersion } from "./package_version.js";
|
|
4
|
+
/**
|
|
5
|
+
* This client's version, read once at module load, used to populate
|
|
6
|
+
* metadata.tools.components[].version in the generated SBOM.
|
|
7
|
+
* @type {string|undefined}
|
|
8
|
+
*/
|
|
9
|
+
const PACKAGE_VERSION = getPackageVersion();
|
|
3
10
|
/**
|
|
4
11
|
*
|
|
5
12
|
* @param component {PackageURL}
|
|
@@ -41,12 +48,20 @@ function getComponent(component, type, scope, licenses, hashes) {
|
|
|
41
48
|
// Add licenses if provided (CycloneDX format). Callers must provide valid SPDX identifiers.
|
|
42
49
|
if (licenses) {
|
|
43
50
|
const licenseArray = Array.isArray(licenses) ? licenses : [licenses];
|
|
44
|
-
|
|
51
|
+
const validLicenses = licenseArray
|
|
52
|
+
.map(lic => {
|
|
45
53
|
if (typeof lic === 'string') {
|
|
46
54
|
return { license: { id: lic } };
|
|
47
55
|
}
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
if (typeof lic === 'object' && lic !== null && ('license' in lic || 'expression' in lic)) {
|
|
57
|
+
return lic;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
})
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
if (validLicenses.length > 0) {
|
|
63
|
+
componentObject.licenses = validLicenses;
|
|
64
|
+
}
|
|
50
65
|
}
|
|
51
66
|
// Add hashes if provided (CycloneDX 1.4 format).
|
|
52
67
|
if (hashes && hashes.length > 0) {
|
|
@@ -127,6 +142,30 @@ export default class CycloneDxSbom {
|
|
|
127
142
|
}
|
|
128
143
|
return this;
|
|
129
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Attach hashes to already-added components by matching their PURL. This is a
|
|
147
|
+
* post-processing step so ecosystem-specific hash sources (e.g. Maven reading
|
|
148
|
+
* the local .m2 cache) can enrich the SBOM without threading their concern
|
|
149
|
+
* through the shared dependency-tree parser. Components without a matching
|
|
150
|
+
* entry, or whose hashes are already set, are left untouched.
|
|
151
|
+
* @param {Map<string, Array<{alg: string, content: string}>>} hashMap - PURL→hashes map
|
|
152
|
+
* @return {CycloneDxSbom} the updated SBOM
|
|
153
|
+
*/
|
|
154
|
+
attachHashes(hashMap) {
|
|
155
|
+
if (!hashMap || hashMap.size === 0) {
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
for (const component of this.components) {
|
|
159
|
+
if (component.hashes) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const hashes = hashMap.get(component.purl);
|
|
163
|
+
if (hashes && hashes.length > 0) {
|
|
164
|
+
component.hashes = hashes;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
130
169
|
/** @param {{}} opts - various options, settings and configuration of application.
|
|
131
170
|
* @return String CycloneDx Sbom json object in a string format
|
|
132
171
|
*/
|
|
@@ -136,11 +175,18 @@ export default class CycloneDxSbom {
|
|
|
136
175
|
const rootPurl = this.rootComponent?.purl;
|
|
137
176
|
this.sbomObject = {
|
|
138
177
|
"bomFormat": "CycloneDX",
|
|
139
|
-
"specVersion": "1.
|
|
178
|
+
"specVersion": "1.6",
|
|
140
179
|
"version": 1,
|
|
141
180
|
"metadata": {
|
|
142
181
|
"timestamp": new Date(),
|
|
143
182
|
"component": this.rootComponent,
|
|
183
|
+
"tools": {
|
|
184
|
+
"components": [{
|
|
185
|
+
"type": "application",
|
|
186
|
+
"name": "trustify-da-javascript-client",
|
|
187
|
+
"version": PACKAGE_VERSION
|
|
188
|
+
}]
|
|
189
|
+
},
|
|
144
190
|
"properties": new Array()
|
|
145
191
|
},
|
|
146
192
|
"components": this.components.filter(c => c.purl !== rootPurl),
|
package/dist/src/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export function selectTrustifyDABackend(opts?: {
|
|
|
24
24
|
export function generateSbom(manifestPath: string, opts?: Options): Promise<object>;
|
|
25
25
|
export { parseImageRef } from "./oci_image/utils.js";
|
|
26
26
|
export { ImageRef } from "./oci_image/images.js";
|
|
27
|
+
export { extractRemediations } from "./remediation.js";
|
|
27
28
|
declare namespace _default {
|
|
28
29
|
export { componentAnalysis };
|
|
29
30
|
export { stackAnalysis };
|
|
@@ -53,7 +54,9 @@ export type Options = {
|
|
|
53
54
|
TRUSTIFY_DA_PYTHON_PATH?: string | undefined;
|
|
54
55
|
TRUSTIFY_DA_PYTHON_VIRTUAL_ENV?: string | undefined;
|
|
55
56
|
TRUSTIFY_DA_PYTHON3_PATH?: string | undefined;
|
|
56
|
-
|
|
57
|
+
TRUSTIFY_DA_PROVIDERS?: string | undefined;
|
|
58
|
+
TRUSTIFY_DA_RECOMMEND?: string | undefined;
|
|
59
|
+
TRUSTIFY_DA_SOURCES?: string | undefined;
|
|
57
60
|
TRUSTIFY_DA_SKOPEO_CONFIG_PATH?: string | undefined;
|
|
58
61
|
TRUSTIFY_DA_SKOPEO_PATH?: string | undefined;
|
|
59
62
|
TRUSTIFY_DA_SYFT_CONFIG_PATH?: string | undefined;
|
|
@@ -80,7 +83,7 @@ export type Options = {
|
|
|
80
83
|
};
|
|
81
84
|
export type BatchAnalysisMetadata = {
|
|
82
85
|
workspaceRoot: string;
|
|
83
|
-
ecosystem: "javascript" | "cargo" | "unknown";
|
|
86
|
+
ecosystem: "javascript" | "cargo" | "pyproject" | "unknown";
|
|
84
87
|
total: number;
|
|
85
88
|
successful: number;
|
|
86
89
|
failed: number;
|
|
@@ -106,7 +109,7 @@ export type SbomResult = {
|
|
|
106
109
|
* @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
|
|
107
110
|
* @throws {Error} if no matching provider, failed to get create content, or backend request failed
|
|
108
111
|
*/
|
|
109
|
-
declare function componentAnalysis(manifest: string, opts?: Options): Promise<
|
|
112
|
+
declare function componentAnalysis(manifest: string, opts?: Options): Promise<any>;
|
|
110
113
|
/**
|
|
111
114
|
* @overload
|
|
112
115
|
* @param {string} manifest
|
|
@@ -124,7 +127,7 @@ declare function stackAnalysis(manifest: string, html: true, opts?: Options | un
|
|
|
124
127
|
* @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
|
|
125
128
|
* @throws {Error}
|
|
126
129
|
*/
|
|
127
|
-
declare function stackAnalysis(manifest: string, html: false, opts?: Options | undefined): Promise<
|
|
130
|
+
declare function stackAnalysis(manifest: string, html: false, opts?: Options | undefined): Promise<any>;
|
|
128
131
|
/**
|
|
129
132
|
* Get stack analysis report for a manifest file.
|
|
130
133
|
* @overload
|
|
@@ -135,7 +138,7 @@ declare function stackAnalysis(manifest: string, html: false, opts?: Options | u
|
|
|
135
138
|
* @throws {Error} if manifest inaccessible, no matching provider, failed to get create content,
|
|
136
139
|
* or backend request failed
|
|
137
140
|
*/
|
|
138
|
-
declare function stackAnalysis(manifest: string, html?: boolean | undefined, opts?: Options | undefined): Promise<string |
|
|
141
|
+
declare function stackAnalysis(manifest: string, html?: boolean | undefined, opts?: Options | undefined): Promise<string | any>;
|
|
139
142
|
/**
|
|
140
143
|
* @overload
|
|
141
144
|
* @param {string} workspaceRoot
|
|
@@ -173,7 +176,7 @@ declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts: Op
|
|
|
173
176
|
batchMetadata: true;
|
|
174
177
|
}): Promise<{
|
|
175
178
|
analysis: {
|
|
176
|
-
[x: string]:
|
|
179
|
+
[x: string]: any;
|
|
177
180
|
};
|
|
178
181
|
metadata: BatchAnalysisMetadata;
|
|
179
182
|
}>;
|
|
@@ -188,7 +191,7 @@ declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts: Op
|
|
|
188
191
|
declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts?: (Options & {
|
|
189
192
|
batchMetadata?: false;
|
|
190
193
|
}) | undefined): Promise<{
|
|
191
|
-
[x: string]:
|
|
194
|
+
[x: string]: any;
|
|
192
195
|
}>;
|
|
193
196
|
/**
|
|
194
197
|
* Get stack analysis for all workspace packages/crates (batch).
|
|
@@ -204,10 +207,10 @@ declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts?: (
|
|
|
204
207
|
* @throws {Error} if workspace root invalid, no manifests found, no packages pass validation, no SBOMs produced, or backend request failed. When `opts.batchMetadata` is set, `error.batchMetadata` may be set on thrown errors.
|
|
205
208
|
*/
|
|
206
209
|
declare function stackAnalysisBatch(workspaceRoot: string, html?: boolean | undefined, opts?: Options | undefined): Promise<string | {
|
|
207
|
-
[x: string]:
|
|
210
|
+
[x: string]: any;
|
|
208
211
|
} | {
|
|
209
212
|
analysis: string | {
|
|
210
|
-
[x: string]:
|
|
213
|
+
[x: string]: any;
|
|
211
214
|
};
|
|
212
215
|
metadata: BatchAnalysisMetadata;
|
|
213
216
|
}>;
|
|
@@ -229,7 +232,7 @@ declare function imageAnalysis(imageRefs: Array<string>, html: true, opts?: Opti
|
|
|
229
232
|
* @throws {Error}
|
|
230
233
|
*/
|
|
231
234
|
declare function imageAnalysis(imageRefs: Array<string>, html: false, opts?: Options | undefined): Promise<{
|
|
232
|
-
[x: string]:
|
|
235
|
+
[x: string]: any;
|
|
233
236
|
}>;
|
|
234
237
|
/**
|
|
235
238
|
* Get image analysis report for a set of OCI image references.
|
|
@@ -242,7 +245,7 @@ declare function imageAnalysis(imageRefs: Array<string>, html: false, opts?: Opt
|
|
|
242
245
|
* or backend request failed
|
|
243
246
|
*/
|
|
244
247
|
declare function imageAnalysis(imageRefs: Array<string>, html?: boolean | undefined, opts?: Options | undefined): Promise<string | {
|
|
245
|
-
[x: string]:
|
|
248
|
+
[x: string]: any;
|
|
246
249
|
}>;
|
|
247
250
|
/**
|
|
248
251
|
* Validates the Exhort token.
|
|
@@ -253,6 +256,8 @@ declare function imageAnalysis(imageRefs: Array<string>, html?: boolean | undefi
|
|
|
253
256
|
declare function validateToken(opts?: Options): Promise<object>;
|
|
254
257
|
import { discoverMavenModules } from './providers/java_maven.js';
|
|
255
258
|
import { discoverGradleSubprojects } from './providers/java_gradle.js';
|
|
259
|
+
import { discoverGoWorkspaceModules } from './providers/golang_gomodules.js';
|
|
260
|
+
import { discoverUvWorkspaceMembers } from './providers/python_uv.js';
|
|
256
261
|
import { discoverWorkspacePackages } from './workspace.js';
|
|
257
262
|
import { discoverWorkspaceCrates } from './workspace.js';
|
|
258
263
|
import { validatePackageJson } from './workspace.js';
|
|
@@ -260,5 +265,6 @@ import { resolveWorkspaceDiscoveryIgnore } from './workspace.js';
|
|
|
260
265
|
import { filterManifestPathsByDiscoveryIgnore } from './workspace.js';
|
|
261
266
|
import { resolveContinueOnError } from './batch_opts.js';
|
|
262
267
|
import { resolveBatchMetadata } from './batch_opts.js';
|
|
263
|
-
export { discoverMavenModules, discoverGradleSubprojects, discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata };
|
|
268
|
+
export { discoverMavenModules, discoverGradleSubprojects, discoverGoWorkspaceModules, discoverUvWorkspaceMembers, discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata };
|
|
264
269
|
export { getProjectLicense, findLicenseFilePath, identifyLicense, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
|
|
270
|
+
export { generateReport, generateDeduplicationKey } from "./remediation_report.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { EOL } from "os";
|
|
3
4
|
import pLimit from 'p-limit';
|
|
4
|
-
import { availableProviders, match } from './provider.js';
|
|
5
5
|
import analysis from './analysis.js';
|
|
6
|
-
import fs from 'node:fs';
|
|
7
|
-
import { getCustom } from "./tools.js";
|
|
8
6
|
import { resolveBatchMetadata, resolveContinueOnError } from './batch_opts.js';
|
|
9
|
-
import {
|
|
7
|
+
import { getPackageVersion } from './package_version.js';
|
|
8
|
+
import { availableProviders, match } from './provider.js';
|
|
9
|
+
import { discoverGoWorkspaceModules } from './providers/golang_gomodules.js';
|
|
10
10
|
import { discoverGradleSubprojects } from './providers/java_gradle.js';
|
|
11
|
+
import { discoverMavenModules } from './providers/java_maven.js';
|
|
12
|
+
import { discoverUvWorkspaceMembers } from './providers/python_uv.js';
|
|
13
|
+
import { getCustom } from "./tools.js";
|
|
11
14
|
import { discoverWorkspaceCrates, discoverWorkspacePackages, filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore, validatePackageJson, } from './workspace.js';
|
|
12
|
-
import.meta.dirname;
|
|
13
|
-
import * as url from 'url';
|
|
14
15
|
export { parseImageRef } from "./oci_image/utils.js";
|
|
15
16
|
export { ImageRef } from "./oci_image/images.js";
|
|
16
17
|
export { getProjectLicense, findLicenseFilePath, identifyLicense, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
|
|
18
|
+
export { extractRemediations } from "./remediation.js";
|
|
19
|
+
export { generateReport, generateDeduplicationKey } from './remediation_report.js';
|
|
17
20
|
export default { componentAnalysis, stackAnalysis, stackAnalysisBatch, imageAnalysis, validateToken, generateSbom };
|
|
18
|
-
export { discoverMavenModules, discoverGradleSubprojects, discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata, };
|
|
21
|
+
export { discoverMavenModules, discoverGradleSubprojects, discoverGoWorkspaceModules, discoverUvWorkspaceMembers, discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata, };
|
|
19
22
|
/**
|
|
20
23
|
* @typedef {{
|
|
21
24
|
* TRUSTIFY_DA_CARGO_PATH?: string | undefined,
|
|
@@ -37,7 +40,9 @@ export { discoverMavenModules, discoverGradleSubprojects, discoverWorkspacePacka
|
|
|
37
40
|
* TRUSTIFY_DA_PYTHON_PATH?: string | undefined,
|
|
38
41
|
* TRUSTIFY_DA_PYTHON_VIRTUAL_ENV?: string | undefined,
|
|
39
42
|
* TRUSTIFY_DA_PYTHON3_PATH?: string | undefined,
|
|
40
|
-
*
|
|
43
|
+
* TRUSTIFY_DA_PROVIDERS?: string | undefined,
|
|
44
|
+
* TRUSTIFY_DA_RECOMMEND?: string | undefined,
|
|
45
|
+
* TRUSTIFY_DA_SOURCES?: string | undefined,
|
|
41
46
|
* TRUSTIFY_DA_SKOPEO_CONFIG_PATH?: string | undefined,
|
|
42
47
|
* TRUSTIFY_DA_SKOPEO_PATH?: string | undefined,
|
|
43
48
|
* TRUSTIFY_DA_SYFT_CONFIG_PATH?: string | undefined,
|
|
@@ -66,7 +71,7 @@ export { discoverMavenModules, discoverGradleSubprojects, discoverWorkspacePacka
|
|
|
66
71
|
/**
|
|
67
72
|
* @typedef {{
|
|
68
73
|
* workspaceRoot: string,
|
|
69
|
-
* ecosystem: 'javascript' | 'cargo' | 'unknown',
|
|
74
|
+
* ecosystem: 'javascript' | 'cargo' | 'pyproject' | 'unknown',
|
|
70
75
|
* total: number,
|
|
71
76
|
* successful: number,
|
|
72
77
|
* failed: number,
|
|
@@ -89,23 +94,7 @@ function logOptionsAndEnvironmentsVariables(alongsideText, valueToBePrinted) {
|
|
|
89
94
|
* @private
|
|
90
95
|
*/
|
|
91
96
|
function readAndPrintVersionFromPackageJson() {
|
|
92
|
-
|
|
93
|
-
// new ESM way in nodeJS ( since node version 22 ) to bring module directory.
|
|
94
|
-
dirName = import.meta.dirname;
|
|
95
|
-
// old ESM way in nodeJS ( before node versions 22.00 to bring module directory)
|
|
96
|
-
if (!dirName) {
|
|
97
|
-
dirName = url.fileURLToPath(new URL('.', import.meta.url));
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
if (__dirname) {
|
|
101
|
-
dirName = __dirname;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
catch (e) {
|
|
105
|
-
console.log("__dirname is not defined, continue with fileUrlPath");
|
|
106
|
-
}
|
|
107
|
-
let packageJson = JSON.parse(fs.readFileSync(path.join(dirName, "..", "package.json")).toString());
|
|
108
|
-
logOptionsAndEnvironmentsVariables("trustify-da-javascript-client analysis started, version: ", packageJson.version);
|
|
97
|
+
logOptionsAndEnvironmentsVariables("trustify-da-javascript-client analysis started, version: ", getPackageVersion());
|
|
109
98
|
}
|
|
110
99
|
/**
|
|
111
100
|
* This function is used to determine the Trustify DA backend URL.
|
|
@@ -173,7 +162,9 @@ async function componentAnalysis(manifest, opts = {}) {
|
|
|
173
162
|
fs.accessSync(manifest, fs.constants.R_OK);
|
|
174
163
|
opts["manifest-type"] = path.basename(manifest);
|
|
175
164
|
let provider = match(manifest, availableProviders, opts); // throws error if no matching provider
|
|
176
|
-
|
|
165
|
+
const result = await analysis.requestComponent(provider, manifest, theUrl, opts); // throws error request sending failed
|
|
166
|
+
result.packageManager = provider.packageManagerName();
|
|
167
|
+
return result;
|
|
177
168
|
}
|
|
178
169
|
/**
|
|
179
170
|
* @overload
|
|
@@ -281,7 +272,7 @@ async function generateOneSbom(manifestPath, workspaceOpts) {
|
|
|
281
272
|
*
|
|
282
273
|
* @param {string} root - Resolved workspace root
|
|
283
274
|
* @param {Options} opts
|
|
284
|
-
* @returns {Promise<{ ecosystem: 'javascript' | 'cargo' | 'maven' | 'gradle' | 'unknown', manifestPaths: string[] }>}
|
|
275
|
+
* @returns {Promise<{ ecosystem: 'javascript' | 'cargo' | 'maven' | 'gradle' | 'gomodules' | 'pyproject' | 'unknown', manifestPaths: string[] }>}
|
|
285
276
|
* @private
|
|
286
277
|
*/
|
|
287
278
|
async function detectWorkspaceManifests(root, opts) {
|
|
@@ -306,7 +297,20 @@ async function detectWorkspaceManifests(root, opts) {
|
|
|
306
297
|
return { ecosystem: 'gradle', manifestPaths };
|
|
307
298
|
}
|
|
308
299
|
}
|
|
309
|
-
|
|
300
|
+
if (fs.existsSync(path.join(root, 'go.work'))) {
|
|
301
|
+
const manifestPaths = await discoverGoWorkspaceModules(root, opts);
|
|
302
|
+
if (manifestPaths.length > 0) {
|
|
303
|
+
return { ecosystem: 'gomodules', manifestPaths };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (fs.existsSync(path.join(root, 'pyproject.toml')) && fs.existsSync(path.join(root, 'uv.lock'))) {
|
|
307
|
+
const manifestPaths = await discoverUvWorkspaceMembers(root, opts);
|
|
308
|
+
if (manifestPaths.length > 0) {
|
|
309
|
+
return { ecosystem: 'pyproject', manifestPaths };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const hasJsLock = fs.existsSync(path.join(root, 'bun.lock'))
|
|
313
|
+
|| fs.existsSync(path.join(root, 'pnpm-lock.yaml'))
|
|
310
314
|
|| fs.existsSync(path.join(root, 'yarn.lock'))
|
|
311
315
|
|| fs.existsSync(path.join(root, 'package-lock.json'));
|
|
312
316
|
if (fs.existsSync(packageJson) && hasJsLock) {
|
|
@@ -484,7 +488,7 @@ async function stackAnalysisBatch(workspaceRoot, html = false, opts = {}) {
|
|
|
484
488
|
}
|
|
485
489
|
}
|
|
486
490
|
if (manifestPaths.length === 0) {
|
|
487
|
-
throw new Error(`No workspace manifests found at ${root}. Ensure Cargo.toml+Cargo.lock or package.json+lock file
|
|
491
|
+
throw new Error(`No workspace manifests found at ${root}. Ensure a supported workspace root exists (Cargo.toml+Cargo.lock, pom.xml, build.gradle, go.work, pyproject.toml+uv.lock, or package.json+lock file).`);
|
|
488
492
|
}
|
|
489
493
|
const workspaceOpts = { ...opts, TRUSTIFY_DA_WORKSPACE_DIR: root };
|
|
490
494
|
const concurrency = resolveBatchConcurrency(opts);
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* @param {import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport} [analysisResult] - analysis result that includes licenses array from backend
|
|
10
10
|
* @returns {Promise<{ projectLicense: { manifest: Object|null, file: Object|null, mismatch: boolean }, incompatibleDependencies: Array<{ purl: string, licenses: string[], category?: string, reason: string }>, error?: string }>}
|
|
11
11
|
*/
|
|
12
|
-
export function runLicenseCheck(sbomContent: string, manifestPath: string, url: string, opts?: import("../index.js").Options, analysisResult?:
|
|
12
|
+
export function runLicenseCheck(sbomContent: string, manifestPath: string, url: string, opts?: import("../index.js").Options, analysisResult?: any): Promise<{
|
|
13
13
|
projectLicense: {
|
|
14
|
-
manifest:
|
|
15
|
-
file:
|
|
14
|
+
manifest: Object | null;
|
|
15
|
+
file: Object | null;
|
|
16
16
|
mismatch: boolean;
|
|
17
17
|
};
|
|
18
18
|
incompatibleDependencies: Array<{
|
|
@@ -85,11 +85,14 @@ export async function runLicenseCheck(sbomContent, manifestPath, url, opts = {},
|
|
|
85
85
|
continue;
|
|
86
86
|
const status = getCompatibility(projectCategory, entry.category);
|
|
87
87
|
if (status === 'incompatible') {
|
|
88
|
+
const reason = entry.category?.toUpperCase() === 'UNKNOWN'
|
|
89
|
+
? 'License not recognized as a standard SPDX identifier. Manual review recommended to verify compatibility.'
|
|
90
|
+
: 'Dependency license(s) are incompatible with the project license.';
|
|
88
91
|
incompatibleDependencies.push({
|
|
89
92
|
purl,
|
|
90
93
|
licenses: entry.licenses,
|
|
91
94
|
category: entry.category,
|
|
92
|
-
reason
|
|
95
|
+
reason
|
|
93
96
|
});
|
|
94
97
|
}
|
|
95
98
|
}
|
|
@@ -114,9 +114,12 @@ export function getCompatibility(projectCategory, dependencyCategory) {
|
|
|
114
114
|
}
|
|
115
115
|
const proj = projectCategory.toUpperCase();
|
|
116
116
|
const dep = dependencyCategory.toUpperCase();
|
|
117
|
-
if (proj === 'UNKNOWN'
|
|
117
|
+
if (proj === 'UNKNOWN') {
|
|
118
118
|
return 'unknown';
|
|
119
119
|
}
|
|
120
|
+
if (dep === 'UNKNOWN') {
|
|
121
|
+
return 'incompatible';
|
|
122
|
+
}
|
|
120
123
|
const restrictiveness = {
|
|
121
124
|
'PERMISSIVE': 1,
|
|
122
125
|
'WEAK_COPYLEFT': 2,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @param {import('../index.js').Options} [opts={}] - options (proxy, token, TRUSTIFY_DA_BACKEND_URL, etc.)
|
|
7
7
|
* @returns {Promise<Object|null>} License details or null if not found
|
|
8
8
|
*/
|
|
9
|
-
export function getLicenseDetails(spdxId: string, opts?: import("../index.js").Options): Promise<
|
|
9
|
+
export function getLicenseDetails(spdxId: string, opts?: import("../index.js").Options): Promise<Object | null>;
|
|
10
10
|
/**
|
|
11
11
|
* Normalize the LicensesResponse shape (array of LicenseProviderResult) into a map of purl -> license info.
|
|
12
12
|
* Each provider result has { status, summary, packages } where packages is { [purl]: { concluded, evidence } }.
|
|
@@ -28,7 +28,7 @@ export function normalizeLicensesResponse(data: unknown, purls?: string[]): Map<
|
|
|
28
28
|
* @param {string[]} [purls] - optional list of purls to restrict to
|
|
29
29
|
* @returns {Map<string, { licenses: string[], category?: string }>}
|
|
30
30
|
*/
|
|
31
|
-
export function licensesFromReport(analysisReport:
|
|
31
|
+
export function licensesFromReport(analysisReport: any, purls?: string[]): Map<string, {
|
|
32
32
|
licenses: string[];
|
|
33
33
|
category?: string;
|
|
34
34
|
}>;
|
|
@@ -78,7 +78,7 @@ export class ImageRef {
|
|
|
78
78
|
* @param {string} [platform]
|
|
79
79
|
* @param {import("index.js").Options} [opts={}]
|
|
80
80
|
*/
|
|
81
|
-
constructor(image: string, platform?: string, opts?:
|
|
81
|
+
constructor(image: string, platform?: string, opts?: any);
|
|
82
82
|
/** @type {Image} */
|
|
83
83
|
image: Image;
|
|
84
84
|
/** @type {Platform} */
|
|
@@ -242,8 +242,22 @@ export class ImageRef {
|
|
|
242
242
|
getPackageURL() {
|
|
243
243
|
/** @type {Object.<string, string>} */
|
|
244
244
|
const qualifiers = {};
|
|
245
|
-
|
|
245
|
+
let repositoryUrl = this.image.getNameWithoutTag();
|
|
246
246
|
const simpleName = this.image.getSimpleName();
|
|
247
|
+
// Normalize Docker Hub image references so all forms produce the same PURL:
|
|
248
|
+
// node → docker.io/node
|
|
249
|
+
// docker.io/library/node → docker.io/node
|
|
250
|
+
if (repositoryUrl != null) {
|
|
251
|
+
const lower = repositoryUrl.toLowerCase();
|
|
252
|
+
if (lower === simpleName.toLowerCase()) {
|
|
253
|
+
// Bare name (e.g. "node") — default to docker.io
|
|
254
|
+
repositoryUrl = `docker.io/${simpleName}`;
|
|
255
|
+
}
|
|
256
|
+
else if (lower.startsWith('docker.io/library/')) {
|
|
257
|
+
// Official image with library/ prefix — strip it
|
|
258
|
+
repositoryUrl = `docker.io/${lower.substring('docker.io/library/'.length)}`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
247
261
|
if (repositoryUrl != null && repositoryUrl.toLowerCase() !== simpleName.toLowerCase()) {
|
|
248
262
|
qualifiers[ImageRef.REPOSITORY_QUALIFIER] = repositoryUrl.toLowerCase();
|
|
249
263
|
}
|