@trustify-da/trustify-da-javascript-client 0.3.0-ea.6549d2a → 0.3.0-ea.68905d3
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 +368 -14
- package/dist/package.json +39 -26
- package/dist/src/analysis.d.ts +28 -5
- package/dist/src/analysis.js +78 -13
- package/dist/src/batch_opts.d.ts +24 -0
- package/dist/src/batch_opts.js +35 -0
- package/dist/src/cli.js +287 -5
- package/dist/src/cyclone_dx_sbom.d.ts +27 -1
- package/dist/src/cyclone_dx_sbom.js +84 -10
- package/dist/src/index.d.ts +145 -8
- package/dist/src/index.js +362 -28
- package/dist/src/license/index.d.ts +5 -5
- package/dist/src/license/index.js +8 -5
- package/dist/src/license/license_utils.d.ts +40 -0
- package/dist/src/license/license_utils.js +137 -0
- package/dist/src/license/licenses_api.d.ts +2 -2
- package/dist/src/license/licenses_api.js +9 -2
- package/dist/src/license/project_license.d.ts +1 -6
- package/dist/src/license/project_license.js +4 -81
- package/dist/src/oci_image/images.d.ts +1 -1
- package/dist/src/oci_image/images.js +15 -1
- package/dist/src/oci_image/utils.d.ts +5 -5
- package/dist/src/oci_image/utils.js +11 -2
- package/dist/src/package_version.d.ts +8 -0
- package/dist/src/package_version.js +31 -0
- package/dist/src/provider.d.ts +8 -4
- package/dist/src/provider.js +18 -5
- package/dist/src/providers/base_java.d.ts +67 -12
- package/dist/src/providers/base_java.js +82 -61
- package/dist/src/providers/base_javascript.d.ts +91 -10
- package/dist/src/providers/base_javascript.js +220 -28
- package/dist/src/providers/base_pyproject.d.ts +158 -0
- package/dist/src/providers/base_pyproject.js +322 -0
- package/dist/src/providers/containerfile_parser.d.ts +5 -0
- package/dist/src/providers/containerfile_parser.js +20 -0
- package/dist/src/providers/golang_gomodules.d.ts +23 -13
- package/dist/src/providers/golang_gomodules.js +210 -121
- package/dist/src/providers/gomod_parser.d.ts +4 -0
- package/dist/src/providers/gomod_parser.js +16 -0
- package/dist/src/providers/java_gradle.d.ts +67 -0
- package/dist/src/providers/java_gradle.js +314 -21
- 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 +38 -10
- package/dist/src/providers/java_maven.js +279 -20
- package/dist/src/providers/javascript_bun.d.ts +22 -0
- package/dist/src/providers/javascript_bun.js +141 -0
- package/dist/src/providers/javascript_npm.d.ts +19 -0
- package/dist/src/providers/javascript_npm.js +61 -1
- package/dist/src/providers/javascript_pnpm.d.ts +14 -1
- package/dist/src/providers/javascript_pnpm.js +55 -5
- package/dist/src/providers/javascript_yarn.d.ts +12 -0
- package/dist/src/providers/javascript_yarn.js +75 -2
- package/dist/src/providers/manifest.d.ts +2 -0
- package/dist/src/providers/manifest.js +28 -7
- package/dist/src/providers/marker_evaluator.d.ts +14 -0
- package/dist/src/providers/marker_evaluator.js +191 -0
- package/dist/src/providers/oci_dockerfile.d.ts +51 -0
- package/dist/src/providers/oci_dockerfile.js +177 -0
- package/dist/src/providers/processors/yarn_berry_processor.d.ts +6 -2
- package/dist/src/providers/processors/yarn_berry_processor.js +91 -7
- package/dist/src/providers/processors/yarn_classic_processor.d.ts +6 -2
- package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
- package/dist/src/providers/python_controller.d.ts +5 -1
- package/dist/src/providers/python_controller.js +69 -8
- package/dist/src/providers/python_pip.d.ts +5 -0
- package/dist/src/providers/python_pip.js +8 -7
- package/dist/src/providers/python_pip_pyproject.d.ts +61 -0
- package/dist/src/providers/python_pip_pyproject.js +146 -0
- package/dist/src/providers/python_poetry.d.ts +98 -0
- package/dist/src/providers/python_poetry.js +286 -0
- package/dist/src/providers/python_uv.d.ts +46 -0
- package/dist/src/providers/python_uv.js +269 -0
- package/dist/src/providers/requirements_parser.js +5 -4
- package/dist/src/providers/rust_cargo.d.ts +56 -0
- package/dist/src/providers/rust_cargo.js +682 -0
- package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
- package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
- package/dist/src/providers/tree-sitter-requirements.wasm +0 -0
- package/dist/src/remediate.d.ts +117 -0
- package/dist/src/remediate.js +249 -0
- package/dist/src/remediation.d.ts +57 -0
- package/dist/src/remediation.js +436 -0
- package/dist/src/remediation_report.d.ts +40 -0
- package/dist/src/remediation_report.js +159 -0
- package/dist/src/sbom.d.ts +25 -1
- package/dist/src/sbom.js +23 -2
- package/dist/src/tools.d.ts +28 -2
- package/dist/src/tools.js +82 -1
- package/dist/src/updaters/maven_updater.d.ts +58 -0
- package/dist/src/updaters/maven_updater.js +345 -0
- package/dist/src/updaters/toml_updater.d.ts +62 -0
- package/dist/src/updaters/toml_updater.js +276 -0
- package/dist/src/workspace.d.ts +70 -0
- package/dist/src/workspace.js +256 -0
- package/package.json +40 -27
- package/dist/src/license/compatibility.d.ts +0 -18
- package/dist/src/license/compatibility.js +0 -45
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { EOL } from "os";
|
|
4
3
|
import { PackageURL } from 'packageurl-js';
|
|
4
|
+
import { readLicenseFile } from '../license/license_utils.js';
|
|
5
5
|
import Sbom from '../sbom.js';
|
|
6
6
|
import { getCustom, getCustomPath, invokeCommand } from "../tools.js";
|
|
7
|
-
|
|
7
|
+
import { filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore } from '../workspace.js';
|
|
8
|
+
import { getParser, getRequireQuery } from './gomod_parser.js';
|
|
9
|
+
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest, packageManagerName() { return 'go'; } };
|
|
8
10
|
/** @typedef {import('../provider').Provider} */
|
|
9
11
|
/** @typedef {import('../provider').Provided} Provided */
|
|
10
12
|
/** @typedef {{name: string, version: string}} Package */
|
|
@@ -16,46 +18,46 @@ export default { isSupported, validateLockFile, provideComponent, provideStack,
|
|
|
16
18
|
const ecosystem = 'golang';
|
|
17
19
|
const defaultMainModuleVersion = "v0.0.0";
|
|
18
20
|
/**
|
|
19
|
-
* @param {string} manifestName
|
|
20
|
-
* @returns {boolean}
|
|
21
|
+
* @param {string} manifestName the subject manifest name-type
|
|
22
|
+
* @returns {boolean} return true if `pom.xml` is the manifest name-type
|
|
21
23
|
*/
|
|
22
24
|
function isSupported(manifestName) {
|
|
23
25
|
return 'go.mod' === manifestName;
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
28
|
* Go modules have no standard license field in go.mod
|
|
27
|
-
* @param {string} manifestPath
|
|
29
|
+
* @param {string} manifestPath path to go.mod
|
|
28
30
|
* @returns {string|null}
|
|
29
31
|
*/
|
|
30
32
|
// eslint-disable-next-line no-unused-vars
|
|
31
|
-
function readLicenseFromManifest(manifestPath) { return
|
|
33
|
+
function readLicenseFromManifest(manifestPath) { return readLicenseFile(manifestPath); }
|
|
32
34
|
/**
|
|
33
|
-
* @param {string} manifestDir
|
|
35
|
+
* @param {string} manifestDir the directory where the manifest lies
|
|
34
36
|
*/
|
|
35
37
|
function validateLockFile() { return true; }
|
|
36
38
|
/**
|
|
37
39
|
* Provide content and content type for maven-maven stack analysis.
|
|
38
|
-
* @param {string} manifest
|
|
39
|
-
* @param {{}} [opts={}]
|
|
40
|
-
* @returns {Provided}
|
|
40
|
+
* @param {string} manifest the manifest path or name
|
|
41
|
+
* @param {{}} [opts={}] optional various options to pass along the application
|
|
42
|
+
* @returns {Promise<Provided>}
|
|
41
43
|
*/
|
|
42
|
-
function provideStack(manifest, opts = {}) {
|
|
44
|
+
async function provideStack(manifest, opts = {}) {
|
|
43
45
|
return {
|
|
44
46
|
ecosystem,
|
|
45
|
-
content: getSBOM(manifest, opts, true),
|
|
47
|
+
content: await getSBOM(manifest, opts, true),
|
|
46
48
|
contentType: 'application/vnd.cyclonedx+json'
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* Provide content and content type for maven-maven component analysis.
|
|
51
|
-
* @param {string} manifest
|
|
52
|
-
* @param {{}} [opts={}]
|
|
53
|
-
* @returns {Provided}
|
|
53
|
+
* @param {string} manifest path to go.mod for component report
|
|
54
|
+
* @param {{}} [opts={}] optional various options to pass along the application
|
|
55
|
+
* @returns {Promise<Provided>}
|
|
54
56
|
*/
|
|
55
|
-
function provideComponent(manifest, opts = {}) {
|
|
57
|
+
async function provideComponent(manifest, opts = {}) {
|
|
56
58
|
return {
|
|
57
59
|
ecosystem,
|
|
58
|
-
content: getSBOM(manifest, opts, false),
|
|
60
|
+
content: await getSBOM(manifest, opts, false),
|
|
59
61
|
contentType: 'application/vnd.cyclonedx+json'
|
|
60
62
|
};
|
|
61
63
|
}
|
|
@@ -76,50 +78,53 @@ function getChildVertexFromEdge(edge) {
|
|
|
76
78
|
return edge.split(" ")[1];
|
|
77
79
|
}
|
|
78
80
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
81
|
+
* Check whether a require_spec has a valid ignore marker.
|
|
82
|
+
* Supports both the legacy `exhortignore` marker and the new `trustify-da-ignore` marker.
|
|
83
|
+
* For direct dependencies: `//exhortignore` or `// trustify-da-ignore`
|
|
84
|
+
* For indirect dependencies: `// indirect; exhortignore` or `// indirect; trustify-da-ignore` (semicolon-separated)
|
|
85
|
+
* @param {import('web-tree-sitter').SyntaxNode} specNode
|
|
86
|
+
* @return {boolean}
|
|
82
87
|
*/
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
function hasExhortIgnore(specNode) {
|
|
89
|
+
// Ideally this would be the following tree-sitter query instead, but for some
|
|
90
|
+
// reason it throws an error here but not in the playground.
|
|
91
|
+
// (require_spec) ((module_path) @path (version) (comment) @comment (#match? @comment "^//.*exhortignore"))
|
|
92
|
+
// QueryError: Bad pattern structure at offset 53: '(comment) @comment (#match? @comment "^//.*exhortignore")) @spec'...
|
|
93
|
+
let comments = specNode.children.filter(c => c.type === 'comment');
|
|
94
|
+
for (let comment of comments) {
|
|
95
|
+
let text = comment.text;
|
|
96
|
+
if (/^\/\/\s*indirect;\s*(exhortignore|trustify-da-ignore)(?![\w-])/.test(text)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (/^\/\/\s*(exhortignore|trustify-da-ignore)(?![\w-])/.test(text)) {
|
|
100
|
+
return true;
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* extract package name from go.mod line that contains exhortignore comment.
|
|
101
|
-
* @param line a row contains exhortignore as part of a comment
|
|
102
|
-
* @return {string} the full package name + group/namespace + version
|
|
103
|
-
* @private
|
|
104
|
-
*/
|
|
105
|
-
function extractPackageName(line) {
|
|
106
|
-
let trimmedRow = line.trim();
|
|
107
|
-
let firstRemarkNotationOccurrence = trimmedRow.indexOf("//");
|
|
108
|
-
return trimmedRow.substring(0, firstRemarkNotationOccurrence).trim();
|
|
103
|
+
return false;
|
|
109
104
|
}
|
|
110
105
|
/**
|
|
111
106
|
*
|
|
112
|
-
* @param {string
|
|
113
|
-
* @
|
|
107
|
+
* @param {string} manifestContent go.mod file contents
|
|
108
|
+
* @param {import('web-tree-sitter').Parser} parser
|
|
109
|
+
* @param {import('web-tree-sitter').Query} requireQuery
|
|
110
|
+
* @return {PackageURL[]} list of ignored dependencies
|
|
114
111
|
*/
|
|
115
|
-
function getIgnoredDeps(
|
|
116
|
-
let
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
function getIgnoredDeps(manifestContent, parser, requireQuery) {
|
|
113
|
+
let tree = parser.parse(manifestContent);
|
|
114
|
+
return requireQuery.matches(tree.rootNode)
|
|
115
|
+
.filter(match => {
|
|
116
|
+
let specNode = match.captures.find(c => c.name === 'spec').node;
|
|
117
|
+
return hasExhortIgnore(specNode);
|
|
118
|
+
})
|
|
119
|
+
.map(match => {
|
|
120
|
+
let name = match.captures.find(c => c.name === 'name').node.text;
|
|
121
|
+
let version = match.captures.find(c => c.name === 'version').node.text;
|
|
122
|
+
return toPurl(`${name} ${version}`, /[ ]{1,3}/);
|
|
123
|
+
});
|
|
119
124
|
}
|
|
120
125
|
/**
|
|
121
126
|
*
|
|
122
|
-
* @param {[
|
|
127
|
+
* @param {PackageURL[]} allIgnoredDeps list of purls of all dependencies that should be ignored
|
|
123
128
|
* @param {PackageURL} purl object to be checked if needed to be ignored
|
|
124
129
|
* @return {boolean}
|
|
125
130
|
*/
|
|
@@ -138,59 +143,29 @@ function enforceRemovingIgnoredDepsInCaseOfAutomaticVersionUpdate(ignoredDeps, s
|
|
|
138
143
|
}
|
|
139
144
|
/**
|
|
140
145
|
*
|
|
141
|
-
* @param {
|
|
142
|
-
* @param {
|
|
143
|
-
* @
|
|
146
|
+
* @param {string} manifestContent go.mod file contents
|
|
147
|
+
* @param {import('web-tree-sitter').Parser} parser
|
|
148
|
+
* @param {import('web-tree-sitter').Query} requireQuery
|
|
149
|
+
* @return {string[]} all dependencies from go.mod file as "name version" strings
|
|
144
150
|
*/
|
|
145
|
-
function collectAllDepsFromManifest(
|
|
146
|
-
let
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
while (requirePositionObject.index > -1) {
|
|
153
|
-
let depsInsideRequirementsBlock = currentSegmentOfGoMod.substring(requirePositionObject.index + requirePositionObject.startingOffeset).trim();
|
|
154
|
-
let endOfBlockIndex = depsInsideRequirementsBlock.indexOf(")");
|
|
155
|
-
let currentIndex = 0;
|
|
156
|
-
while (currentIndex < endOfBlockIndex) {
|
|
157
|
-
let endOfLinePosition = depsInsideRequirementsBlock.indexOf(EOL, currentIndex);
|
|
158
|
-
let dependency = depsInsideRequirementsBlock.substring(currentIndex, endOfLinePosition);
|
|
159
|
-
result.push(dependency.trim());
|
|
160
|
-
currentIndex = endOfLinePosition + 1;
|
|
161
|
-
}
|
|
162
|
-
currentSegmentOfGoMod = currentSegmentOfGoMod.substring(endOfBlockIndex + 1).trim();
|
|
163
|
-
requirePositionObject = decideRequireBlockIndex(currentSegmentOfGoMod);
|
|
164
|
-
}
|
|
165
|
-
function decideRequireBlockIndex(goMod) {
|
|
166
|
-
let object = {};
|
|
167
|
-
let index = goMod.indexOf("require(");
|
|
168
|
-
object.startingOffeset = "require(".length;
|
|
169
|
-
if (index === -1) {
|
|
170
|
-
index = goMod.indexOf("require (");
|
|
171
|
-
object.startingOffeset = "require (".length;
|
|
172
|
-
if (index === -1) {
|
|
173
|
-
index = goMod.indexOf("require (");
|
|
174
|
-
object.startingOffeset = "require (".length;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
object.index = index;
|
|
178
|
-
return object;
|
|
179
|
-
}
|
|
180
|
-
return result;
|
|
151
|
+
function collectAllDepsFromManifest(manifestContent, parser, requireQuery) {
|
|
152
|
+
let tree = parser.parse(manifestContent);
|
|
153
|
+
return requireQuery.matches(tree.rootNode).map(match => {
|
|
154
|
+
let name = match.captures.find(c => c.name === 'name').node.text;
|
|
155
|
+
let version = match.captures.find(c => c.name === 'version').node.text;
|
|
156
|
+
return `${name} ${version}`;
|
|
157
|
+
});
|
|
181
158
|
}
|
|
182
159
|
/**
|
|
183
160
|
*
|
|
184
161
|
* @param {string} rootElementName the rootElementName element of go mod graph, to compare only direct deps from go mod graph against go.mod manifest
|
|
185
|
-
* @param{[
|
|
186
|
-
* @param {string
|
|
162
|
+
* @param {string[]} goModGraphOutputRows the goModGraphOutputRows from go mod graph' output
|
|
163
|
+
* @param {string} manifestContent go.mod file contents
|
|
187
164
|
* @private
|
|
188
165
|
*/
|
|
189
|
-
function performManifestVersionsCheck(rootElementName, goModGraphOutputRows,
|
|
190
|
-
let goMod = fs.readFileSync(manifest).toString().trim();
|
|
191
|
-
let lines = goMod.split(getLineSeparatorGolang());
|
|
166
|
+
function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, manifestContent, parser, requireQuery) {
|
|
192
167
|
let comparisonLines = goModGraphOutputRows.filter((line) => line.startsWith(rootElementName)).map((line) => getChildVertexFromEdge(line));
|
|
193
|
-
let manifestDeps = collectAllDepsFromManifest(
|
|
168
|
+
let manifestDeps = collectAllDepsFromManifest(manifestContent, parser, requireQuery);
|
|
194
169
|
try {
|
|
195
170
|
comparisonLines.forEach((dependency) => {
|
|
196
171
|
let parts = dependency.split("@");
|
|
@@ -202,7 +177,7 @@ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, man
|
|
|
202
177
|
let currentVersion = components[1];
|
|
203
178
|
if (currentDepName === depName) {
|
|
204
179
|
if (currentVersion !== version) {
|
|
205
|
-
throw new Error(`
|
|
180
|
+
throw new Error(`version mismatch for dependency "${depName}", manifest version=${currentVersion}, installed version=${version}, if you want to allow version mismatch for analysis between installed and requested packages, set environment variable/setting MATCH_MANIFEST_VERSIONS=false`);
|
|
206
181
|
}
|
|
207
182
|
}
|
|
208
183
|
});
|
|
@@ -213,15 +188,45 @@ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, man
|
|
|
213
188
|
throw error;
|
|
214
189
|
}
|
|
215
190
|
}
|
|
191
|
+
/** Parses go.sum to build a hash map of module@version to CycloneDX hashes. */
|
|
192
|
+
function parseGoSumHashes(manifestDir) {
|
|
193
|
+
let goSumPath = path.join(manifestDir, 'go.sum');
|
|
194
|
+
if (!fs.existsSync(goSumPath)) {
|
|
195
|
+
return new Map();
|
|
196
|
+
}
|
|
197
|
+
let hashMap = new Map();
|
|
198
|
+
let content = fs.readFileSync(goSumPath, 'utf-8');
|
|
199
|
+
for (let line of content.split('\n')) {
|
|
200
|
+
line = line.trim();
|
|
201
|
+
if (!line) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
let parts = line.split(/\s+/);
|
|
205
|
+
if (parts.length < 3) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
let [mod, version, hash] = parts;
|
|
209
|
+
if (version.endsWith('/go.mod')) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (!hash.startsWith('h1:')) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
let base64Part = hash.slice(3);
|
|
216
|
+
let hexDigest = Buffer.from(base64Part, 'base64').toString('hex');
|
|
217
|
+
hashMap.set(`${mod}@${version}`, [{ alg: "SHA-256", content: hexDigest }]);
|
|
218
|
+
}
|
|
219
|
+
return hashMap;
|
|
220
|
+
}
|
|
216
221
|
/**
|
|
217
222
|
* Create SBOM json string for go Module.
|
|
218
223
|
* @param {string} manifest - path for go.mod
|
|
219
224
|
* @param {{}} [opts={}] - optional various options to pass along the application
|
|
220
225
|
* @param {boolean} includeTransitive - whether the sbom should contain transitive dependencies of the main module or not.
|
|
221
|
-
* @returns {string} the SBOM json content
|
|
226
|
+
* @returns {Promise<string>} the SBOM json content
|
|
222
227
|
* @private
|
|
223
228
|
*/
|
|
224
|
-
function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
229
|
+
async function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
225
230
|
// get custom goBin path
|
|
226
231
|
let goBin = getCustomPath('go', opts);
|
|
227
232
|
// verify goBin is accessible
|
|
@@ -247,15 +252,27 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
|
247
252
|
catch (error) {
|
|
248
253
|
throw new Error('failed to determine root module name', { cause: error });
|
|
249
254
|
}
|
|
250
|
-
let
|
|
255
|
+
let manifestContent = fs.readFileSync(manifest).toString();
|
|
256
|
+
let [parser, requireQuery] = await Promise.all([getParser(), getRequireQuery()]);
|
|
257
|
+
let ignoredDeps = getIgnoredDeps(manifestContent, parser, requireQuery);
|
|
251
258
|
let allIgnoredDeps = ignoredDeps.map((dep) => dep.toString());
|
|
252
259
|
let sbom = new Sbom();
|
|
253
260
|
let rows = goGraphOutput.split(getLineSeparatorGolang()).filter(line => !line.includes(' go@'));
|
|
254
|
-
let root =
|
|
261
|
+
let root = goModEditOutput['Module']['Path'];
|
|
262
|
+
// Build set of direct dependency paths from go mod edit -json
|
|
263
|
+
let directDepPaths = new Set();
|
|
264
|
+
if (goModEditOutput['Require']) {
|
|
265
|
+
goModEditOutput['Require'].forEach(req => {
|
|
266
|
+
if (!req['Indirect']) {
|
|
267
|
+
directDepPaths.add(req['Path']);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
255
271
|
let matchManifestVersions = getCustom("MATCH_MANIFEST_VERSIONS", "false", opts);
|
|
256
272
|
if (matchManifestVersions === "true") {
|
|
257
|
-
performManifestVersionsCheck(root, rows,
|
|
273
|
+
performManifestVersionsCheck(root, rows, manifestContent, parser, requireQuery);
|
|
258
274
|
}
|
|
275
|
+
let goSumHashes = parseGoSumHashes(manifestDir);
|
|
259
276
|
const mainModule = toPurl(root, "@");
|
|
260
277
|
const license = readLicenseFromManifest(manifest);
|
|
261
278
|
sbom.addRoot(mainModule, license);
|
|
@@ -272,8 +289,12 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
|
272
289
|
currentParent = getParentVertexFromEdge(row);
|
|
273
290
|
source = toPurl(currentParent, "@");
|
|
274
291
|
}
|
|
275
|
-
let
|
|
276
|
-
|
|
292
|
+
let child = getChildVertexFromEdge(row);
|
|
293
|
+
let target = toPurl(child, "@");
|
|
294
|
+
if (getParentVertexFromEdge(row) === root && !directDepPaths.has(getPackageName(child))) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
sbom.addDependency(source, target, undefined, goSumHashes.get(child));
|
|
277
298
|
});
|
|
278
299
|
// at the end, filter out all ignored dependencies including versions.
|
|
279
300
|
sbom.filterIgnoredDepsIncludingVersion(allIgnoredDeps);
|
|
@@ -282,20 +303,32 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
|
282
303
|
else {
|
|
283
304
|
let directDependencies = rows.filter(row => row.startsWith(root));
|
|
284
305
|
directDependencies.forEach(pair => {
|
|
285
|
-
let
|
|
286
|
-
let
|
|
287
|
-
if (dependencyNotIgnored(ignoredDeps,
|
|
288
|
-
|
|
306
|
+
let child = getChildVertexFromEdge(pair);
|
|
307
|
+
let target = toPurl(child, "@");
|
|
308
|
+
if (dependencyNotIgnored(ignoredDeps, target)) {
|
|
309
|
+
if (directDepPaths.has(getPackageName(child))) {
|
|
310
|
+
sbom.addDependency(mainModule, target, undefined, goSumHashes.get(child));
|
|
311
|
+
}
|
|
289
312
|
}
|
|
290
313
|
});
|
|
291
314
|
enforceRemovingIgnoredDepsInCaseOfAutomaticVersionUpdate(ignoredDeps, sbom);
|
|
292
315
|
}
|
|
316
|
+
// Backfill hashes for components that were only seen as dependency sources
|
|
317
|
+
for (const comp of sbom.sbomModel.components) {
|
|
318
|
+
if (!comp.hashes && comp.purl) {
|
|
319
|
+
let key = decodeURIComponent(comp.purl.replace('pkg:golang/', ''));
|
|
320
|
+
let hashes = goSumHashes.get(key);
|
|
321
|
+
if (hashes) {
|
|
322
|
+
comp.hashes = hashes;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
293
326
|
return sbom.getAsJsonString(opts);
|
|
294
327
|
}
|
|
295
328
|
/**
|
|
296
329
|
* Utility function for creating Purl String
|
|
297
330
|
|
|
298
|
-
* @param {string
|
|
331
|
+
* @param {string} dependency the name of the artifact, can include a namespace(group) or not - namespace/artifactName.
|
|
299
332
|
* @param {RegExp} delimiter delimiter between name of dependency and version
|
|
300
333
|
* @private
|
|
301
334
|
* @returns {PackageURL|null} PackageUrl Object ready to be used in SBOM
|
|
@@ -320,12 +353,12 @@ function toPurl(dependency, delimiter) {
|
|
|
320
353
|
}
|
|
321
354
|
return pkg;
|
|
322
355
|
}
|
|
323
|
-
/** This function gets rows from go mod graph
|
|
356
|
+
/** This function gets rows from go mod graph, and go.mod graph, and selecting for all
|
|
324
357
|
* packages the has more than one minor the final versions as selected by golang MVS algorithm.
|
|
325
|
-
* @param {[
|
|
358
|
+
* @param {string[]} rows all the rows from go modules dependency tree
|
|
326
359
|
* @param {string} manifestPath the path of the go.mod file
|
|
327
360
|
* @param {string} path to go binary
|
|
328
|
-
* @return {[
|
|
361
|
+
* @return {string[]} rows that contains final versions.
|
|
329
362
|
*/
|
|
330
363
|
function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
|
|
331
364
|
let manifestDir = path.dirname(manifestPath);
|
|
@@ -338,13 +371,7 @@ function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
|
|
|
338
371
|
catch (error) {
|
|
339
372
|
throw new Error('failed to list all modules', { cause: error });
|
|
340
373
|
}
|
|
341
|
-
let finalVersionModules =
|
|
342
|
-
finalVersionsForAllModules.split(getLineSeparatorGolang()).filter(string => string.trim() !== "")
|
|
343
|
-
.filter(string => string.trim().split(" ").length === 2)
|
|
344
|
-
.forEach((dependency) => {
|
|
345
|
-
let dep = dependency.split(" ");
|
|
346
|
-
finalVersionModules[dep[0]] = dep[1];
|
|
347
|
-
});
|
|
374
|
+
let finalVersionModules = parseModuleVersions(finalVersionsForAllModules);
|
|
348
375
|
let finalVersionModulesArray = new Array();
|
|
349
376
|
rows.filter(string => string.trim() !== "").forEach(module => {
|
|
350
377
|
let child = getChildVertexFromEdge(module);
|
|
@@ -380,7 +407,7 @@ function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
|
|
|
380
407
|
/**
|
|
381
408
|
*
|
|
382
409
|
* @param {string} fullPackage - full package with its name and version
|
|
383
|
-
* @return
|
|
410
|
+
* @return {string} package name only
|
|
384
411
|
* @private
|
|
385
412
|
*/
|
|
386
413
|
function getPackageName(fullPackage) {
|
|
@@ -398,14 +425,76 @@ function isSpecialGoModule(moduleName) {
|
|
|
398
425
|
/**
|
|
399
426
|
*
|
|
400
427
|
* @param {string} fullPackage - full package with its name and version
|
|
401
|
-
* @return
|
|
428
|
+
* @return {string|undefined} package version only
|
|
402
429
|
* @private
|
|
403
430
|
*/
|
|
404
431
|
function getVersionOfPackage(fullPackage) {
|
|
405
432
|
let parts = fullPackage.split("@");
|
|
406
433
|
return parts.length > 1 ? parts[1] : undefined;
|
|
407
434
|
}
|
|
435
|
+
function parseModuleVersions(goListOutput) {
|
|
436
|
+
let modules = {};
|
|
437
|
+
goListOutput.split(getLineSeparatorGolang()).filter(string => string.trim() !== "")
|
|
438
|
+
.forEach((line) => {
|
|
439
|
+
let parts = line.trim().split(" ");
|
|
440
|
+
if (parts.length === 2) {
|
|
441
|
+
modules[parts[0]] = parts[1];
|
|
442
|
+
}
|
|
443
|
+
else if (parts.length >= 4 && parts[2] === "=>") {
|
|
444
|
+
modules[parts[0]] = parts[parts.length - 1];
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
return modules;
|
|
448
|
+
}
|
|
408
449
|
function getLineSeparatorGolang() {
|
|
409
450
|
let reg = /\n|\r\n/;
|
|
410
451
|
return reg;
|
|
411
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Discover all go.mod manifest paths in a Go workspace.
|
|
455
|
+
* Uses `go work edit -json` to get workspace members.
|
|
456
|
+
*
|
|
457
|
+
* @param {string} workspaceRoot - Absolute or relative path to workspace root (must contain go.work)
|
|
458
|
+
* @param {import('../index.js').Options} [opts={}]
|
|
459
|
+
* @returns {Promise<string[]>} Paths to go.mod files (absolute)
|
|
460
|
+
*/
|
|
461
|
+
export async function discoverGoWorkspaceModules(workspaceRoot, opts = {}) {
|
|
462
|
+
const root = path.resolve(workspaceRoot);
|
|
463
|
+
const goWork = path.join(root, 'go.work');
|
|
464
|
+
if (!fs.existsSync(goWork)) {
|
|
465
|
+
return [];
|
|
466
|
+
}
|
|
467
|
+
const goBin = getCustomPath('go', opts);
|
|
468
|
+
let output;
|
|
469
|
+
try {
|
|
470
|
+
output = invokeCommand(goBin, ['work', 'edit', '-json', goWork], { cwd: root });
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
let workspace;
|
|
476
|
+
try {
|
|
477
|
+
workspace = JSON.parse(output.toString().trim());
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
return [];
|
|
481
|
+
}
|
|
482
|
+
const useEntries = workspace.Use || [];
|
|
483
|
+
if (useEntries.length === 0) {
|
|
484
|
+
return [];
|
|
485
|
+
}
|
|
486
|
+
const manifestPaths = [];
|
|
487
|
+
for (const entry of useEntries) {
|
|
488
|
+
const diskPath = entry.DiskPath;
|
|
489
|
+
if (!diskPath) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
const moduleDir = path.resolve(root, diskPath);
|
|
493
|
+
const goMod = path.join(moduleDir, 'go.mod');
|
|
494
|
+
if (fs.existsSync(goMod)) {
|
|
495
|
+
manifestPaths.push(goMod);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const ignorePatterns = resolveWorkspaceDiscoveryIgnore(opts);
|
|
499
|
+
return filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns);
|
|
500
|
+
}
|
|
@@ -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-gomod.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 getRequireQuery() {
|
|
14
|
+
const language = await init();
|
|
15
|
+
return new Query(language, '(require_spec (module_path) @name (version) @version) @spec');
|
|
16
|
+
}
|
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discover all build.gradle[.kts] manifest paths in a Gradle multi-project build.
|
|
3
|
+
* Uses a custom init script to get structured project listing.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} workspaceRoot - Absolute or relative path to workspace root (must contain settings.gradle[.kts])
|
|
6
|
+
* @param {import('../index.js').Options} [opts={}]
|
|
7
|
+
* @returns {Promise<string[]>} Paths to build.gradle[.kts] files (absolute)
|
|
8
|
+
*/
|
|
9
|
+
export function discoverGradleSubprojects(workspaceRoot: string, opts?: import("../index.js").Options): Promise<string[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Parse the structured output from the Gradle init script.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} raw - Raw stdout from gradle
|
|
14
|
+
* @returns {{ path: string, dir: string }[]}
|
|
15
|
+
*/
|
|
16
|
+
export function parseGradleInitScriptOutput(raw: string): {
|
|
17
|
+
path: string;
|
|
18
|
+
dir: string;
|
|
19
|
+
}[];
|
|
20
|
+
/**
|
|
21
|
+
* Parse the structured output from the Gradle hash init script.
|
|
22
|
+
* Each recognised line has the form `::DA_HASH::group:name:version::<file-path>`.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} raw - Raw stdout from gradle
|
|
25
|
+
* @returns {{ id: string, file: string }[]} component id (`group:name:version`) and absolute artifact file path
|
|
26
|
+
*/
|
|
27
|
+
export function parseGradleHashScriptOutput(raw: string): {
|
|
28
|
+
id: string;
|
|
29
|
+
file: string;
|
|
30
|
+
}[];
|
|
31
|
+
/**
|
|
32
|
+
* Parse a Gradle module component id (`group:name:version`) into its coordinate
|
|
33
|
+
* parts. The caller builds the canonical PURL key from these parts using the same
|
|
34
|
+
* builder {@link parseDep} uses, so the stored key and the lookup key cannot drift.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} id - the Gradle component id
|
|
37
|
+
* @returns {{group: string, name: string, version: string}|null} the parsed coordinate, or null when the id is malformed
|
|
38
|
+
*/
|
|
39
|
+
export function parseComponentId(id: string): {
|
|
40
|
+
group: string;
|
|
41
|
+
name: string;
|
|
42
|
+
version: string;
|
|
43
|
+
} | null;
|
|
1
44
|
/**
|
|
2
45
|
* This class provides common functionality for Groovy and Kotlin DSL files.
|
|
3
46
|
*/
|
|
@@ -35,6 +78,30 @@ export default class Java_gradle extends Base_java {
|
|
|
35
78
|
* @returns {Provided}
|
|
36
79
|
*/
|
|
37
80
|
provideComponent(manifest: string, opts?: {}): Provided;
|
|
81
|
+
/**
|
|
82
|
+
* Compute SHA-256 hashes for the resolved artifacts of a Gradle manifest.
|
|
83
|
+
*
|
|
84
|
+
* Rather than scanning the local Gradle cache, this asks Gradle itself for
|
|
85
|
+
* the resolved artifact files via an init script (mirroring the pattern used
|
|
86
|
+
* by {@link discoverGradleSubprojects}), then hashes each file with the
|
|
87
|
+
* Node.js `crypto` module. The result is keyed by the canonical PURL string
|
|
88
|
+
* built via {@link Base_java#toPurl} — the same builder {@link parseDep} uses
|
|
89
|
+
* for the lookup — so the stored key and the lookup key cannot drift.
|
|
90
|
+
*
|
|
91
|
+
* Degrades gracefully: if Gradle cannot be invoked, the init script fails, or
|
|
92
|
+
* an artifact has no readable file (e.g. BOM/`platform()` dependencies), the
|
|
93
|
+
* hash for that component is omitted rather than throwing. A warning is emitted
|
|
94
|
+
* on every degradation path so incomplete hash coverage is visible even without
|
|
95
|
+
* `TRUSTIFY_DA_DEBUG` (mirroring the pip/cargo providers).
|
|
96
|
+
*
|
|
97
|
+
* @param {string} manifest - path to build.gradle[.kts]
|
|
98
|
+
* @param {{}} [opts={}] - optional various options to pass along the application
|
|
99
|
+
* @returns {Promise<Map<string, Array<{alg: string, content: string}>>>} map of canonical PURL string to CycloneDX hashes
|
|
100
|
+
*/
|
|
101
|
+
parseGradleHashes(manifest: string, opts?: {}): Promise<Map<string, Array<{
|
|
102
|
+
alg: string;
|
|
103
|
+
content: string;
|
|
104
|
+
}>>>;
|
|
38
105
|
#private;
|
|
39
106
|
}
|
|
40
107
|
export type Provided = import("../provider.js").Provided;
|