@trustify-da/trustify-da-javascript-client 0.3.0-ea.61444d4 → 0.3.0-ea.62b88e5
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 +22 -8
- package/dist/package.json +6 -5
- package/dist/src/analysis.d.ts +40 -19
- package/dist/src/analysis.js +41 -5
- package/dist/src/cli.js +95 -17
- package/dist/src/config.d.ts +120 -0
- package/dist/src/config.js +260 -0
- 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 +23 -23
- package/dist/src/index.js +23 -39
- package/dist/src/license/index.d.ts +2 -2
- package/dist/src/license/index.js +9 -6
- package/dist/src/license/licenses_api.d.ts +2 -2
- package/dist/src/license/licenses_api.js +1 -1
- 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 +106 -4
- package/dist/src/providers/golang_gomodules.js +6 -5
- package/dist/src/providers/java_gradle.d.ts +48 -0
- package/dist/src/providers/java_gradle.js +204 -20
- package/dist/src/providers/java_maven.d.ts +28 -8
- package/dist/src/providers/java_maven.js +104 -10
- 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/manifest.js +6 -3
- 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_uv.js +6 -0
- package/dist/src/providers/requirements_parser.js +1 -1
- package/dist/src/providers/rust_cargo.js +34 -52
- package/dist/src/remediate.d.ts +90 -4
- package/dist/src/remediate.js +153 -58
- package/dist/src/remediation.d.ts +54 -30
- package/dist/src/remediation.js +92 -53
- package/dist/src/remediation_report.d.ts +3 -20
- package/dist/src/remediation_report.js +37 -16
- package/dist/src/sbom.d.ts +11 -0
- package/dist/src/sbom.js +10 -0
- package/dist/src/tools.d.ts +9 -11
- package/dist/src/tools.js +34 -13
- package/dist/src/updaters/maven_updater.d.ts +40 -8
- package/dist/src/updaters/maven_updater.js +26 -3
- package/dist/src/updaters/toml_updater.d.ts +42 -13
- package/dist/src/updaters/toml_updater.js +36 -4
- package/dist/src/workspace.d.ts +2 -1
- package/dist/src/workspace.js +2 -1
- package/package.json +7 -6
|
@@ -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
|
}
|
|
@@ -55,11 +55,14 @@ export default class Manifest {
|
|
|
55
55
|
}
|
|
56
56
|
loadIgnored(content) {
|
|
57
57
|
let deps = [];
|
|
58
|
-
|
|
58
|
+
// Prefer the new 'trustify-da-ignore' key, falling back to the legacy
|
|
59
|
+
// 'exhortignore' key for backwards compatibility.
|
|
60
|
+
let ignored = content['trustify-da-ignore'] || content.exhortignore;
|
|
61
|
+
if (!ignored) {
|
|
59
62
|
return deps;
|
|
60
63
|
}
|
|
61
|
-
for (let i = 0; i <
|
|
62
|
-
deps.push(toPurl("npm",
|
|
64
|
+
for (let i = 0; i < ignored.length; i++) {
|
|
65
|
+
deps.push(toPurl("npm", ignored[i]));
|
|
63
66
|
}
|
|
64
67
|
return deps;
|
|
65
68
|
}
|
|
@@ -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
|
}
|
|
@@ -144,6 +144,12 @@ export default class Python_uv extends Base_pyproject {
|
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
+
// PEP 440 direct references (name @ url) — skip, no pinned version available
|
|
148
|
+
if (child.children.find(c => c.type === 'url_spec')) {
|
|
149
|
+
currentPkg = null;
|
|
150
|
+
collectingVia = false;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
147
153
|
let name = nameNode.text;
|
|
148
154
|
let version = null;
|
|
149
155
|
let versionMatches = pinnedVersionQuery.matches(child);
|
|
@@ -16,7 +16,7 @@ export async function getRequirementQuery() {
|
|
|
16
16
|
}
|
|
17
17
|
export async function getIgnoreQuery() {
|
|
18
18
|
const language = await init();
|
|
19
|
-
return new Query(language, '((requirement (package) @name) @req . (comment) @comment (#match? @comment "^#[\\t ]*exhortignore"))');
|
|
19
|
+
return new Query(language, '((requirement (package) @name) @req . (comment) @comment (#match? @comment "^#[\\t ]*(exhortignore|trustify-da-ignore)(?![\\w-])"))');
|
|
20
20
|
}
|
|
21
21
|
export async function getPinnedVersionQuery() {
|
|
22
22
|
const language = await init();
|
|
@@ -116,30 +116,25 @@ function resolveWorkspaceLicense(parsed, metadata) {
|
|
|
116
116
|
return parsed.workspace?.package?.license || null;
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* the workspace root, so when a member crate's Cargo.toml is provided we walk
|
|
122
|
-
* up the directory tree looking for Cargo.lock (stopping when we find a
|
|
123
|
-
* Cargo.toml that contains a [workspace] section, or when we reach the
|
|
124
|
-
* filesystem root).
|
|
125
|
-
* When TRUSTIFY_DA_WORKSPACE_DIR is provided (via env var or opts),
|
|
126
|
-
* checks only that directory for Cargo.lock — no walk-up.
|
|
119
|
+
* Finds the directory containing Cargo.lock, honoring TRUSTIFY_DA_WORKSPACE_DIR.
|
|
120
|
+
* Walks up from manifestDir looking for Cargo.lock, stopping at workspace root or filesystem root.
|
|
127
121
|
* @param {string} manifestDir - the directory where the manifest lies
|
|
128
122
|
* @param {{TRUSTIFY_DA_WORKSPACE_DIR?: string}} [opts={}] - optional workspace root
|
|
129
|
-
* @returns {
|
|
123
|
+
* @returns {string|null} the directory path containing Cargo.lock, or null if not found
|
|
124
|
+
* @private
|
|
130
125
|
*/
|
|
131
|
-
function
|
|
126
|
+
function _findCargoLockDir(manifestDir, opts = {}) {
|
|
132
127
|
const workspaceDir = getCustom('TRUSTIFY_DA_WORKSPACE_DIR', null, opts);
|
|
133
128
|
if (workspaceDir) {
|
|
134
129
|
const dir = path.resolve(workspaceDir);
|
|
135
|
-
return fs.existsSync(path.join(dir, 'Cargo.lock'));
|
|
130
|
+
return fs.existsSync(path.join(dir, 'Cargo.lock')) ? dir : null;
|
|
136
131
|
}
|
|
137
132
|
let dir = path.resolve(manifestDir);
|
|
138
133
|
let parent = dir;
|
|
139
134
|
do {
|
|
140
135
|
dir = parent;
|
|
141
136
|
if (fs.existsSync(path.join(dir, 'Cargo.lock'))) {
|
|
142
|
-
return
|
|
137
|
+
return dir;
|
|
143
138
|
}
|
|
144
139
|
// If this directory has a Cargo.toml with [workspace], the lock file
|
|
145
140
|
// should have been here — stop searching.
|
|
@@ -148,7 +143,7 @@ function validateLockFile(manifestDir, opts = {}) {
|
|
|
148
143
|
try {
|
|
149
144
|
let content = fs.readFileSync(cargoToml, 'utf-8');
|
|
150
145
|
if (/\[workspace\]/.test(content)) {
|
|
151
|
-
return
|
|
146
|
+
return null;
|
|
152
147
|
}
|
|
153
148
|
}
|
|
154
149
|
catch (_) {
|
|
@@ -157,7 +152,23 @@ function validateLockFile(manifestDir, opts = {}) {
|
|
|
157
152
|
}
|
|
158
153
|
parent = path.dirname(dir);
|
|
159
154
|
} while (parent !== dir);
|
|
160
|
-
return
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Validates that Cargo.lock exists in the manifest directory or in a parent
|
|
159
|
+
* workspace root directory. In Cargo workspaces the lock file always lives at
|
|
160
|
+
* the workspace root, so when a member crate's Cargo.toml is provided we walk
|
|
161
|
+
* up the directory tree looking for Cargo.lock (stopping when we find a
|
|
162
|
+
* Cargo.toml that contains a [workspace] section, or when we reach the
|
|
163
|
+
* filesystem root).
|
|
164
|
+
* When TRUSTIFY_DA_WORKSPACE_DIR is provided (via env var or opts),
|
|
165
|
+
* checks only that directory for Cargo.lock — no walk-up.
|
|
166
|
+
* @param {string} manifestDir - the directory where the manifest lies
|
|
167
|
+
* @param {{TRUSTIFY_DA_WORKSPACE_DIR?: string}} [opts={}] - optional workspace root
|
|
168
|
+
* @returns {boolean} true if Cargo.lock is found
|
|
169
|
+
*/
|
|
170
|
+
function validateLockFile(manifestDir, opts = {}) {
|
|
171
|
+
return _findCargoLockDir(manifestDir, opts) !== null;
|
|
161
172
|
}
|
|
162
173
|
/**
|
|
163
174
|
* Provide content and content type for Cargo stack analysis.
|
|
@@ -201,7 +212,7 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
|
201
212
|
let ignoredDeps = getIgnoredDeps(manifest, metadata);
|
|
202
213
|
let crateType = detectCrateType(metadata);
|
|
203
214
|
let license = readLicenseFromManifest(manifest, metadata);
|
|
204
|
-
let hashMap = parseCargoLockHashes(manifestDir);
|
|
215
|
+
let hashMap = parseCargoLockHashes(manifestDir, opts);
|
|
205
216
|
let sbom;
|
|
206
217
|
if (crateType === CrateType.WORKSPACE_VIRTUAL) {
|
|
207
218
|
sbom = handleVirtualWorkspace(manifest, metadata, ignoredDeps, includeTransitive, opts, license, hashMap);
|
|
@@ -248,15 +259,17 @@ function executeCargoMetadata(cargoBin, manifestDir) {
|
|
|
248
259
|
/**
|
|
249
260
|
* Parses Cargo.lock to extract SHA-256 checksums for each package.
|
|
250
261
|
* @param {string} manifestDir - directory containing Cargo.toml (Cargo.lock is searched upward)
|
|
262
|
+
* @param {{TRUSTIFY_DA_WORKSPACE_DIR?: string}} [opts={}] - optional workspace root
|
|
251
263
|
* @returns {Map<string, Array<{alg: string, content: string}>>} map of "name@version" to CycloneDX hashes
|
|
252
264
|
* @private
|
|
253
265
|
*/
|
|
254
|
-
function parseCargoLockHashes(manifestDir) {
|
|
266
|
+
function parseCargoLockHashes(manifestDir, opts = {}) {
|
|
255
267
|
let hashMap = new Map();
|
|
256
|
-
let
|
|
257
|
-
if (!
|
|
268
|
+
let lockDir = _findCargoLockDir(manifestDir, opts);
|
|
269
|
+
if (!lockDir) {
|
|
258
270
|
return hashMap;
|
|
259
271
|
}
|
|
272
|
+
let lockPath = path.join(lockDir, 'Cargo.lock');
|
|
260
273
|
try {
|
|
261
274
|
let content = fs.readFileSync(lockPath, 'utf-8');
|
|
262
275
|
let parsed = parseToml(content);
|
|
@@ -267,41 +280,10 @@ function parseCargoLockHashes(manifestDir) {
|
|
|
267
280
|
}
|
|
268
281
|
}
|
|
269
282
|
catch (error) {
|
|
270
|
-
console.warn(
|
|
283
|
+
console.warn(`Failed to parse Cargo.lock at ${lockPath} for hashes: ${error.message}. SBOM will be generated without hashes.`);
|
|
271
284
|
}
|
|
272
285
|
return hashMap;
|
|
273
286
|
}
|
|
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
|
-
}
|
|
305
287
|
/**
|
|
306
288
|
* Detects the type of Cargo project from metadata.
|
|
307
289
|
* @param {object} metadata - parsed cargo metadata
|
|
@@ -459,7 +441,7 @@ function addTransitiveDeps(sbom, metadata, packageId, ignoredDeps, visited, hash
|
|
|
459
441
|
let depPurl = depPackage.source == null
|
|
460
442
|
? toPathDepPurl(depPackage.name, depPackage.version)
|
|
461
443
|
: toPurl(depPackage.name, depPackage.version);
|
|
462
|
-
let hashes = hashMap
|
|
444
|
+
let hashes = hashMap.get(`${depPackage.name}@${depPackage.version}`);
|
|
463
445
|
sbom.addDependency(sourcePurl, depPurl, undefined, hashes);
|
|
464
446
|
addTransitiveDeps(sbom, metadata, depId, ignoredDeps, visited, hashMap);
|
|
465
447
|
}
|
|
@@ -491,7 +473,7 @@ function addDirectDeps(sbom, metadata, packageId, parentPurl, ignoredDeps, hashM
|
|
|
491
473
|
let depPurl = depPackage.source == null
|
|
492
474
|
? toPathDepPurl(depPackage.name, depPackage.version)
|
|
493
475
|
: toPurl(depPackage.name, depPackage.version);
|
|
494
|
-
let hashes = hashMap
|
|
476
|
+
let hashes = hashMap.get(`${depPackage.name}@${depPackage.version}`);
|
|
495
477
|
sbom.addDependency(parentPurl, depPurl, undefined, hashes);
|
|
496
478
|
}
|
|
497
479
|
}
|