@cyclonedx/cdxgen 12.2.0 → 12.3.0
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 +242 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +532 -168
- package/bin/convert.js +99 -0
- package/bin/evinse.js +23 -0
- package/bin/repl.js +339 -8
- package/bin/sign.js +8 -0
- package/bin/validate.js +8 -0
- package/bin/verify.js +8 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/lolbas-index.json +150 -0
- package/data/queries-darwin.json +63 -3
- package/data/queries-win.json +45 -3
- package/data/queries.json +74 -2
- package/data/rules/chrome-extensions.yaml +240 -0
- package/data/rules/ci-permissions.yaml +478 -18
- package/data/rules/container-risk.yaml +270 -0
- package/data/rules/obom-runtime.yaml +891 -0
- package/data/rules/package-integrity.yaml +49 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-model-v3.0.1.jsonld +15999 -0
- package/lib/audit/index.js +1924 -0
- package/lib/audit/index.poku.js +1488 -0
- package/lib/audit/progress.js +137 -0
- package/lib/audit/progress.poku.js +188 -0
- package/lib/audit/reporters.js +618 -0
- package/lib/audit/scoring.js +310 -0
- package/lib/audit/scoring.poku.js +341 -0
- package/lib/audit/targets.js +260 -0
- package/lib/audit/targets.poku.js +331 -0
- package/lib/cli/index.js +276 -68
- package/lib/cli/index.poku.js +368 -0
- package/lib/helpers/analyzer.js +1052 -5
- package/lib/helpers/analyzer.poku.js +301 -0
- package/lib/helpers/annotationFormatter.js +49 -0
- package/lib/helpers/annotationFormatter.poku.js +44 -0
- package/lib/helpers/bomUtils.js +36 -0
- package/lib/helpers/bomUtils.poku.js +51 -0
- package/lib/helpers/caxa.js +2 -2
- package/lib/helpers/chromextutils.js +1153 -0
- package/lib/helpers/chromextutils.poku.js +493 -0
- package/lib/helpers/ciParsers/githubActions.js +1632 -45
- package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
- package/lib/helpers/containerRisk.js +186 -0
- package/lib/helpers/containerRisk.poku.js +52 -0
- package/lib/helpers/depsUtils.js +16 -0
- package/lib/helpers/depsUtils.poku.js +58 -1
- package/lib/helpers/display.js +245 -61
- package/lib/helpers/display.poku.js +162 -2
- package/lib/helpers/exportUtils.js +123 -0
- package/lib/helpers/exportUtils.poku.js +60 -0
- package/lib/helpers/formulationParsers.js +69 -0
- package/lib/helpers/formulationParsers.poku.js +44 -0
- package/lib/helpers/gtfobins.js +189 -0
- package/lib/helpers/gtfobins.poku.js +49 -0
- package/lib/helpers/lolbas.js +267 -0
- package/lib/helpers/lolbas.poku.js +39 -0
- package/lib/helpers/osqueryTransform.js +84 -0
- package/lib/helpers/osqueryTransform.poku.js +49 -0
- package/lib/helpers/provenanceUtils.js +193 -0
- package/lib/helpers/provenanceUtils.poku.js +145 -0
- package/lib/helpers/pylockutils.js +281 -0
- package/lib/helpers/pylockutils.poku.js +48 -0
- package/lib/helpers/registryProvenance.js +793 -0
- package/lib/helpers/registryProvenance.poku.js +452 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/source.js +1267 -0
- package/lib/helpers/source.poku.js +771 -0
- package/lib/helpers/spdxUtils.js +97 -0
- package/lib/helpers/spdxUtils.poku.js +70 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +882 -136
- package/lib/helpers/utils.poku.js +995 -91
- package/lib/managers/binary.js +29 -5
- package/lib/managers/docker.js +179 -52
- package/lib/managers/docker.poku.js +327 -28
- package/lib/managers/oci.js +107 -23
- package/lib/managers/oci.poku.js +132 -0
- package/lib/server/openapi.yaml +50 -0
- package/lib/server/server.js +228 -331
- package/lib/server/server.poku.js +220 -5
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +20 -5
- package/lib/stages/postgen/auditBom.poku.js +1729 -67
- package/lib/stages/postgen/postgen.js +40 -0
- package/lib/stages/postgen/postgen.poku.js +47 -0
- package/lib/stages/postgen/ruleEngine.js +80 -2
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/spdxConverter.poku.js +341 -0
- package/lib/validator/bomValidator.js +232 -0
- package/lib/validator/bomValidator.poku.js +70 -0
- package/lib/validator/complianceRules.js +70 -7
- package/lib/validator/complianceRules.poku.js +30 -0
- package/lib/validator/reporters/annotations.js +2 -2
- package/lib/validator/reporters/console.js +13 -2
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -8
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +115 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +27 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +35 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +35 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +63 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +13 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +5 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts +97 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/containerRisk.d.ts +17 -0
- package/types/lib/helpers/containerRisk.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +4 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/exportUtils.d.ts +40 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +17 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts +16 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -0
- package/types/lib/helpers/osqueryTransform.d.ts +7 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +90 -0
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
- package/types/lib/helpers/pylockutils.d.ts +51 -0
- package/types/lib/helpers/pylockutils.d.ts.map +1 -0
- package/types/lib/helpers/registryProvenance.d.ts +17 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts +141 -0
- package/types/lib/helpers/source.d.ts.map +1 -0
- package/types/lib/helpers/spdxUtils.d.ts +2 -0
- package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
- package/types/lib/helpers/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +30 -11
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +0 -35
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +1 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/bin/dependencies.d.ts +0 -3
- package/types/bin/dependencies.d.ts.map +0 -1
- package/types/bin/licenses.d.ts +0 -3
- package/types/bin/licenses.d.ts.map +0 -1
package/lib/server/server.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import http from "node:http";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import process from "node:process";
|
|
@@ -9,16 +8,31 @@ import compression from "compression";
|
|
|
9
8
|
import connect from "connect";
|
|
10
9
|
|
|
11
10
|
import { createBom, submitBom } from "../cli/index.js";
|
|
11
|
+
import { normalizeOutputFormats } from "../helpers/exportUtils.js";
|
|
12
|
+
import {
|
|
13
|
+
cleanupSourceDir,
|
|
14
|
+
findGitRefForPurlVersion,
|
|
15
|
+
getGitAllowProtocol,
|
|
16
|
+
gitClone,
|
|
17
|
+
isAllowedPath,
|
|
18
|
+
isAllowedWinPath,
|
|
19
|
+
maybePurlSource,
|
|
20
|
+
maybeRemotePath,
|
|
21
|
+
PURL_REGISTRY_LOOKUP_WARNING,
|
|
22
|
+
resolveGitUrlFromPurl,
|
|
23
|
+
resolvePurlSourceDirectory,
|
|
24
|
+
sanitizeRemoteUrlForLogs,
|
|
25
|
+
validateAndRejectGitSource,
|
|
26
|
+
validatePurlSource,
|
|
27
|
+
} from "../helpers/source.js";
|
|
12
28
|
import {
|
|
13
29
|
CDXGEN_VERSION,
|
|
14
|
-
getTmpDir,
|
|
15
30
|
hasDangerousUnicode,
|
|
16
31
|
isSecureMode,
|
|
17
|
-
isValidDriveRoot,
|
|
18
32
|
isWin,
|
|
19
|
-
safeSpawnSync,
|
|
20
33
|
} from "../helpers/utils.js";
|
|
21
34
|
import { postProcess } from "../stages/postgen/postgen.js";
|
|
35
|
+
import { convertCycloneDxToSpdx } from "../stages/postgen/spdxConverter.js";
|
|
22
36
|
|
|
23
37
|
// Timeout milliseconds. Default 10 mins
|
|
24
38
|
const TIMEOUT_MS =
|
|
@@ -36,10 +50,15 @@ const ALLOWED_PARAMS = [
|
|
|
36
50
|
"projectGroup",
|
|
37
51
|
"projectTag",
|
|
38
52
|
"projectVersion",
|
|
53
|
+
"autoCreate",
|
|
54
|
+
"isLatest",
|
|
39
55
|
"parentUUID",
|
|
56
|
+
"parentProjectName",
|
|
57
|
+
"parentProjectVersion",
|
|
40
58
|
"serverUrl",
|
|
41
59
|
"apiKey",
|
|
42
60
|
"specVersion",
|
|
61
|
+
"format",
|
|
43
62
|
"filter",
|
|
44
63
|
"only",
|
|
45
64
|
"autoCompositions",
|
|
@@ -57,270 +76,37 @@ const ALLOWED_PARAMS = [
|
|
|
57
76
|
|
|
58
77
|
const app = connect();
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
deflate: true,
|
|
63
|
-
limit: "1mb",
|
|
64
|
-
}),
|
|
65
|
-
);
|
|
66
|
-
app.use(compression());
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Return git allow protocol string from the environment variables.
|
|
70
|
-
*
|
|
71
|
-
* @returns {string} git allow protocol string
|
|
72
|
-
*/
|
|
73
|
-
function getGitAllowProtocol() {
|
|
74
|
-
return (
|
|
75
|
-
process.env.GIT_ALLOW_PROTOCOL ||
|
|
76
|
-
process.env.CDXGEN_SERVER_GIT_ALLOW_PROTOCOL ||
|
|
77
|
-
(isSecureMode ? "https:ssh" : "https:git:ssh")
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Checks the given hostname against the allowed list.
|
|
83
|
-
*
|
|
84
|
-
* @param {string} hostname Host name to check
|
|
85
|
-
* @returns {boolean} true if the hostname in its entirety is allowed. false otherwise.
|
|
86
|
-
*/
|
|
87
|
-
export function isAllowedHost(hostname) {
|
|
88
|
-
if (!process.env.CDXGEN_SERVER_ALLOWED_HOSTS) {
|
|
79
|
+
function isAllowedHttpHost(hostname) {
|
|
80
|
+
if (!process.env.CDXGEN_ALLOWED_HOSTS) {
|
|
89
81
|
return true;
|
|
90
82
|
}
|
|
91
|
-
|
|
92
|
-
if (hasDangerousUnicode(hostname)) {
|
|
83
|
+
if (!hostname || hasDangerousUnicode(hostname)) {
|
|
93
84
|
return false;
|
|
94
85
|
}
|
|
95
|
-
|
|
96
|
-
.
|
|
97
|
-
.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Checks the given path string to belong to a drive in Windows.
|
|
102
|
-
*
|
|
103
|
-
* @param {string} p Path string to check
|
|
104
|
-
* @returns {boolean} true if the windows path belongs to a drive. false otherwise (device names)
|
|
105
|
-
*/
|
|
106
|
-
export function isAllowedWinPath(p) {
|
|
107
|
-
if (typeof p !== "string") {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
if (p === "") {
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
// Guard against dangerous Unicode characters
|
|
114
|
-
if (hasDangerousUnicode(p)) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
const normalized = path.normalize(p);
|
|
119
|
-
// Check the entire normalized path for dangerous patterns
|
|
120
|
-
if (hasDangerousUnicode(normalized)) {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
const { root } = path.parse(normalized);
|
|
124
|
-
// Both Relative paths and invalid windows device names are resulting in an empty root
|
|
125
|
-
// To keep things simple, we don't accept relative paths for Windows server-mode users at all
|
|
126
|
-
|
|
127
|
-
// Invocations with unix-style paths result in "\\" as the root on windows
|
|
128
|
-
// path.parse(path.normalize("/foo/bar"))
|
|
129
|
-
// { root: '\\', dir: '\\foo', base: 'bar', ext: '', name: 'bar' }
|
|
130
|
-
if (root === "\\") {
|
|
86
|
+
const allowHosts = process.env.CDXGEN_ALLOWED_HOSTS.split(",")
|
|
87
|
+
.map((host) => host.trim())
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
for (const allowedHost of allowHosts) {
|
|
90
|
+
if (hostname === allowedHost) {
|
|
131
91
|
return true;
|
|
132
92
|
}
|
|
133
|
-
// Check for device/UNC paths - these should always return false
|
|
134
|
-
if (root.startsWith("\\\\")) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
// Strict validation for drive letter format
|
|
138
|
-
return isValidDriveRoot(root);
|
|
139
|
-
} catch (_err) {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Checks the given path against the allowed list.
|
|
146
|
-
*
|
|
147
|
-
* @param {string} p Path string to check
|
|
148
|
-
* @returns {boolean} true if the path is present in the allowed paths. false otherwise.
|
|
149
|
-
*/
|
|
150
|
-
export function isAllowedPath(p) {
|
|
151
|
-
if (typeof p !== "string") {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
// Guard against dangerous Unicode characters
|
|
155
|
-
if (hasDangerousUnicode(p)) {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
if (!process.env.CDXGEN_SERVER_ALLOWED_PATHS) {
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
// Handle CVE-2025-27210 without relying entirely on node blocklists
|
|
162
|
-
if (isWin && !isAllowedWinPath(p)) {
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
return (process.env.CDXGEN_SERVER_ALLOWED_PATHS || "")
|
|
166
|
-
.split(",")
|
|
167
|
-
.filter(Boolean)
|
|
168
|
-
.some((ap) => {
|
|
169
|
-
const resolvedP = path.resolve(p);
|
|
170
|
-
const resolvedAp = path.resolve(ap);
|
|
171
|
-
const relativePath = path.relative(resolvedAp, resolvedP);
|
|
172
|
-
return (
|
|
173
|
-
relativePath === "" ||
|
|
174
|
-
(!relativePath.startsWith("..") && !path.isAbsolute(relativePath))
|
|
175
|
-
);
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Determine if the file path could be a remote URL.
|
|
181
|
-
*
|
|
182
|
-
* @param {string} filePath The Git URL or local path
|
|
183
|
-
* @returns {Boolean} True if the file path is a remote URL. false otherwise.
|
|
184
|
-
*/
|
|
185
|
-
export function maybeRemotePath(filePath) {
|
|
186
|
-
return /^[a-zA-Z0-9+.-]+:\/\//.test(filePath) || filePath.startsWith("git@");
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Validates a given Git URL/Path against dangerous protocols and allowed hosts.
|
|
191
|
-
*
|
|
192
|
-
* @param {string} filePath The Git URL or local path
|
|
193
|
-
* @returns {Object|null} Error object if invalid, or null if valid
|
|
194
|
-
*/
|
|
195
|
-
export function validateAndRejectGitSource(filePath) {
|
|
196
|
-
if (/^(ext|fd)::/i.test(filePath)) {
|
|
197
|
-
return {
|
|
198
|
-
status: 400,
|
|
199
|
-
error: "Invalid Protocol",
|
|
200
|
-
details: "The provided protocol is not allowed.",
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
if (maybeRemotePath(filePath)) {
|
|
204
|
-
let gitUrlObj;
|
|
205
|
-
try {
|
|
206
|
-
let urlToParse = filePath;
|
|
207
|
-
if (filePath.startsWith("git@") && !filePath.includes("://")) {
|
|
208
|
-
urlToParse = `ssh://${filePath.replace(":", "/")}`;
|
|
209
|
-
}
|
|
210
|
-
gitUrlObj = new URL(urlToParse);
|
|
211
|
-
} catch (_err) {
|
|
212
|
-
return {
|
|
213
|
-
status: 400,
|
|
214
|
-
error: "Invalid URL Format",
|
|
215
|
-
details: "The provided Git URL is malformed.",
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
const gitAllowProtocol = getGitAllowProtocol();
|
|
219
|
-
const allowedSchemes = gitAllowProtocol
|
|
220
|
-
.split(":")
|
|
221
|
-
.filter(Boolean)
|
|
222
|
-
.map((p) => `${p.toLowerCase()}:`);
|
|
223
|
-
|
|
224
93
|
if (
|
|
225
|
-
|
|
226
|
-
|
|
94
|
+
allowedHost.startsWith("*.") &&
|
|
95
|
+
hostname.endsWith(allowedHost.slice(1))
|
|
227
96
|
) {
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (!allowedSchemes.includes(gitUrlObj.protocol)) {
|
|
232
|
-
return {
|
|
233
|
-
status: 400,
|
|
234
|
-
error: "Protocol Not Allowed",
|
|
235
|
-
details: `The protocol '${gitUrlObj.protocol}' is not permitted by GIT_ALLOW_PROTOCOL.`,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (gitUrlObj.href.includes("::")) {
|
|
240
|
-
return {
|
|
241
|
-
status: 400,
|
|
242
|
-
error: "Invalid URL Syntax",
|
|
243
|
-
details: "Git remote helper syntax (::) is not allowed.",
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (!isAllowedHost(gitUrlObj.hostname)) {
|
|
248
|
-
return {
|
|
249
|
-
status: 403,
|
|
250
|
-
error: "Host Not Allowed",
|
|
251
|
-
details: "The Git URL host is not allowed as per the allowlist.",
|
|
252
|
-
};
|
|
97
|
+
return true;
|
|
253
98
|
}
|
|
254
99
|
}
|
|
255
|
-
|
|
256
|
-
return null;
|
|
100
|
+
return false;
|
|
257
101
|
}
|
|
258
102
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const gitArgs = [
|
|
267
|
-
"-c",
|
|
268
|
-
"alias.clone=",
|
|
269
|
-
"-c",
|
|
270
|
-
"core.fsmonitor=false",
|
|
271
|
-
"-c",
|
|
272
|
-
"safe.bareRepository=explicit",
|
|
273
|
-
"clone",
|
|
274
|
-
repoUrl,
|
|
275
|
-
"--depth",
|
|
276
|
-
"1",
|
|
277
|
-
tempDir,
|
|
278
|
-
];
|
|
279
|
-
if (branch) {
|
|
280
|
-
const firstBranchStr = Array.isArray(branch) ? branch[0] : String(branch);
|
|
281
|
-
if (firstBranchStr.startsWith("-")) {
|
|
282
|
-
console.log(
|
|
283
|
-
`Skipping branch clone: invalid branch name ${firstBranchStr}`,
|
|
284
|
-
);
|
|
285
|
-
} else {
|
|
286
|
-
const cloneIndex = gitArgs.indexOf("clone");
|
|
287
|
-
gitArgs.splice(cloneIndex + 1, 0, "--branch", firstBranchStr);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
console.log(
|
|
291
|
-
`Cloning Repo${branch ? ` with branch ${branch}` : ""} to ${tempDir}`,
|
|
292
|
-
);
|
|
293
|
-
const gitAllowProtocol = getGitAllowProtocol();
|
|
294
|
-
const envConfigs = {
|
|
295
|
-
GIT_CONFIG_COUNT: "2",
|
|
296
|
-
GIT_CONFIG_KEY_0: "core.fsmonitor",
|
|
297
|
-
GIT_CONFIG_VALUE_0: "false",
|
|
298
|
-
GIT_CONFIG_KEY_1: "safe.bareRepository",
|
|
299
|
-
GIT_CONFIG_VALUE_1: "explicit",
|
|
300
|
-
};
|
|
301
|
-
const env = isSecureMode
|
|
302
|
-
? {
|
|
303
|
-
...process.env,
|
|
304
|
-
...envConfigs,
|
|
305
|
-
GIT_CONFIG_NOSYSTEM: "1",
|
|
306
|
-
GIT_CONFIG_NOGLOBAL: "1",
|
|
307
|
-
GIT_ALLOW_PROTOCOL: gitAllowProtocol,
|
|
308
|
-
}
|
|
309
|
-
: {
|
|
310
|
-
...process.env,
|
|
311
|
-
...envConfigs,
|
|
312
|
-
GIT_ALLOW_PROTOCOL: gitAllowProtocol,
|
|
313
|
-
};
|
|
314
|
-
const result = safeSpawnSync("git", gitArgs, {
|
|
315
|
-
shell: false,
|
|
316
|
-
env,
|
|
317
|
-
});
|
|
318
|
-
if (result.status !== 0) {
|
|
319
|
-
console.log(result.stderr);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return tempDir;
|
|
323
|
-
}
|
|
103
|
+
app.use(
|
|
104
|
+
bodyParser.json({
|
|
105
|
+
deflate: true,
|
|
106
|
+
limit: "1mb",
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
app.use(compression());
|
|
324
110
|
|
|
325
111
|
function sanitizeStr(s) {
|
|
326
112
|
return s ? s.replace(/[\r\n]/g, "") : s;
|
|
@@ -504,7 +290,10 @@ const start = (options) => {
|
|
|
504
290
|
process.exit(1);
|
|
505
291
|
}
|
|
506
292
|
}
|
|
507
|
-
if (
|
|
293
|
+
if (
|
|
294
|
+
!process.env.CDXGEN_GIT_ALLOWED_HOSTS &&
|
|
295
|
+
!process.env.CDXGEN_SERVER_ALLOWED_HOSTS
|
|
296
|
+
) {
|
|
508
297
|
console.log(
|
|
509
298
|
"No allowlist for git hosts has been specified. This is a security risk that could expose the system to SSRF vulnerabilities!",
|
|
510
299
|
);
|
|
@@ -512,7 +301,11 @@ const start = (options) => {
|
|
|
512
301
|
process.exit(1);
|
|
513
302
|
}
|
|
514
303
|
}
|
|
515
|
-
if (
|
|
304
|
+
if (
|
|
305
|
+
isSecureMode &&
|
|
306
|
+
!process.env.CDXGEN_ALLOWED_PATHS &&
|
|
307
|
+
!process.env.CDXGEN_SERVER_ALLOWED_PATHS
|
|
308
|
+
) {
|
|
516
309
|
console.log(
|
|
517
310
|
"No allowlist for paths has been specified. This is a security risk that could expose the filesystem and internal secrets!",
|
|
518
311
|
);
|
|
@@ -580,98 +373,202 @@ const start = (options) => {
|
|
|
580
373
|
}),
|
|
581
374
|
);
|
|
582
375
|
}
|
|
583
|
-
|
|
584
|
-
if (validationError) {
|
|
585
|
-
res.writeHead(validationError.status, {
|
|
586
|
-
"Content-Type": "application/json",
|
|
587
|
-
});
|
|
588
|
-
return res.end(
|
|
589
|
-
JSON.stringify({
|
|
590
|
-
error: validationError.error,
|
|
591
|
-
details: validationError.details,
|
|
592
|
-
}),
|
|
593
|
-
);
|
|
594
|
-
}
|
|
376
|
+
let cloneDir;
|
|
595
377
|
let srcDir;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
378
|
+
try {
|
|
379
|
+
let sourcePath = filePath;
|
|
380
|
+
let purlResolution;
|
|
381
|
+
if (maybePurlSource(sourcePath)) {
|
|
382
|
+
const purlValidationError = validatePurlSource(sourcePath);
|
|
383
|
+
if (purlValidationError) {
|
|
384
|
+
res.writeHead(purlValidationError.status, {
|
|
385
|
+
"Content-Type": "application/json",
|
|
386
|
+
});
|
|
387
|
+
return res.end(
|
|
388
|
+
JSON.stringify({
|
|
389
|
+
error: purlValidationError.error,
|
|
390
|
+
details: purlValidationError.details,
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
purlResolution = await resolveGitUrlFromPurl(sourcePath);
|
|
395
|
+
if (!purlResolution?.repoUrl) {
|
|
396
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
397
|
+
return res.end(
|
|
398
|
+
JSON.stringify({
|
|
399
|
+
error: "Unsupported purl source",
|
|
400
|
+
details:
|
|
401
|
+
"Unable to resolve the provided package URL to a repository URL.",
|
|
402
|
+
}),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
if (purlResolution.registry) {
|
|
406
|
+
console.warn(
|
|
407
|
+
`${PURL_REGISTRY_LOOKUP_WARNING} Registry: ${purlResolution.registry}, purl type: ${purlResolution.type}, resolved URL: ${sanitizeRemoteUrlForLogs(purlResolution.repoUrl)}`,
|
|
408
|
+
);
|
|
409
|
+
} else {
|
|
410
|
+
console.warn(
|
|
411
|
+
`Resolved repository URL from purl metadata. purl type: ${purlResolution.type}, resolved URL: ${sanitizeRemoteUrlForLogs(purlResolution.repoUrl)}`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
sourcePath = purlResolution.repoUrl;
|
|
415
|
+
}
|
|
416
|
+
const validationError = validateAndRejectGitSource(sourcePath);
|
|
417
|
+
if (validationError) {
|
|
418
|
+
res.writeHead(validationError.status, {
|
|
419
|
+
"Content-Type": "application/json",
|
|
420
|
+
});
|
|
606
421
|
return res.end(
|
|
607
422
|
JSON.stringify({
|
|
608
|
-
error:
|
|
609
|
-
details:
|
|
423
|
+
error: validationError.error,
|
|
424
|
+
details: validationError.details,
|
|
610
425
|
}),
|
|
611
426
|
);
|
|
612
427
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
428
|
+
if (maybeRemotePath(sourcePath)) {
|
|
429
|
+
let gitRef = reqOptions.gitBranch;
|
|
430
|
+
if (!gitRef && purlResolution?.version) {
|
|
431
|
+
gitRef = findGitRefForPurlVersion(sourcePath, purlResolution);
|
|
432
|
+
if (!gitRef) {
|
|
433
|
+
console.warn(
|
|
434
|
+
`Unable to find a matching git tag for version '${purlResolution.version}'. Falling back to repository default branch.`,
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
cloneDir = gitClone(sourcePath, gitRef);
|
|
439
|
+
srcDir = cloneDir;
|
|
440
|
+
if (purlResolution?.type === "npm") {
|
|
441
|
+
const cloneRootDir = cloneDir;
|
|
442
|
+
const purlSourceDir = resolvePurlSourceDirectory(
|
|
443
|
+
srcDir,
|
|
444
|
+
purlResolution,
|
|
445
|
+
);
|
|
446
|
+
if (purlSourceDir && purlSourceDir !== cloneRootDir) {
|
|
447
|
+
const relativeDir = path.relative(cloneRootDir, purlSourceDir);
|
|
448
|
+
if (relativeDir.startsWith("..") || path.isAbsolute(relativeDir)) {
|
|
449
|
+
console.warn(
|
|
450
|
+
`Ignoring detected npm package directory outside clone root: ${purlSourceDir}`,
|
|
451
|
+
);
|
|
452
|
+
} else {
|
|
453
|
+
console.warn(
|
|
454
|
+
`Using npm package directory '${purlSourceDir}' for purl '${purlResolution.namespace ? `${purlResolution.namespace}/` : ""}${purlResolution.name}'.`,
|
|
455
|
+
);
|
|
456
|
+
srcDir = purlSourceDir;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
cleanup = true;
|
|
461
|
+
} else {
|
|
462
|
+
srcDir = sourcePath;
|
|
463
|
+
if (
|
|
464
|
+
!isAllowedPath(path.resolve(srcDir)) ||
|
|
465
|
+
(isWin && !isAllowedWinPath(srcDir))
|
|
466
|
+
) {
|
|
467
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
468
|
+
return res.end(
|
|
469
|
+
JSON.stringify({
|
|
470
|
+
error: "Path Not Allowed",
|
|
471
|
+
details: "Path is not allowed as per the allowlist.",
|
|
472
|
+
}),
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (srcDir !== path.resolve(srcDir)) {
|
|
477
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
629
478
|
return res.end(
|
|
630
479
|
JSON.stringify({
|
|
631
|
-
error: "
|
|
632
|
-
details: "
|
|
480
|
+
error: "Absolute path needed",
|
|
481
|
+
details: "Relative paths are not supported in server mode.",
|
|
633
482
|
}),
|
|
634
483
|
);
|
|
635
484
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
485
|
+
console.log("Generating SBOM for", srcDir);
|
|
486
|
+
let bomNSData = (await createBom(srcDir, reqOptions)) || {};
|
|
487
|
+
bomNSData = postProcess(bomNSData, reqOptions, srcDir);
|
|
488
|
+
const requestedFormats = normalizeOutputFormats(reqOptions.format);
|
|
489
|
+
let responseBomJson = bomNSData.bomJson;
|
|
490
|
+
if (
|
|
491
|
+
requestedFormats.includes("spdx") &&
|
|
492
|
+
bomNSData?.bomJson?.bomFormat === "CycloneDX"
|
|
493
|
+
) {
|
|
494
|
+
responseBomJson = convertCycloneDxToSpdx(bomNSData.bomJson, reqOptions);
|
|
640
495
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const errorMessages = error.response?.body?.errors;
|
|
649
|
-
if (errorMessages) {
|
|
650
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
496
|
+
if (reqOptions.serverUrl && reqOptions.apiKey) {
|
|
497
|
+
let serverHostname;
|
|
498
|
+
try {
|
|
499
|
+
serverHostname = new URL(reqOptions.serverUrl).hostname;
|
|
500
|
+
} catch (err) {
|
|
501
|
+
console.log("Invalid Dependency-Track server URL", err);
|
|
502
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
651
503
|
return res.end(
|
|
652
504
|
JSON.stringify({
|
|
653
|
-
error: "
|
|
654
|
-
details:
|
|
505
|
+
error: "Invalid Server URL",
|
|
506
|
+
details: "The Dependency-Track server URL is invalid.",
|
|
655
507
|
}),
|
|
656
508
|
);
|
|
657
509
|
}
|
|
510
|
+
if (!isAllowedHttpHost(serverHostname)) {
|
|
511
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
512
|
+
return res.end(
|
|
513
|
+
JSON.stringify({
|
|
514
|
+
error: "Host Not Allowed",
|
|
515
|
+
details: "The URL host is not allowed as per the allowlist.",
|
|
516
|
+
}),
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
if (isSecureMode && !reqOptions.serverUrl?.startsWith("https://")) {
|
|
520
|
+
console.log(
|
|
521
|
+
"Dependency Track API server is used with a non-https url, which poses a security risk.",
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
console.log(
|
|
525
|
+
`Publishing SBOM ${reqOptions.projectName} to Dependency Track`,
|
|
526
|
+
reqOptions.serverUrl,
|
|
527
|
+
);
|
|
528
|
+
try {
|
|
529
|
+
await submitBom(reqOptions, bomNSData.bomJson);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
const errorMessages = error.response?.body?.errors;
|
|
532
|
+
if (errorMessages) {
|
|
533
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
534
|
+
return res.end(
|
|
535
|
+
JSON.stringify({
|
|
536
|
+
error:
|
|
537
|
+
"Unable to submit the SBOM to the Dependency-Track server",
|
|
538
|
+
details: errorMessages,
|
|
539
|
+
}),
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
658
543
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
544
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
545
|
+
if (responseBomJson) {
|
|
546
|
+
if (
|
|
547
|
+
typeof responseBomJson === "string" ||
|
|
548
|
+
responseBomJson instanceof String
|
|
549
|
+
) {
|
|
550
|
+
res.write(responseBomJson);
|
|
551
|
+
} else {
|
|
552
|
+
res.write(JSON.stringify(responseBomJson, null, null));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
res.end("\n");
|
|
556
|
+
} catch (err) {
|
|
557
|
+
if (!res.headersSent) {
|
|
558
|
+
console.log("Unable to generate SBOM", err);
|
|
559
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
560
|
+
return res.end(
|
|
561
|
+
JSON.stringify({
|
|
562
|
+
error: "Unable to generate SBOM",
|
|
563
|
+
details: "Unexpected server error while generating SBOM.",
|
|
564
|
+
}),
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
console.log("Error while generating SBOM response", err);
|
|
568
|
+
} finally {
|
|
569
|
+
if (cleanup && cloneDir) {
|
|
570
|
+
cleanupSourceDir(cloneDir);
|
|
669
571
|
}
|
|
670
|
-
}
|
|
671
|
-
res.end("\n");
|
|
672
|
-
if (cleanup && srcDir?.startsWith(getTmpDir()) && fs.rmSync) {
|
|
673
|
-
console.log(`Cleaning up ${srcDir}`);
|
|
674
|
-
fs.rmSync(srcDir, { recursive: true, force: true });
|
|
675
572
|
}
|
|
676
573
|
});
|
|
677
574
|
};
|