@trustify-da/trustify-da-javascript-client 0.3.0-ea.00529d4 → 0.3.0-ea.01107e6
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 +176 -2
- package/dist/package.json +6 -2
- package/dist/src/cli.js +48 -1
- package/dist/src/cyclone_dx_sbom.d.ts +13 -0
- package/dist/src/cyclone_dx_sbom.js +39 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +8 -24
- package/dist/src/package_version.d.ts +8 -0
- package/dist/src/package_version.js +31 -0
- package/dist/src/providers/base_java.d.ts +53 -0
- package/dist/src/providers/base_java.js +64 -17
- package/dist/src/providers/base_javascript.d.ts +54 -0
- package/dist/src/providers/base_javascript.js +105 -3
- package/dist/src/providers/golang_gomodules.js +43 -2
- package/dist/src/providers/java_maven.d.ts +28 -3
- package/dist/src/providers/java_maven.js +96 -7
- package/dist/src/providers/javascript_bun.d.ts +12 -0
- package/dist/src/providers/javascript_bun.js +42 -1
- package/dist/src/providers/javascript_npm.d.ts +13 -0
- 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/processors/yarn_berry_processor.d.ts +5 -1
- package/dist/src/providers/processors/yarn_berry_processor.js +3 -2
- package/dist/src/providers/processors/yarn_classic_processor.d.ts +5 -1
- package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
- package/dist/src/providers/python_controller.js +4 -1
- package/dist/src/providers/rust_cargo.js +74 -15
- package/dist/src/remediate.d.ts +21 -0
- package/dist/src/remediate.js +149 -0
- package/dist/src/remediation.d.ts +27 -3
- package/dist/src/remediation.js +135 -20
- 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.js +24 -1
- package/package.json +7 -3
|
@@ -1,11 +1,51 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { parse } from 'jsonc-parser';
|
|
4
|
-
import Base_javascript from './base_javascript.js';
|
|
4
|
+
import Base_javascript, { sriToHash } from './base_javascript.js';
|
|
5
5
|
export default class Javascript_bun extends Base_javascript {
|
|
6
6
|
_lockFileName() {
|
|
7
7
|
return "bun.lock";
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Parses `bun.lock` into a hash map keyed by `name@version`. Each entry in
|
|
11
|
+
* the `packages` section is an array whose first element is the resolved id
|
|
12
|
+
* (`name@version`) and whose fourth element is the SRI integrity string.
|
|
13
|
+
* @param {string} lockDir - Directory containing bun.lock
|
|
14
|
+
* @returns {Map<string, Array<{alg: string, content: string}>>} Hash map
|
|
15
|
+
* @protected
|
|
16
|
+
*/
|
|
17
|
+
_parseLockFileHashes(lockDir) {
|
|
18
|
+
const map = new Map();
|
|
19
|
+
const lockPath = path.join(lockDir, this._lockFileName());
|
|
20
|
+
if (!fs.existsSync(lockPath)) {
|
|
21
|
+
return map;
|
|
22
|
+
}
|
|
23
|
+
let lockData;
|
|
24
|
+
try {
|
|
25
|
+
lockData = parse(fs.readFileSync(lockPath, 'utf-8'));
|
|
26
|
+
}
|
|
27
|
+
catch (_) {
|
|
28
|
+
return map;
|
|
29
|
+
}
|
|
30
|
+
const packages = lockData?.packages || {};
|
|
31
|
+
for (const entry of Object.values(packages)) {
|
|
32
|
+
if (!Array.isArray(entry) || typeof entry[0] !== 'string' || typeof entry[3] !== 'string') {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const id = entry[0];
|
|
36
|
+
const at = id.lastIndexOf('@');
|
|
37
|
+
if (at <= 0) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const name = id.slice(0, at);
|
|
41
|
+
const version = id.slice(at + 1);
|
|
42
|
+
const hash = sriToHash(entry[3]);
|
|
43
|
+
if (name && version && hash) {
|
|
44
|
+
map.set(`${name}@${version}`, [hash]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return map;
|
|
48
|
+
}
|
|
9
49
|
_cmdName() {
|
|
10
50
|
return "bun";
|
|
11
51
|
}
|
|
@@ -20,6 +60,7 @@ export default class Javascript_bun extends Base_javascript {
|
|
|
20
60
|
const manifestDir = path.dirname(this._getManifest().manifestPath);
|
|
21
61
|
const lockDir = this._findLockFileDir(manifestDir, opts) || manifestDir;
|
|
22
62
|
this._createLockFile(lockDir);
|
|
63
|
+
this._loadHashes(lockDir);
|
|
23
64
|
const lockContent = fs.readFileSync(path.join(lockDir, 'bun.lock'), 'utf-8');
|
|
24
65
|
const lockData = parse(lockContent);
|
|
25
66
|
const packages = lockData.packages || {};
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export default class Javascript_npm extends Base_javascript {
|
|
2
|
+
/**
|
|
3
|
+
* Parses `package-lock.json` (lockfileVersion 2/3) into a hash map keyed by
|
|
4
|
+
* `name@version`. Each `packages` entry is keyed by its install path
|
|
5
|
+
* (e.g. `node_modules/@hapi/joi`); the package name is the segment after the
|
|
6
|
+
* final `node_modules/`. The `integrity` field holds an SRI string.
|
|
7
|
+
* @param {string} lockDir - Directory containing package-lock.json
|
|
8
|
+
* @returns {Map<string, Array<{alg: string, content: string}>>} Hash map
|
|
9
|
+
* @protected
|
|
10
|
+
*/
|
|
11
|
+
protected _parseLockFileHashes(lockDir: string): Map<string, Array<{
|
|
12
|
+
alg: string;
|
|
13
|
+
content: string;
|
|
14
|
+
}>>;
|
|
2
15
|
_listCmdArgs(includeTransitive: any): string[];
|
|
3
16
|
_buildDependencyTree(includeTransitive: any, opts?: {}): Object | {
|
|
4
17
|
name: any;
|
|
@@ -1,8 +1,47 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import Base_javascript, { sriToHash } from './base_javascript.js';
|
|
2
4
|
export default class Javascript_npm extends Base_javascript {
|
|
3
5
|
_lockFileName() {
|
|
4
6
|
return "package-lock.json";
|
|
5
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Parses `package-lock.json` (lockfileVersion 2/3) into a hash map keyed by
|
|
10
|
+
* `name@version`. Each `packages` entry is keyed by its install path
|
|
11
|
+
* (e.g. `node_modules/@hapi/joi`); the package name is the segment after the
|
|
12
|
+
* final `node_modules/`. The `integrity` field holds an SRI string.
|
|
13
|
+
* @param {string} lockDir - Directory containing package-lock.json
|
|
14
|
+
* @returns {Map<string, Array<{alg: string, content: string}>>} Hash map
|
|
15
|
+
* @protected
|
|
16
|
+
*/
|
|
17
|
+
_parseLockFileHashes(lockDir) {
|
|
18
|
+
const map = new Map();
|
|
19
|
+
const lockPath = path.join(lockDir, this._lockFileName());
|
|
20
|
+
if (!fs.existsSync(lockPath)) {
|
|
21
|
+
return map;
|
|
22
|
+
}
|
|
23
|
+
let lock;
|
|
24
|
+
try {
|
|
25
|
+
lock = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
|
|
26
|
+
}
|
|
27
|
+
catch (_) {
|
|
28
|
+
return map;
|
|
29
|
+
}
|
|
30
|
+
const packages = lock.packages || {};
|
|
31
|
+
for (const [pkgPath, entry] of Object.entries(packages)) {
|
|
32
|
+
if (!pkgPath || !entry || !entry.integrity || !entry.version) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const marker = 'node_modules/';
|
|
36
|
+
const idx = pkgPath.lastIndexOf(marker);
|
|
37
|
+
const name = idx < 0 ? pkgPath : pkgPath.slice(idx + marker.length);
|
|
38
|
+
const hash = sriToHash(entry.integrity);
|
|
39
|
+
if (name && hash) {
|
|
40
|
+
map.set(`${name}@${entry.version}`, [hash]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return map;
|
|
44
|
+
}
|
|
6
45
|
_cmdName() {
|
|
7
46
|
return "npm";
|
|
8
47
|
}
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export default class Javascript_pnpm extends Base_javascript {
|
|
2
|
+
/**
|
|
3
|
+
* Parses `pnpm-lock.yaml` (lockfileVersion 9) into a hash map keyed by
|
|
4
|
+
* `name@version`. Entries in the `packages` section are keyed by
|
|
5
|
+
* `<name>@<version>` (optionally followed by a `(peer@x)` suffix); the
|
|
6
|
+
* `resolution.integrity` field holds an SRI string.
|
|
7
|
+
* @param {string} lockDir - Directory containing pnpm-lock.yaml
|
|
8
|
+
* @returns {Map<string, Array<{alg: string, content: string}>>} Hash map
|
|
9
|
+
* @protected
|
|
10
|
+
*/
|
|
11
|
+
protected _parseLockFileHashes(lockDir: string): Map<string, Array<{
|
|
12
|
+
alg: string;
|
|
13
|
+
content: string;
|
|
14
|
+
}>>;
|
|
2
15
|
_listCmdArgs(includeTransitive: any): string[];
|
|
3
16
|
_buildDependencyTree(includeTransitive: any, opts?: {}): any;
|
|
4
17
|
}
|
|
@@ -1,8 +1,54 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { load as yamlLoad } from 'js-yaml';
|
|
4
|
+
import Base_javascript, { sriToHash } from './base_javascript.js';
|
|
2
5
|
export default class Javascript_pnpm extends Base_javascript {
|
|
3
6
|
_lockFileName() {
|
|
4
7
|
return "pnpm-lock.yaml";
|
|
5
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Parses `pnpm-lock.yaml` (lockfileVersion 9) into a hash map keyed by
|
|
11
|
+
* `name@version`. Entries in the `packages` section are keyed by
|
|
12
|
+
* `<name>@<version>` (optionally followed by a `(peer@x)` suffix); the
|
|
13
|
+
* `resolution.integrity` field holds an SRI string.
|
|
14
|
+
* @param {string} lockDir - Directory containing pnpm-lock.yaml
|
|
15
|
+
* @returns {Map<string, Array<{alg: string, content: string}>>} Hash map
|
|
16
|
+
* @protected
|
|
17
|
+
*/
|
|
18
|
+
_parseLockFileHashes(lockDir) {
|
|
19
|
+
const map = new Map();
|
|
20
|
+
const lockPath = path.join(lockDir, this._lockFileName());
|
|
21
|
+
if (!fs.existsSync(lockPath)) {
|
|
22
|
+
return map;
|
|
23
|
+
}
|
|
24
|
+
let lock;
|
|
25
|
+
try {
|
|
26
|
+
lock = yamlLoad(fs.readFileSync(lockPath, 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
catch (_) {
|
|
29
|
+
return map;
|
|
30
|
+
}
|
|
31
|
+
const packages = lock?.packages || {};
|
|
32
|
+
for (const [key, entry] of Object.entries(packages)) {
|
|
33
|
+
const integrity = entry?.resolution?.integrity;
|
|
34
|
+
if (!key || !integrity) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// Strip any peer-dependency suffix, then split name from version
|
|
38
|
+
const base = key.split('(')[0];
|
|
39
|
+
const at = base.lastIndexOf('@');
|
|
40
|
+
if (at <= 0) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const name = base.slice(0, at);
|
|
44
|
+
const version = base.slice(at + 1);
|
|
45
|
+
const hash = sriToHash(integrity);
|
|
46
|
+
if (name && version && hash) {
|
|
47
|
+
map.set(`${name}@${version}`, [hash]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return map;
|
|
51
|
+
}
|
|
6
52
|
_cmdName() {
|
|
7
53
|
return "pnpm";
|
|
8
54
|
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
export default class Javascript_yarn extends Base_javascript {
|
|
2
2
|
static VERSION_PATTERN: RegExp;
|
|
3
|
+
/**
|
|
4
|
+
* Parses `yarn.lock` into a hash map keyed by `name@version`. Supports both
|
|
5
|
+
* Yarn Classic (v1) entries with an `integrity` SRI field and Yarn Berry
|
|
6
|
+
* (v2+) entries with a `checksum` field.
|
|
7
|
+
* @param {string} lockDir - Directory containing yarn.lock
|
|
8
|
+
* @returns {Map<string, Array<{alg: string, content: string}>>} Hash map
|
|
9
|
+
* @protected
|
|
10
|
+
*/
|
|
11
|
+
protected _parseLockFileHashes(lockDir: string): Map<string, Array<{
|
|
12
|
+
alg: string;
|
|
13
|
+
content: string;
|
|
14
|
+
}>>;
|
|
3
15
|
_listCmdArgs(includeTransitive: any, manifestDir: any): any;
|
|
4
16
|
_updateLockFileCmdArgs(manifestDir: any): any;
|
|
5
17
|
_setUp(manifestPath: any, opts: any): void;
|
|
@@ -1,12 +1,85 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseSyml } from '@yarnpkg/parsers';
|
|
4
|
+
import Base_javascript, { sriToHash } from './base_javascript.js';
|
|
2
5
|
import Yarn_berry_processor from './processors/yarn_berry_processor.js';
|
|
3
6
|
import Yarn_classic_processor from './processors/yarn_classic_processor.js';
|
|
7
|
+
/**
|
|
8
|
+
* Converts a Yarn Berry `checksum` value to a CycloneDX hash object. Berry
|
|
9
|
+
* checksums are formatted as `<cacheKey>/<hex-sha512>`; the digest is the
|
|
10
|
+
* hex-encoded SHA-512 following the final slash.
|
|
11
|
+
* @param {string} checksum - The checksum field value
|
|
12
|
+
* @returns {{alg: string, content: string}|null} CycloneDX hash, or null
|
|
13
|
+
*/
|
|
14
|
+
function berryChecksumToHash(checksum) {
|
|
15
|
+
if (typeof checksum !== 'string') {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const slash = checksum.lastIndexOf('/');
|
|
19
|
+
const hex = (slash >= 0 ? checksum.slice(slash + 1) : checksum).trim();
|
|
20
|
+
if (!/^[0-9a-f]+$/i.test(hex)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return { alg: 'SHA-512', content: hex.toLowerCase() };
|
|
24
|
+
}
|
|
4
25
|
export default class Javascript_yarn extends Base_javascript {
|
|
5
26
|
static VERSION_PATTERN = /^([0-9]+)\./;
|
|
6
27
|
#processor;
|
|
7
28
|
_lockFileName() {
|
|
8
29
|
return "yarn.lock";
|
|
9
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Parses `yarn.lock` into a hash map keyed by `name@version`. Supports both
|
|
33
|
+
* Yarn Classic (v1) entries with an `integrity` SRI field and Yarn Berry
|
|
34
|
+
* (v2+) entries with a `checksum` field.
|
|
35
|
+
* @param {string} lockDir - Directory containing yarn.lock
|
|
36
|
+
* @returns {Map<string, Array<{alg: string, content: string}>>} Hash map
|
|
37
|
+
* @protected
|
|
38
|
+
*/
|
|
39
|
+
_parseLockFileHashes(lockDir) {
|
|
40
|
+
const map = new Map();
|
|
41
|
+
const lockPath = path.join(lockDir, this._lockFileName());
|
|
42
|
+
if (!fs.existsSync(lockPath)) {
|
|
43
|
+
return map;
|
|
44
|
+
}
|
|
45
|
+
let content;
|
|
46
|
+
try {
|
|
47
|
+
content = fs.readFileSync(lockPath, 'utf-8');
|
|
48
|
+
}
|
|
49
|
+
catch (_) {
|
|
50
|
+
return map;
|
|
51
|
+
}
|
|
52
|
+
const parsed = parseSyml(content);
|
|
53
|
+
for (const [spec, entry] of Object.entries(parsed)) {
|
|
54
|
+
if (spec === '__metadata') {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const version = entry.version;
|
|
58
|
+
if (!version) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
let hash = null;
|
|
62
|
+
if (entry.integrity) {
|
|
63
|
+
hash = sriToHash(entry.integrity);
|
|
64
|
+
}
|
|
65
|
+
else if (entry.checksum) {
|
|
66
|
+
hash = berryChecksumToHash(entry.checksum);
|
|
67
|
+
}
|
|
68
|
+
if (!hash) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Handle comma-separated specifiers (e.g., "pkg@^1.0.0, pkg@^2.0.0")
|
|
72
|
+
for (const rawSpec of spec.split(',')) {
|
|
73
|
+
const trimmed = rawSpec.trim();
|
|
74
|
+
const atIndex = trimmed.lastIndexOf('@');
|
|
75
|
+
if (atIndex > 0) {
|
|
76
|
+
const name = trimmed.slice(0, atIndex);
|
|
77
|
+
map.set(`${name}@${version}`, [hash]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return map;
|
|
82
|
+
}
|
|
10
83
|
_cmdName() {
|
|
11
84
|
return "yarn";
|
|
12
85
|
}
|
|
@@ -34,6 +107,6 @@ export default class Javascript_yarn extends Base_javascript {
|
|
|
34
107
|
return this.#processor.parseDepTreeOutput(output);
|
|
35
108
|
}
|
|
36
109
|
_addDependenciesToSbom(sbom, depTree) {
|
|
37
|
-
this.#processor.addDependenciesToSbom(sbom, depTree);
|
|
110
|
+
this.#processor.addDependenciesToSbom(sbom, depTree, purl => this._hashesForPurl(purl));
|
|
38
111
|
}
|
|
39
112
|
}
|
|
@@ -34,8 +34,12 @@ export default class Yarn_berry_processor extends Yarn_processor {
|
|
|
34
34
|
* Adds dependencies to the SBOM
|
|
35
35
|
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
36
36
|
* @param {Object} depTree - The dependency tree object
|
|
37
|
+
* @param {function(PackageURL): (Array<{alg: string, content: string}>|undefined)} [hashesForPurl] - Resolves lock-file hashes for a purl
|
|
37
38
|
*/
|
|
38
|
-
addDependenciesToSbom(sbom: Sbom, depTree: Object
|
|
39
|
+
addDependenciesToSbom(sbom: Sbom, depTree: Object, hashesForPurl?: (arg0: PackageURL) => (Array<{
|
|
40
|
+
alg: string;
|
|
41
|
+
content: string;
|
|
42
|
+
}> | undefined)): void;
|
|
39
43
|
#private;
|
|
40
44
|
}
|
|
41
45
|
import Yarn_processor from "./yarn_processor.js";
|
|
@@ -78,8 +78,9 @@ export default class Yarn_berry_processor extends Yarn_processor {
|
|
|
78
78
|
* Adds dependencies to the SBOM
|
|
79
79
|
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
80
80
|
* @param {Object} depTree - The dependency tree object
|
|
81
|
+
* @param {function(PackageURL): (Array<{alg: string, content: string}>|undefined)} [hashesForPurl] - Resolves lock-file hashes for a purl
|
|
81
82
|
*/
|
|
82
|
-
addDependenciesToSbom(sbom, depTree) {
|
|
83
|
+
addDependenciesToSbom(sbom, depTree, hashesForPurl = () => undefined) {
|
|
83
84
|
if (!depTree) {
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
@@ -137,7 +138,7 @@ export default class Yarn_berry_processor extends Yarn_processor {
|
|
|
137
138
|
}
|
|
138
139
|
const to = this.#purlFromLocator(d.locator);
|
|
139
140
|
if (to) {
|
|
140
|
-
sbom.addDependency(from, to);
|
|
141
|
+
sbom.addDependency(from, to, undefined, hashesForPurl(to));
|
|
141
142
|
}
|
|
142
143
|
});
|
|
143
144
|
});
|
|
@@ -30,8 +30,12 @@ export default class Yarn_classic_processor extends Yarn_processor {
|
|
|
30
30
|
* Adds dependencies to the SBOM
|
|
31
31
|
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
32
32
|
* @param {Object} depTree - The dependency tree object
|
|
33
|
+
* @param {function(PackageURL): (Array<{alg: string, content: string}>|undefined)} [hashesForPurl] - Resolves lock-file hashes for a purl
|
|
33
34
|
*/
|
|
34
|
-
addDependenciesToSbom(sbom: Sbom, depTree: Object
|
|
35
|
+
addDependenciesToSbom(sbom: Sbom, depTree: Object, hashesForPurl?: (arg0: PackageURL) => (Array<{
|
|
36
|
+
alg: string;
|
|
37
|
+
content: string;
|
|
38
|
+
}> | undefined)): void;
|
|
35
39
|
#private;
|
|
36
40
|
}
|
|
37
41
|
import Yarn_processor from "./yarn_processor.js";
|
|
@@ -50,8 +50,9 @@ export default class Yarn_classic_processor extends Yarn_processor {
|
|
|
50
50
|
* Adds dependencies to the SBOM
|
|
51
51
|
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
52
52
|
* @param {Object} depTree - The dependency tree object
|
|
53
|
+
* @param {function(PackageURL): (Array<{alg: string, content: string}>|undefined)} [hashesForPurl] - Resolves lock-file hashes for a purl
|
|
53
54
|
*/
|
|
54
|
-
addDependenciesToSbom(sbom, depTree) {
|
|
55
|
+
addDependenciesToSbom(sbom, depTree, hashesForPurl = () => undefined) {
|
|
55
56
|
if (!depTree?.data?.trees) {
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
@@ -60,12 +61,12 @@ export default class Yarn_classic_processor extends Yarn_processor {
|
|
|
60
61
|
depTree.data.trees.forEach(n => {
|
|
61
62
|
const dep = new NodeMetaData(n);
|
|
62
63
|
if (this._manifest.dependencies.includes(dep.name)) {
|
|
63
|
-
sbom.addDependency(rootPurl, dep.purl);
|
|
64
|
+
sbom.addDependency(rootPurl, dep.purl, undefined, hashesForPurl(dep.purl));
|
|
64
65
|
}
|
|
65
66
|
purls.set(dep.name, dep.purl);
|
|
66
67
|
});
|
|
67
68
|
depTree.data.trees.forEach(n => {
|
|
68
|
-
this.#addChildrenToSbom(sbom, n, purls);
|
|
69
|
+
this.#addChildrenToSbom(sbom, n, purls, hashesForPurl);
|
|
69
70
|
});
|
|
70
71
|
}
|
|
71
72
|
/**
|
|
@@ -73,9 +74,10 @@ export default class Yarn_classic_processor extends Yarn_processor {
|
|
|
73
74
|
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
74
75
|
* @param {Object} node - The current dependency node
|
|
75
76
|
* @param {Map<string, PackageURL>} purls - Map of dependency names to their PackageURL objects
|
|
77
|
+
* @param {function(PackageURL): (Array<{alg: string, content: string}>|undefined)} hashesForPurl - Resolves lock-file hashes for a purl
|
|
76
78
|
* @private
|
|
77
79
|
*/
|
|
78
|
-
#addChildrenToSbom(sbom, node, purls) {
|
|
80
|
+
#addChildrenToSbom(sbom, node, purls, hashesForPurl) {
|
|
79
81
|
const dep = new NodeMetaData(node);
|
|
80
82
|
const children = node.children ? node.children : [];
|
|
81
83
|
children.forEach(c => {
|
|
@@ -83,9 +85,9 @@ export default class Yarn_classic_processor extends Yarn_processor {
|
|
|
83
85
|
const from = dep.shadow ? purls.get(dep.name) : dep.purl;
|
|
84
86
|
const to = child.shadow ? purls.get(child.name) : child.purl;
|
|
85
87
|
if (from && to) {
|
|
86
|
-
sbom.addDependency(from, to);
|
|
88
|
+
sbom.addDependency(from, to, undefined, hashesForPurl(to));
|
|
87
89
|
}
|
|
88
|
-
this.#addChildrenToSbom(sbom, c, purls);
|
|
90
|
+
this.#addChildrenToSbom(sbom, c, purls, hashesForPurl);
|
|
89
91
|
});
|
|
90
92
|
}
|
|
91
93
|
}
|
|
@@ -178,8 +178,11 @@ export default class Python_controller {
|
|
|
178
178
|
startingTime = new Date();
|
|
179
179
|
console.log("Starting time to get requirements.txt dependency tree = " + startingTime);
|
|
180
180
|
}
|
|
181
|
+
let installBestEfforts = getCustom("TRUSTIFY_DA_PYTHON_INSTALL_BEST_EFFORTS", "false", this.options);
|
|
182
|
+
if (installBestEfforts === "true" && this.realEnvironment) {
|
|
183
|
+
throw new Error("Conflicting settings, TRUSTIFY_DA_PYTHON_INSTALL_BEST_EFFORTS=true requires TRUSTIFY_DA_PYTHON_VIRTUAL_ENV=true");
|
|
184
|
+
}
|
|
181
185
|
if (!this.realEnvironment) {
|
|
182
|
-
let installBestEfforts = getCustom("TRUSTIFY_DA_PYTHON_INSTALL_BEST_EFFORTS", "false", this.options);
|
|
183
186
|
if (installBestEfforts === "false") {
|
|
184
187
|
try {
|
|
185
188
|
invokeCommand(this.pathToPipBin, ['install', '-r', this.pathToRequirements]);
|
|
@@ -201,12 +201,13 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
|
201
201
|
let ignoredDeps = getIgnoredDeps(manifest, metadata);
|
|
202
202
|
let crateType = detectCrateType(metadata);
|
|
203
203
|
let license = readLicenseFromManifest(manifest, metadata);
|
|
204
|
+
let hashMap = parseCargoLockHashes(manifestDir);
|
|
204
205
|
let sbom;
|
|
205
206
|
if (crateType === CrateType.WORKSPACE_VIRTUAL) {
|
|
206
|
-
sbom = handleVirtualWorkspace(manifest, metadata, ignoredDeps, includeTransitive, opts, license);
|
|
207
|
+
sbom = handleVirtualWorkspace(manifest, metadata, ignoredDeps, includeTransitive, opts, license, hashMap);
|
|
207
208
|
}
|
|
208
209
|
else {
|
|
209
|
-
sbom = handleSingleCrate(metadata, ignoredDeps, includeTransitive, opts, license);
|
|
210
|
+
sbom = handleSingleCrate(metadata, ignoredDeps, includeTransitive, opts, license, hashMap);
|
|
210
211
|
}
|
|
211
212
|
return sbom;
|
|
212
213
|
}
|
|
@@ -244,6 +245,63 @@ function executeCargoMetadata(cargoBin, manifestDir) {
|
|
|
244
245
|
throw new Error('failed to execute cargo metadata', { cause: error });
|
|
245
246
|
}
|
|
246
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Parses Cargo.lock to extract SHA-256 checksums for each package.
|
|
250
|
+
* @param {string} manifestDir - directory containing Cargo.toml (Cargo.lock is searched upward)
|
|
251
|
+
* @returns {Map<string, Array<{alg: string, content: string}>>} map of "name@version" to CycloneDX hashes
|
|
252
|
+
* @private
|
|
253
|
+
*/
|
|
254
|
+
function parseCargoLockHashes(manifestDir) {
|
|
255
|
+
let hashMap = new Map();
|
|
256
|
+
let lockPath = findCargoLock(manifestDir);
|
|
257
|
+
if (!lockPath) {
|
|
258
|
+
return hashMap;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
let content = fs.readFileSync(lockPath, 'utf-8');
|
|
262
|
+
let parsed = parseToml(content);
|
|
263
|
+
for (let pkg of (parsed.package || [])) {
|
|
264
|
+
if (pkg.name && pkg.version && pkg.checksum) {
|
|
265
|
+
hashMap.set(`${pkg.name}@${pkg.version}`, [{ alg: 'SHA-256', content: pkg.checksum }]);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.warn('Failed to parse Cargo.lock for hashes, SBOM will be generated without hashes');
|
|
271
|
+
}
|
|
272
|
+
return hashMap;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Searches upward from the manifest directory to find the nearest Cargo.lock.
|
|
276
|
+
* @param {string} manifestDir - starting directory
|
|
277
|
+
* @returns {string|null} absolute path to Cargo.lock or null if not found
|
|
278
|
+
* @private
|
|
279
|
+
*/
|
|
280
|
+
function findCargoLock(manifestDir) {
|
|
281
|
+
let dir = path.resolve(manifestDir);
|
|
282
|
+
let parent = dir;
|
|
283
|
+
do {
|
|
284
|
+
dir = parent;
|
|
285
|
+
let lockFile = path.join(dir, 'Cargo.lock');
|
|
286
|
+
if (fs.existsSync(lockFile)) {
|
|
287
|
+
return lockFile;
|
|
288
|
+
}
|
|
289
|
+
let cargoToml = path.join(dir, 'Cargo.toml');
|
|
290
|
+
if (fs.existsSync(cargoToml)) {
|
|
291
|
+
try {
|
|
292
|
+
let content = fs.readFileSync(cargoToml, 'utf-8');
|
|
293
|
+
if (/\[workspace\]/.test(content)) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (_) {
|
|
298
|
+
// ignore read errors, keep searching
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
parent = path.dirname(dir);
|
|
302
|
+
} while (parent !== dir);
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
247
305
|
/**
|
|
248
306
|
* Detects the type of Cargo project from metadata.
|
|
249
307
|
* @param {object} metadata - parsed cargo metadata
|
|
@@ -275,7 +333,7 @@ function detectCrateType(metadata) {
|
|
|
275
333
|
* @returns {string} SBOM json string
|
|
276
334
|
* @private
|
|
277
335
|
*/
|
|
278
|
-
function handleSingleCrate(metadata, ignoredDeps, includeTransitive, opts, license) {
|
|
336
|
+
function handleSingleCrate(metadata, ignoredDeps, includeTransitive, opts, license, hashMap) {
|
|
279
337
|
let rootPackageId = metadata.resolve.root;
|
|
280
338
|
let rootPackage = findPackageById(metadata, rootPackageId);
|
|
281
339
|
let rootPurl = toPurl(rootPackage.name, rootPackage.version);
|
|
@@ -286,10 +344,10 @@ function handleSingleCrate(metadata, ignoredDeps, includeTransitive, opts, licen
|
|
|
286
344
|
return sbom.getAsJsonString(opts);
|
|
287
345
|
}
|
|
288
346
|
if (includeTransitive) {
|
|
289
|
-
addTransitiveDeps(sbom, metadata, rootPackageId, ignoredDeps, new Set(), rootPurl);
|
|
347
|
+
addTransitiveDeps(sbom, metadata, rootPackageId, ignoredDeps, new Set(), hashMap, rootPurl);
|
|
290
348
|
}
|
|
291
349
|
else {
|
|
292
|
-
addDirectDeps(sbom, metadata, rootPackageId, rootPurl, ignoredDeps);
|
|
350
|
+
addDirectDeps(sbom, metadata, rootPackageId, rootPurl, ignoredDeps, hashMap);
|
|
293
351
|
}
|
|
294
352
|
return sbom.getAsJsonString(opts);
|
|
295
353
|
}
|
|
@@ -315,7 +373,7 @@ function handleSingleCrate(metadata, ignoredDeps, includeTransitive, opts, licen
|
|
|
315
373
|
* @returns {string} SBOM json string
|
|
316
374
|
* @private
|
|
317
375
|
*/
|
|
318
|
-
function handleVirtualWorkspace(manifest, metadata, ignoredDeps, includeTransitive, opts, license) {
|
|
376
|
+
function handleVirtualWorkspace(manifest, metadata, ignoredDeps, includeTransitive, opts, license, hashMap) {
|
|
319
377
|
let workspaceRoot = metadata.workspace_root;
|
|
320
378
|
let rootName = path.basename(workspaceRoot);
|
|
321
379
|
let workspaceVersion = getWorkspaceVersion(metadata);
|
|
@@ -323,7 +381,6 @@ function handleVirtualWorkspace(manifest, metadata, ignoredDeps, includeTransiti
|
|
|
323
381
|
let sbom = new Sbom();
|
|
324
382
|
sbom.addRoot(rootPurl, license);
|
|
325
383
|
if (includeTransitive) {
|
|
326
|
-
// Stack analysis: walk all members and their full dependency trees
|
|
327
384
|
let workspaceMembers = metadata.workspace_members || [];
|
|
328
385
|
for (let memberId of workspaceMembers) {
|
|
329
386
|
let memberPackage = findPackageById(metadata, memberId);
|
|
@@ -334,11 +391,10 @@ function handleVirtualWorkspace(manifest, metadata, ignoredDeps, includeTransiti
|
|
|
334
391
|
? toPathDepPurl(memberPackage.name, memberPackage.version)
|
|
335
392
|
: toPurl(memberPackage.name, memberPackage.version);
|
|
336
393
|
sbom.addDependency(rootPurl, memberPurl);
|
|
337
|
-
addTransitiveDeps(sbom, metadata, memberId, ignoredDeps, new Set(), memberPurl);
|
|
394
|
+
addTransitiveDeps(sbom, metadata, memberId, ignoredDeps, new Set(), hashMap, memberPurl);
|
|
338
395
|
}
|
|
339
396
|
}
|
|
340
397
|
else {
|
|
341
|
-
// Component analysis: only [workspace.dependencies] from root Cargo.toml
|
|
342
398
|
let workspaceDeps = getWorkspaceDepsFromManifest(manifest);
|
|
343
399
|
for (let depName of workspaceDeps) {
|
|
344
400
|
if (isDepIgnored(depName, ignoredDeps)) {
|
|
@@ -355,7 +411,8 @@ function handleVirtualWorkspace(manifest, metadata, ignoredDeps, includeTransiti
|
|
|
355
411
|
let depPurl = pkg.source == null
|
|
356
412
|
? toPathDepPurl(pkg.name, pkg.version)
|
|
357
413
|
: toPurl(pkg.name, pkg.version);
|
|
358
|
-
|
|
414
|
+
let hashes = hashMap.get(`${pkg.name}@${pkg.version}`);
|
|
415
|
+
sbom.addDependency(rootPurl, depPurl, undefined, hashes);
|
|
359
416
|
}
|
|
360
417
|
}
|
|
361
418
|
return sbom.getAsJsonString(opts);
|
|
@@ -374,7 +431,7 @@ function handleVirtualWorkspace(manifest, metadata, ignoredDeps, includeTransiti
|
|
|
374
431
|
* so callers can ensure it matches the purl already added to the SBOM
|
|
375
432
|
* @private
|
|
376
433
|
*/
|
|
377
|
-
function addTransitiveDeps(sbom, metadata, packageId, ignoredDeps, visited, startingPurl) {
|
|
434
|
+
function addTransitiveDeps(sbom, metadata, packageId, ignoredDeps, visited, hashMap, startingPurl) {
|
|
378
435
|
if (visited.has(packageId)) {
|
|
379
436
|
return;
|
|
380
437
|
}
|
|
@@ -402,8 +459,9 @@ function addTransitiveDeps(sbom, metadata, packageId, ignoredDeps, visited, star
|
|
|
402
459
|
let depPurl = depPackage.source == null
|
|
403
460
|
? toPathDepPurl(depPackage.name, depPackage.version)
|
|
404
461
|
: toPurl(depPackage.name, depPackage.version);
|
|
405
|
-
|
|
406
|
-
|
|
462
|
+
let hashes = hashMap ? hashMap.get(`${depPackage.name}@${depPackage.version}`) : undefined;
|
|
463
|
+
sbom.addDependency(sourcePurl, depPurl, undefined, hashes);
|
|
464
|
+
addTransitiveDeps(sbom, metadata, depId, ignoredDeps, visited, hashMap);
|
|
407
465
|
}
|
|
408
466
|
}
|
|
409
467
|
/**
|
|
@@ -416,7 +474,7 @@ function addTransitiveDeps(sbom, metadata, packageId, ignoredDeps, visited, star
|
|
|
416
474
|
* @param {Set<string>} ignoredDeps - set of ignored dependency names
|
|
417
475
|
* @private
|
|
418
476
|
*/
|
|
419
|
-
function addDirectDeps(sbom, metadata, packageId, parentPurl, ignoredDeps) {
|
|
477
|
+
function addDirectDeps(sbom, metadata, packageId, parentPurl, ignoredDeps, hashMap) {
|
|
420
478
|
let resolveNode = findResolveNode(metadata, packageId);
|
|
421
479
|
if (!resolveNode) {
|
|
422
480
|
return;
|
|
@@ -433,7 +491,8 @@ function addDirectDeps(sbom, metadata, packageId, parentPurl, ignoredDeps) {
|
|
|
433
491
|
let depPurl = depPackage.source == null
|
|
434
492
|
? toPathDepPurl(depPackage.name, depPackage.version)
|
|
435
493
|
: toPurl(depPackage.name, depPackage.version);
|
|
436
|
-
|
|
494
|
+
let hashes = hashMap ? hashMap.get(`${depPackage.name}@${depPackage.version}`) : undefined;
|
|
495
|
+
sbom.addDependency(parentPurl, depPurl, undefined, hashes);
|
|
437
496
|
}
|
|
438
497
|
}
|
|
439
498
|
/**
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrates the full remediation pipeline for a single manifest or directory:
|
|
3
|
+
* discover manifests → scan via DA backend → extract remediations → apply or preview.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} targetPath - path to a manifest file or directory
|
|
6
|
+
* @param {object} [options]
|
|
7
|
+
* @param {boolean} [options.dryRun=false] - preview changes without modifying files (applies by default)
|
|
8
|
+
* @param {string} [options.providers] - comma-separated provider list
|
|
9
|
+
* @param {string} [options.sources] - comma-separated source list
|
|
10
|
+
* @param {'dependency'|'bundle'} [options.groupBy='dependency'] - report grouping strategy
|
|
11
|
+
* @returns {Promise<{exitCode: number, output: string}>}
|
|
12
|
+
*/
|
|
13
|
+
export function runRemediation(targetPath: string, options?: {
|
|
14
|
+
dryRun?: boolean | undefined;
|
|
15
|
+
providers?: string | undefined;
|
|
16
|
+
sources?: string | undefined;
|
|
17
|
+
groupBy?: "bundle" | "dependency" | undefined;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
exitCode: number;
|
|
20
|
+
output: string;
|
|
21
|
+
}>;
|