@trustify-da/trustify-da-javascript-client 0.3.0-ea.bbe2094 → 0.3.0-ea.be56bee
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 +1 -1
- package/dist/package.json +24 -21
- package/dist/src/analysis.d.ts +7 -7
- package/dist/src/analysis.js +4 -4
- package/dist/src/cyclone_dx_sbom.js +11 -3
- package/dist/src/index.d.ts +10 -10
- package/dist/src/index.js +4 -2
- 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/provider.d.ts +4 -3
- package/dist/src/provider.js +4 -2
- package/dist/src/providers/base_java.d.ts +14 -3
- package/dist/src/providers/base_java.js +16 -6
- package/dist/src/providers/base_javascript.d.ts +14 -9
- package/dist/src/providers/base_javascript.js +7 -0
- 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 +4 -0
- package/dist/src/providers/containerfile_parser.js +16 -0
- package/dist/src/providers/golang_gomodules.d.ts +2 -1
- package/dist/src/providers/golang_gomodules.js +1 -1
- 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 +2 -2
- package/dist/src/providers/java_maven.js +72 -0
- package/dist/src/providers/javascript_npm.d.ts +6 -1
- package/dist/src/providers/oci_dockerfile.d.ts +49 -0
- package/dist/src/providers/oci_dockerfile.js +104 -0
- package/dist/src/providers/processors/yarn_berry_processor.d.ts +2 -2
- package/dist/src/providers/processors/yarn_classic_processor.d.ts +2 -2
- package/dist/src/providers/python_controller.js +57 -3
- package/dist/src/providers/python_pip.d.ts +1 -0
- package/dist/src/providers/python_pip.js +1 -1
- package/dist/src/providers/python_poetry.d.ts +31 -8
- package/dist/src/providers/python_poetry.js +62 -14
- package/dist/src/providers/python_uv.d.ts +5 -14
- package/dist/src/providers/python_uv.js +44 -2
- package/dist/src/providers/rust_cargo.d.ts +6 -2
- package/dist/src/providers/rust_cargo.js +31 -4
- package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
- package/dist/src/tools.d.ts +2 -2
- package/package.json +25 -22
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type purlType =
|
|
1
|
+
export type purlType = any;
|
|
2
2
|
/** @typedef {import('../provider').Provider} */
|
|
3
3
|
/** @typedef {import('../provider').Provided} Provided */
|
|
4
4
|
/**
|
|
@@ -19,7 +19,7 @@ export default class Base_javascript {
|
|
|
19
19
|
* @param {Object} opts - Configuration options for the provider
|
|
20
20
|
* @protected
|
|
21
21
|
*/
|
|
22
|
-
protected _setUp(manifestPath: string, opts:
|
|
22
|
+
protected _setUp(manifestPath: string, opts: Object): void;
|
|
23
23
|
/**
|
|
24
24
|
* Gets the current manifest object
|
|
25
25
|
* @returns {Manifest} The manifest object
|
|
@@ -47,6 +47,11 @@ export default class Base_javascript {
|
|
|
47
47
|
*/
|
|
48
48
|
protected _cmdName(): string;
|
|
49
49
|
/**
|
|
50
|
+
* Returns the package manager name (e.g. npm, yarn, pnpm, bun)
|
|
51
|
+
* @returns {string} The package manager name
|
|
52
|
+
*/
|
|
53
|
+
packageManagerName(): string;
|
|
54
|
+
/**
|
|
50
55
|
* Returns the command arguments for listing dependencies
|
|
51
56
|
* @returns {Array<string>} The command arguments
|
|
52
57
|
* @abstract
|
|
@@ -86,21 +91,21 @@ export default class Base_javascript {
|
|
|
86
91
|
* @param {Object} [opts={}] - optional; may contain TRUSTIFY_DA_WORKSPACE_DIR
|
|
87
92
|
* @returns {boolean} True if the lock file exists
|
|
88
93
|
*/
|
|
89
|
-
validateLockFile(manifestDir: string, opts?:
|
|
94
|
+
validateLockFile(manifestDir: string, opts?: Object): boolean;
|
|
90
95
|
/**
|
|
91
96
|
* Provides content and content type for stack analysis
|
|
92
97
|
* @param {string} manifestPath - The manifest path or name
|
|
93
98
|
* @param {Object} [opts={}] - Optional configuration options
|
|
94
99
|
* @returns {Provided} The provided data for stack analysis
|
|
95
100
|
*/
|
|
96
|
-
provideStack(manifestPath: string, opts?:
|
|
101
|
+
provideStack(manifestPath: string, opts?: Object): Provided;
|
|
97
102
|
/**
|
|
98
103
|
* Provides content and content type for component analysis
|
|
99
104
|
* @param {string} manifestPath - Path to package.json for component report
|
|
100
105
|
* @param {Object} [opts={}] - Optional configuration options
|
|
101
106
|
* @returns {Provided} The provided data for component analysis
|
|
102
107
|
*/
|
|
103
|
-
provideComponent(manifestPath: string, opts?:
|
|
108
|
+
provideComponent(manifestPath: string, opts?: Object): Provided;
|
|
104
109
|
/**
|
|
105
110
|
* Read license from manifest (package.json). Reused by npm, pnpm, yarn.
|
|
106
111
|
* @param {string} manifestPath - path to package.json
|
|
@@ -114,21 +119,21 @@ export default class Base_javascript {
|
|
|
114
119
|
* @returns {Object} The dependency tree
|
|
115
120
|
* @protected
|
|
116
121
|
*/
|
|
117
|
-
protected _buildDependencyTree(includeTransitive: boolean, opts?:
|
|
122
|
+
protected _buildDependencyTree(includeTransitive: boolean, opts?: Object): Object;
|
|
118
123
|
/**
|
|
119
124
|
* Recursively builds the Sbom from the JSON that npm listing returns
|
|
120
125
|
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
121
126
|
* @param {Object} depTree - The current dependency tree
|
|
122
127
|
* @protected
|
|
123
128
|
*/
|
|
124
|
-
protected _addDependenciesToSbom(sbom: Sbom, depTree:
|
|
129
|
+
protected _addDependenciesToSbom(sbom: Sbom, depTree: Object): void;
|
|
125
130
|
/**
|
|
126
131
|
* Extracts root dependencies from the dependency tree
|
|
127
132
|
* @param {Object} depTree - The dependency tree object
|
|
128
133
|
* @returns {Map<string, PackageURL>} Map of dependency names to their PackageURL objects
|
|
129
134
|
* @protected
|
|
130
135
|
*/
|
|
131
|
-
protected _getRootDependencies(depTree:
|
|
136
|
+
protected _getRootDependencies(depTree: Object): Map<string, PackageURL>;
|
|
132
137
|
/**
|
|
133
138
|
* Gets the version of the package manager
|
|
134
139
|
* @returns {string} The version string of the package manager
|
|
@@ -150,6 +155,6 @@ export default class Base_javascript {
|
|
|
150
155
|
protected _parseDepTreeOutput(output: string): string;
|
|
151
156
|
#private;
|
|
152
157
|
}
|
|
153
|
-
export type Provided =
|
|
158
|
+
export type Provided = any;
|
|
154
159
|
import Manifest from './manifest.js';
|
|
155
160
|
import Sbom from '../sbom.js';
|
|
@@ -71,6 +71,13 @@ export default class Base_javascript {
|
|
|
71
71
|
throw new TypeError("_cmdName must be implemented");
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
|
+
* Returns the package manager name (e.g. npm, yarn, pnpm, bun)
|
|
75
|
+
* @returns {string} The package manager name
|
|
76
|
+
*/
|
|
77
|
+
packageManagerName() {
|
|
78
|
+
return this._cmdName();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
74
81
|
* Returns the command arguments for listing dependencies
|
|
75
82
|
* @returns {Array<string>} The command arguments
|
|
76
83
|
* @abstract
|
|
@@ -13,7 +13,7 @@ export default class Base_pyproject {
|
|
|
13
13
|
* @param {Object} [opts={}]
|
|
14
14
|
* @returns {boolean}
|
|
15
15
|
*/
|
|
16
|
-
validateLockFile(manifestDir: string, opts?:
|
|
16
|
+
validateLockFile(manifestDir: string, opts?: Object): boolean;
|
|
17
17
|
/**
|
|
18
18
|
* Walk up from manifestDir to find the directory containing the lock file.
|
|
19
19
|
* Follows the same pattern as Base_javascript._findLockFileDir().
|
|
@@ -22,7 +22,7 @@ export default class Base_pyproject {
|
|
|
22
22
|
* @returns {string|null}
|
|
23
23
|
* @protected
|
|
24
24
|
*/
|
|
25
|
-
protected _findLockFileDir(manifestDir: string, opts?:
|
|
25
|
+
protected _findLockFileDir(manifestDir: string, opts?: Object): string | null;
|
|
26
26
|
/**
|
|
27
27
|
* Detect workspace root boundaries.
|
|
28
28
|
* Currently only uv has native workspace support ([tool.uv.workspace] in pyproject.toml).
|
|
@@ -44,13 +44,13 @@ export default class Base_pyproject {
|
|
|
44
44
|
* @param {Object} [opts={}]
|
|
45
45
|
* @returns {Promise<Provided>}
|
|
46
46
|
*/
|
|
47
|
-
provideStack(manifest: string, opts?:
|
|
47
|
+
provideStack(manifest: string, opts?: Object): Promise<Provided>;
|
|
48
48
|
/**
|
|
49
49
|
* @param {string} manifest - path to pyproject.toml
|
|
50
50
|
* @param {Object} [opts={}]
|
|
51
51
|
* @returns {Promise<Provided>}
|
|
52
52
|
*/
|
|
53
|
-
provideComponent(manifest: string, opts?:
|
|
53
|
+
provideComponent(manifest: string, opts?: Object): Promise<Provided>;
|
|
54
54
|
/**
|
|
55
55
|
* @returns {string}
|
|
56
56
|
* @protected
|
|
@@ -61,6 +61,11 @@ export default class Base_pyproject {
|
|
|
61
61
|
* @protected
|
|
62
62
|
*/
|
|
63
63
|
protected _cmdName(): string;
|
|
64
|
+
/**
|
|
65
|
+
* Returns the package manager name (e.g. pip, poetry, uv)
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
packageManagerName(): string;
|
|
64
69
|
/**
|
|
65
70
|
* Resolve dependencies using the tool-specific command and parser.
|
|
66
71
|
*
|
|
@@ -72,7 +77,7 @@ export default class Base_pyproject {
|
|
|
72
77
|
* @returns {Promise<DependencyData>}
|
|
73
78
|
* @protected
|
|
74
79
|
*/
|
|
75
|
-
protected _getDependencyData(manifestDir: string, workspaceDir: string, parsed: object, opts:
|
|
80
|
+
protected _getDependencyData(manifestDir: string, workspaceDir: string, parsed: object, opts: Object): Promise<DependencyData>;
|
|
76
81
|
/**
|
|
77
82
|
* Canonicalize a Python package name per PEP 503.
|
|
78
83
|
* @param {string} name
|
|
@@ -145,6 +145,13 @@ export default class Base_pyproject {
|
|
|
145
145
|
_cmdName() {
|
|
146
146
|
throw new TypeError('_cmdName must be implemented');
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Returns the package manager name (e.g. pip, poetry, uv)
|
|
150
|
+
* @returns {string}
|
|
151
|
+
*/
|
|
152
|
+
packageManagerName() {
|
|
153
|
+
return this._cmdName();
|
|
154
|
+
}
|
|
148
155
|
/**
|
|
149
156
|
* Resolve dependencies using the tool-specific command and parser.
|
|
150
157
|
*
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { Language, Parser, Query } from 'web-tree-sitter';
|
|
3
|
+
const wasmUrl = new URL('./tree-sitter-containerfile.wasm', import.meta.url);
|
|
4
|
+
async function init() {
|
|
5
|
+
await Parser.init();
|
|
6
|
+
const wasmBytes = new Uint8Array(await readFile(wasmUrl));
|
|
7
|
+
return await Language.load(wasmBytes);
|
|
8
|
+
}
|
|
9
|
+
export async function getParser() {
|
|
10
|
+
const language = await init();
|
|
11
|
+
return new Parser().setLanguage(language);
|
|
12
|
+
}
|
|
13
|
+
export async function getFromQuery() {
|
|
14
|
+
const language = await init();
|
|
15
|
+
return new Query(language, '(from_instruction (image_spec) @image)');
|
|
16
|
+
}
|
|
@@ -13,9 +13,10 @@ declare namespace _default {
|
|
|
13
13
|
export { provideComponent };
|
|
14
14
|
export { provideStack };
|
|
15
15
|
export { readLicenseFromManifest };
|
|
16
|
+
export function packageManagerName(): string;
|
|
16
17
|
}
|
|
17
18
|
export default _default;
|
|
18
|
-
export type Provided =
|
|
19
|
+
export type Provided = any;
|
|
19
20
|
export type Package = {
|
|
20
21
|
name: string;
|
|
21
22
|
version: string;
|
|
@@ -6,7 +6,7 @@ import Sbom from '../sbom.js';
|
|
|
6
6
|
import { getCustom, getCustomPath, invokeCommand } from "../tools.js";
|
|
7
7
|
import { filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore } from '../workspace.js';
|
|
8
8
|
import { getParser, getRequireQuery } from './gomod_parser.js';
|
|
9
|
-
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest };
|
|
9
|
+
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest, packageManagerName() { return 'go'; } };
|
|
10
10
|
/** @typedef {import('../provider').Provider} */
|
|
11
11
|
/** @typedef {import('../provider').Provided} Provided */
|
|
12
12
|
/** @typedef {{name: string, version: string}} Package */
|
|
@@ -3,5 +3,5 @@ export default class Java_gradle_groovy extends Java_gradle {
|
|
|
3
3
|
_parseAliasForLibsNotation(alias: any): any;
|
|
4
4
|
_extractDepToBeIgnored(dep: any): any;
|
|
5
5
|
}
|
|
6
|
-
export type Provided =
|
|
6
|
+
export type Provided = any;
|
|
7
7
|
import Java_gradle from './java_gradle.js';
|
|
@@ -7,5 +7,5 @@ export default class Java_gradle_kotlin extends Java_gradle {
|
|
|
7
7
|
_parseAliasForLibsNotation(alias: any): any;
|
|
8
8
|
_extractDepToBeIgnored(dep: any): string | null;
|
|
9
9
|
}
|
|
10
|
-
export type Provided =
|
|
10
|
+
export type Provided = any;
|
|
11
11
|
import Java_gradle from './java_gradle.js';
|
|
@@ -51,8 +51,8 @@ export default class Java_maven extends Base_java {
|
|
|
51
51
|
createSbomFileFromTextFormat(textGraphList: string, ignoredDeps: [string], opts: any, manifestPath: string): string;
|
|
52
52
|
#private;
|
|
53
53
|
}
|
|
54
|
-
export type Java_maven =
|
|
55
|
-
export type Provided =
|
|
54
|
+
export type Java_maven = any;
|
|
55
|
+
export type Provided = any;
|
|
56
56
|
export type Package = {
|
|
57
57
|
name: string;
|
|
58
58
|
version: string;
|
|
@@ -181,6 +181,7 @@ export default class Java_maven extends Base_java {
|
|
|
181
181
|
/** @type [Dependency] */
|
|
182
182
|
let dependencies = this.#getDependencies(tmpEffectivePom)
|
|
183
183
|
.filter(d => !this.#dependencyIn(d, ignored));
|
|
184
|
+
dependencies = this.#resolveVersionRanges(dependencies, manifestPath, opts);
|
|
184
185
|
let sbom = new Sbom();
|
|
185
186
|
let rootDependency = this.#getRootFromPom(tmpEffectivePom, manifestPath);
|
|
186
187
|
let purlRoot = this.toPurl(rootDependency.groupId, rootDependency.artifactId, rootDependency.version);
|
|
@@ -289,6 +290,77 @@ export default class Java_maven extends Base_java {
|
|
|
289
290
|
#dependencyIn(dep, deps) {
|
|
290
291
|
return deps.filter(d => dep.artifactId === d.artifactId && dep.groupId === d.groupId && dep.scope === d.scope).length > 0;
|
|
291
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Returns true if the given version string is a Maven version range
|
|
295
|
+
* (starts with '[' or '(').
|
|
296
|
+
* @param {string} version
|
|
297
|
+
* @returns {boolean}
|
|
298
|
+
* @private
|
|
299
|
+
*/
|
|
300
|
+
#isVersionRange(version) {
|
|
301
|
+
return typeof version === 'string' && (version.startsWith('[') || version.startsWith('('));
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Resolves Maven version ranges in the given dependency list by running
|
|
305
|
+
* maven-dependency-plugin:tree and reading the concrete versions it selects.
|
|
306
|
+
* If no dependency uses a version range, returns the list unchanged.
|
|
307
|
+
* @param {Dependency[]} dependencies
|
|
308
|
+
* @param {string} manifestPath
|
|
309
|
+
* @param {object} opts
|
|
310
|
+
* @returns {Dependency[]}
|
|
311
|
+
* @private
|
|
312
|
+
*/
|
|
313
|
+
#resolveVersionRanges(dependencies, manifestPath, opts = {}) {
|
|
314
|
+
// short-circuit if no dependency has a version range
|
|
315
|
+
if (!dependencies.some(dep => this.#isVersionRange(dep.version))) {
|
|
316
|
+
return dependencies;
|
|
317
|
+
}
|
|
318
|
+
const mvn = this.selectToolBinary(manifestPath, opts);
|
|
319
|
+
const mvnArgs = JSON.parse(getCustom('TRUSTIFY_DA_MVN_ARGS', '[]', opts));
|
|
320
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'trustify_da_range_'));
|
|
321
|
+
const tmpDepTree = path.join(tmpDir, 'mvn_deptree_ranges.txt');
|
|
322
|
+
try {
|
|
323
|
+
this._invokeCommand(mvn, [
|
|
324
|
+
'-q',
|
|
325
|
+
'org.apache.maven.plugins:maven-dependency-plugin:3.6.0:tree',
|
|
326
|
+
'-Dscope=compile',
|
|
327
|
+
'-DoutputType=text',
|
|
328
|
+
`-DoutputFile=${tmpDepTree}`,
|
|
329
|
+
...mvnArgs
|
|
330
|
+
], { cwd: path.dirname(manifestPath) });
|
|
331
|
+
const content = fs.readFileSync(tmpDepTree);
|
|
332
|
+
const lines = content.toString().split(EOL).filter(l => l.trim() !== '');
|
|
333
|
+
// Build a map of groupId:artifactId -> resolved version from depth-1 entries
|
|
334
|
+
/** @type {Map<string, string>} */
|
|
335
|
+
const resolvedVersions = new Map();
|
|
336
|
+
for (const line of lines) {
|
|
337
|
+
if (this._getDepth(line) === 1) {
|
|
338
|
+
const purl = this.parseDep(line);
|
|
339
|
+
resolvedVersions.set(`${purl.namespace}:${purl.name}`, purl.version);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Replace version ranges with resolved concrete versions
|
|
343
|
+
return dependencies.map(dep => {
|
|
344
|
+
if (this.#isVersionRange(dep.version)) {
|
|
345
|
+
const key = `${dep.groupId}:${dep.artifactId}`;
|
|
346
|
+
const resolved = resolvedVersions.get(key);
|
|
347
|
+
if (resolved) {
|
|
348
|
+
return { ...dep, version: resolved };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return dep;
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
|
|
356
|
+
console.error("Failed to resolve Maven version ranges: " + error.message);
|
|
357
|
+
}
|
|
358
|
+
return dependencies;
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
292
364
|
}
|
|
293
365
|
const DEFAULT_MAVEN_DISCOVERY_IGNORE = [
|
|
294
366
|
'**/target/**',
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export default class Javascript_npm extends Base_javascript {
|
|
2
2
|
_listCmdArgs(includeTransitive: any): string[];
|
|
3
|
-
_buildDependencyTree(includeTransitive: any, opts?: {}):
|
|
3
|
+
_buildDependencyTree(includeTransitive: any, opts?: {}): Object | {
|
|
4
|
+
name: any;
|
|
5
|
+
version: any;
|
|
6
|
+
dependencies: any;
|
|
7
|
+
optionalDependencies: any;
|
|
8
|
+
};
|
|
4
9
|
}
|
|
5
10
|
import Base_javascript from './base_javascript.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the last FROM instruction from a Dockerfile using tree-sitter to extract the base image reference.
|
|
3
|
+
* In multi-stage builds, the last FROM represents the final stage.
|
|
4
|
+
* @param {string} manifestContent the content of the Dockerfile
|
|
5
|
+
* @returns {Promise<string>} the image reference from the last FROM instruction
|
|
6
|
+
* @throws {Error} when no FROM instruction is found or when ARG substitution is used
|
|
7
|
+
*/
|
|
8
|
+
export function parseFromImage(manifestContent: string): Promise<string>;
|
|
9
|
+
declare namespace _default {
|
|
10
|
+
export { isSupported };
|
|
11
|
+
export { validateLockFile };
|
|
12
|
+
export { provideComponent };
|
|
13
|
+
export { provideStack };
|
|
14
|
+
export { readLicenseFromManifest };
|
|
15
|
+
export function packageManagerName(): string;
|
|
16
|
+
}
|
|
17
|
+
export default _default;
|
|
18
|
+
export type Provided = any;
|
|
19
|
+
/**
|
|
20
|
+
* Check if the given manifest name is a Dockerfile or Containerfile.
|
|
21
|
+
* Supports dot-suffixed variants such as Dockerfile.dev or Containerfile.prod.
|
|
22
|
+
* @param {string} manifestName the manifest file name to check
|
|
23
|
+
* @returns {boolean} true if the manifest is a Dockerfile or Containerfile
|
|
24
|
+
*/
|
|
25
|
+
declare function isSupported(manifestName: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Dockerfiles have no lock file, so validation always passes.
|
|
28
|
+
* @returns {boolean} always true
|
|
29
|
+
*/
|
|
30
|
+
declare function validateLockFile(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Provide content and content type for Dockerfile component analysis.
|
|
33
|
+
* @param {string} manifest path to the Dockerfile
|
|
34
|
+
* @param {{}} [opts={}] optional various options to pass along the application
|
|
35
|
+
* @returns {Promise<Provided>}
|
|
36
|
+
*/
|
|
37
|
+
declare function provideComponent(manifest: string, opts?: {}): Promise<Provided>;
|
|
38
|
+
/**
|
|
39
|
+
* Provide content and content type for Dockerfile stack analysis.
|
|
40
|
+
* @param {string} manifest path to the Dockerfile
|
|
41
|
+
* @param {{}} [opts={}] optional various options to pass along the application
|
|
42
|
+
* @returns {Promise<Provided>}
|
|
43
|
+
*/
|
|
44
|
+
declare function provideStack(manifest: string, opts?: {}): Promise<Provided>;
|
|
45
|
+
/**
|
|
46
|
+
* Dockerfiles contain no license information.
|
|
47
|
+
* @returns {null} always null
|
|
48
|
+
*/
|
|
49
|
+
declare function readLicenseFromManifest(): null;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { generateImageSBOM, parseImageRef } from '../oci_image/utils.js';
|
|
3
|
+
import { getFromQuery, getParser } from './containerfile_parser.js';
|
|
4
|
+
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest, packageManagerName() { return 'oci'; } };
|
|
5
|
+
/** @typedef {import('../provider').Provider} */
|
|
6
|
+
/** @typedef {import('../provider').Provided} Provided */
|
|
7
|
+
/**
|
|
8
|
+
* @type {string} ecosystem identifier for OCI image packages
|
|
9
|
+
* @private
|
|
10
|
+
*/
|
|
11
|
+
const ecosystem = 'oci';
|
|
12
|
+
/**
|
|
13
|
+
* Check if the given manifest name is a Dockerfile or Containerfile.
|
|
14
|
+
* Supports dot-suffixed variants such as Dockerfile.dev or Containerfile.prod.
|
|
15
|
+
* @param {string} manifestName the manifest file name to check
|
|
16
|
+
* @returns {boolean} true if the manifest is a Dockerfile or Containerfile
|
|
17
|
+
*/
|
|
18
|
+
function isSupported(manifestName) {
|
|
19
|
+
return /^(Dockerfile|Containerfile)(\..+)?$/.test(manifestName);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Dockerfiles have no lock file, so validation always passes.
|
|
23
|
+
* @returns {boolean} always true
|
|
24
|
+
*/
|
|
25
|
+
function validateLockFile() { return true; }
|
|
26
|
+
/**
|
|
27
|
+
* Check whether a syntax node contains any expansion (variable substitution) children.
|
|
28
|
+
* @param {import('web-tree-sitter').SyntaxNode} node
|
|
29
|
+
* @returns {boolean}
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
function containsExpansion(node) {
|
|
33
|
+
if (node.type === 'expansion') {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
37
|
+
if (containsExpansion(node.child(i))) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse the last FROM instruction from a Dockerfile using tree-sitter to extract the base image reference.
|
|
45
|
+
* In multi-stage builds, the last FROM represents the final stage.
|
|
46
|
+
* @param {string} manifestContent the content of the Dockerfile
|
|
47
|
+
* @returns {Promise<string>} the image reference from the last FROM instruction
|
|
48
|
+
* @throws {Error} when no FROM instruction is found or when ARG substitution is used
|
|
49
|
+
*/
|
|
50
|
+
export async function parseFromImage(manifestContent) {
|
|
51
|
+
const [parser, fromQuery] = await Promise.all([getParser(), getFromQuery()]);
|
|
52
|
+
const tree = parser.parse(manifestContent);
|
|
53
|
+
const matches = fromQuery.matches(tree.rootNode);
|
|
54
|
+
if (matches.length === 0) {
|
|
55
|
+
throw new Error('No FROM line found in Dockerfile');
|
|
56
|
+
}
|
|
57
|
+
const lastMatch = matches[matches.length - 1];
|
|
58
|
+
const imageSpec = lastMatch.captures.find(c => c.name === 'image').node;
|
|
59
|
+
if (containsExpansion(imageSpec)) {
|
|
60
|
+
throw new Error('Dockerfile uses ARG substitution in FROM line — cannot resolve variable references');
|
|
61
|
+
}
|
|
62
|
+
return imageSpec.text;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generate an image SBOM from a Dockerfile manifest using syft.
|
|
66
|
+
* @param {string} manifest path to the Dockerfile
|
|
67
|
+
* @param {{}} [opts={}] optional various options to pass along the application
|
|
68
|
+
* @returns {Promise<{ecosystem: string, content: string, contentType: string}>}
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
async function getImageSBOM(manifest, opts = {}) {
|
|
72
|
+
const manifestContent = fs.readFileSync(manifest, 'utf-8');
|
|
73
|
+
const image = await parseFromImage(manifestContent);
|
|
74
|
+
const imageRef = parseImageRef(image, opts);
|
|
75
|
+
const sbom = generateImageSBOM(imageRef, opts);
|
|
76
|
+
return {
|
|
77
|
+
ecosystem,
|
|
78
|
+
content: JSON.stringify(sbom),
|
|
79
|
+
contentType: 'application/vnd.cyclonedx+json'
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Provide content and content type for Dockerfile component analysis.
|
|
84
|
+
* @param {string} manifest path to the Dockerfile
|
|
85
|
+
* @param {{}} [opts={}] optional various options to pass along the application
|
|
86
|
+
* @returns {Promise<Provided>}
|
|
87
|
+
*/
|
|
88
|
+
async function provideComponent(manifest, opts = {}) {
|
|
89
|
+
return getImageSBOM(manifest, opts);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Provide content and content type for Dockerfile stack analysis.
|
|
93
|
+
* @param {string} manifest path to the Dockerfile
|
|
94
|
+
* @param {{}} [opts={}] optional various options to pass along the application
|
|
95
|
+
* @returns {Promise<Provided>}
|
|
96
|
+
*/
|
|
97
|
+
async function provideStack(manifest, opts = {}) {
|
|
98
|
+
return getImageSBOM(manifest, opts);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Dockerfiles contain no license information.
|
|
102
|
+
* @returns {null} always null
|
|
103
|
+
*/
|
|
104
|
+
function readLicenseFromManifest() { return null; }
|
|
@@ -29,13 +29,13 @@ export default class Yarn_berry_processor extends Yarn_processor {
|
|
|
29
29
|
* @param {Object} depTree - The dependency tree object
|
|
30
30
|
* @returns {Map<string, PackageURL>} Map of dependency names to their PackageURL objects
|
|
31
31
|
*/
|
|
32
|
-
getRootDependencies(depTree:
|
|
32
|
+
getRootDependencies(depTree: Object): Map<string, PackageURL>;
|
|
33
33
|
/**
|
|
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
37
|
*/
|
|
38
|
-
addDependenciesToSbom(sbom: Sbom, depTree:
|
|
38
|
+
addDependenciesToSbom(sbom: Sbom, depTree: Object): void;
|
|
39
39
|
#private;
|
|
40
40
|
}
|
|
41
41
|
import Yarn_processor from "./yarn_processor.js";
|
|
@@ -25,13 +25,13 @@ export default class Yarn_classic_processor extends Yarn_processor {
|
|
|
25
25
|
* @param {Object} depTree - The dependency tree object
|
|
26
26
|
* @returns {Map<string, PackageURL>} Map of dependency names to their PackageURL objects
|
|
27
27
|
*/
|
|
28
|
-
getRootDependencies(depTree:
|
|
28
|
+
getRootDependencies(depTree: Object): Map<string, PackageURL>;
|
|
29
29
|
/**
|
|
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
33
|
*/
|
|
34
|
-
addDependenciesToSbom(sbom: Sbom, depTree:
|
|
34
|
+
addDependenciesToSbom(sbom: Sbom, depTree: Object): void;
|
|
35
35
|
#private;
|
|
36
36
|
}
|
|
37
37
|
import Yarn_processor from "./yarn_processor.js";
|
|
@@ -19,6 +19,53 @@ function getPipShowOutput(depNames) {
|
|
|
19
19
|
throw new Error('fail invoking \'pip show\' to fetch metadata for all installed packages in environment', { cause: error });
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Get pip inspect output from env var override or by invoking pip inspect.
|
|
24
|
+
* Returns null if pip inspect is unavailable (pip < 22.2) or if freeze/show
|
|
25
|
+
* env vars are set without an inspect override (to avoid inconsistent data).
|
|
26
|
+
* @return {string|null} pip inspect JSON output, or null on failure
|
|
27
|
+
*/
|
|
28
|
+
function getPipInspectOutput() {
|
|
29
|
+
if (environmentVariableIsPopulated("TRUSTIFY_DA_PIP_INSPECT")) {
|
|
30
|
+
return new Buffer.from(process.env["TRUSTIFY_DA_PIP_INSPECT"], 'base64').toString('ascii');
|
|
31
|
+
}
|
|
32
|
+
if (environmentVariableIsPopulated("TRUSTIFY_DA_PIP_FREEZE") || environmentVariableIsPopulated("TRUSTIFY_DA_PIP_SHOW")) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return invokeCommand(this.pathToPipBin, ['inspect']).toString();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.warn('pip inspect is not available (requires pip 22.2+), SBOM will be generated without hashes');
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse pip inspect JSON output and build a hash lookup map.
|
|
45
|
+
* @param {string|null} inspectOutput - raw pip inspect JSON string
|
|
46
|
+
* @return {Map<string, Array<{alg: string, content: string}>>} map of lowercase package name to hashes
|
|
47
|
+
*/
|
|
48
|
+
function parsePipInspectHashes(inspectOutput) {
|
|
49
|
+
if (!inspectOutput) {
|
|
50
|
+
return new Map();
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
let inspectData = JSON.parse(inspectOutput);
|
|
54
|
+
let hashMap = new Map();
|
|
55
|
+
for (let pkg of (inspectData.installed || [])) {
|
|
56
|
+
let name = pkg.metadata?.name;
|
|
57
|
+
let sha256 = pkg.download_info?.archive_info?.hashes?.sha256;
|
|
58
|
+
if (name && sha256) {
|
|
59
|
+
hashMap.set(name.toLowerCase(), [{ alg: "SHA-256", content: sha256 }]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return hashMap;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.warn('Failed to parse pip inspect output, SBOM will be generated without hashes');
|
|
66
|
+
return new Map();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
22
69
|
/** @typedef {{name: string, version: string, dependencies: DependencyEntry[], hashes?: Array<{alg: string, content: string}>}} DependencyEntry */
|
|
23
70
|
export default class Python_controller {
|
|
24
71
|
pythonEnvDir;
|
|
@@ -225,6 +272,8 @@ export default class Python_controller {
|
|
|
225
272
|
CachedEnvironmentDeps[packageName.replace("_", "-")] = pipDepTreeEntryForCache;
|
|
226
273
|
});
|
|
227
274
|
}
|
|
275
|
+
const inspectOutput = getPipInspectOutput.call(this);
|
|
276
|
+
const hashMap = parsePipInspectHashes(inspectOutput);
|
|
228
277
|
parsedRequirements.forEach(({ name: depName, version: manifestVersion, hasMarker }) => {
|
|
229
278
|
if (hasMarker && CachedEnvironmentDeps[depName.toLowerCase()] === undefined) {
|
|
230
279
|
return;
|
|
@@ -247,7 +296,7 @@ export default class Python_controller {
|
|
|
247
296
|
}
|
|
248
297
|
let path = [];
|
|
249
298
|
path.push(depName.toLowerCase());
|
|
250
|
-
bringAllDependencies(dependencies, depName, CachedEnvironmentDeps, includeTransitive, path, usePipDepTree);
|
|
299
|
+
bringAllDependencies(dependencies, depName, CachedEnvironmentDeps, includeTransitive, path, usePipDepTree, hashMap);
|
|
251
300
|
});
|
|
252
301
|
dependencies.sort((dep1, dep2) => {
|
|
253
302
|
const DEP1 = dep1.name.toLowerCase();
|
|
@@ -327,8 +376,9 @@ function getDepsList(record) {
|
|
|
327
376
|
* @param includeTransitive
|
|
328
377
|
* @param usePipDepTree
|
|
329
378
|
* @param {[string]}path array representing the path of the current branch in dependency tree, starting with a root dependency - that is - a given dependency in requirements.txt
|
|
379
|
+
* @param {Map<string, Array<{alg: string, content: string}>>} hashMap - map of lowercase package name to hashes
|
|
330
380
|
*/
|
|
331
|
-
function bringAllDependencies(dependencies, dependencyName, cachedEnvironmentDeps, includeTransitive, path, usePipDepTree) {
|
|
381
|
+
function bringAllDependencies(dependencies, dependencyName, cachedEnvironmentDeps, includeTransitive, path, usePipDepTree, hashMap) {
|
|
332
382
|
if (dependencyName?.trim() === "") {
|
|
333
383
|
return;
|
|
334
384
|
}
|
|
@@ -350,7 +400,11 @@ function bringAllDependencies(dependencies, dependencyName, cachedEnvironmentDep
|
|
|
350
400
|
directDeps = record.dependencies;
|
|
351
401
|
}
|
|
352
402
|
let targetDeps = [];
|
|
403
|
+
let hashes = hashMap?.get(depName.toLowerCase());
|
|
353
404
|
let entry = { "name": depName, "version": version, "dependencies": [] };
|
|
405
|
+
if (hashes) {
|
|
406
|
+
entry.hashes = hashes;
|
|
407
|
+
}
|
|
354
408
|
dependencies.push(entry);
|
|
355
409
|
directDeps.forEach((dep) => {
|
|
356
410
|
let depArray = [];
|
|
@@ -360,7 +414,7 @@ function bringAllDependencies(dependencies, dependencyName, cachedEnvironmentDep
|
|
|
360
414
|
depArray.push(dep.toLowerCase());
|
|
361
415
|
if (includeTransitive) {
|
|
362
416
|
// send to recurrsion the array of all deps in path + the current dependency name which is not on the path.
|
|
363
|
-
bringAllDependencies(targetDeps, dep, cachedEnvironmentDeps, includeTransitive, path.concat(depArray), usePipDepTree);
|
|
417
|
+
bringAllDependencies(targetDeps, dep, cachedEnvironmentDeps, includeTransitive, path.concat(depArray), usePipDepTree, hashMap);
|
|
364
418
|
}
|
|
365
419
|
}
|
|
366
420
|
// sort ra
|
|
@@ -5,7 +5,7 @@ import Sbom from '../sbom.js';
|
|
|
5
5
|
import { environmentVariableIsPopulated, getCustom, getCustomPath, invokeCommand } from "../tools.js";
|
|
6
6
|
import Python_controller from './python_controller.js';
|
|
7
7
|
import { getParser, getIgnoreQuery, getPinnedVersionQuery } from './requirements_parser.js';
|
|
8
|
-
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest };
|
|
8
|
+
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest, packageManagerName() { return 'pip'; } };
|
|
9
9
|
/** @typedef {{name: string, version: string, dependencies: DependencyEntry[], hashes?: Array<{alg: string, content: string}>}} DependencyEntry */
|
|
10
10
|
/**
|
|
11
11
|
* @type {string} ecosystem for python-pip is 'pip'
|