@cyclonedx/cdxgen 12.2.1 → 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 +239 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +513 -167
- 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 +154 -11
- package/lib/cli/index.poku.js +251 -0
- package/lib/helpers/analyzer.js +446 -2
- package/lib/helpers/analyzer.poku.js +72 -1
- 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/display.js +241 -59
- 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/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/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +700 -128
- package/lib/helpers/utils.poku.js +877 -80
- 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 +17 -0
- package/lib/server/server.js +225 -336
- package/lib/server/server.poku.js +16 -10
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +19 -3
- 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 +11 -0
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -7
- 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/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/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/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +29 -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 -36
- 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 =
|
|
@@ -44,6 +58,7 @@ const ALLOWED_PARAMS = [
|
|
|
44
58
|
"serverUrl",
|
|
45
59
|
"apiKey",
|
|
46
60
|
"specVersion",
|
|
61
|
+
"format",
|
|
47
62
|
"filter",
|
|
48
63
|
"only",
|
|
49
64
|
"autoCompositions",
|
|
@@ -61,274 +76,37 @@ const ALLOWED_PARAMS = [
|
|
|
61
76
|
|
|
62
77
|
const app = connect();
|
|
63
78
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
deflate: true,
|
|
67
|
-
limit: "1mb",
|
|
68
|
-
}),
|
|
69
|
-
);
|
|
70
|
-
app.use(compression());
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Return git allow protocol string from the environment variables.
|
|
74
|
-
*
|
|
75
|
-
* @returns {string} git allow protocol string
|
|
76
|
-
*/
|
|
77
|
-
function getGitAllowProtocol() {
|
|
78
|
-
return (
|
|
79
|
-
process.env.GIT_ALLOW_PROTOCOL ||
|
|
80
|
-
process.env.CDXGEN_SERVER_GIT_ALLOW_PROTOCOL ||
|
|
81
|
-
(isSecureMode ? "https:ssh" : "https:git:ssh")
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Checks the given hostname against the allowed list.
|
|
87
|
-
*
|
|
88
|
-
* @param {string} hostname Host name to check
|
|
89
|
-
* @returns {boolean} true if the hostname in its entirety is allowed. false otherwise.
|
|
90
|
-
*/
|
|
91
|
-
export function isAllowedHost(hostname) {
|
|
92
|
-
if (!process.env.CDXGEN_SERVER_ALLOWED_HOSTS) {
|
|
79
|
+
function isAllowedHttpHost(hostname) {
|
|
80
|
+
if (!process.env.CDXGEN_ALLOWED_HOSTS) {
|
|
93
81
|
return true;
|
|
94
82
|
}
|
|
95
|
-
|
|
96
|
-
if (hasDangerousUnicode(hostname)) {
|
|
83
|
+
if (!hostname || hasDangerousUnicode(hostname)) {
|
|
97
84
|
return false;
|
|
98
85
|
}
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Checks the given path string to belong to a drive in Windows.
|
|
106
|
-
*
|
|
107
|
-
* @param {string} p Path string to check
|
|
108
|
-
* @returns {boolean} true if the windows path belongs to a drive. false otherwise (device names)
|
|
109
|
-
*/
|
|
110
|
-
export function isAllowedWinPath(p) {
|
|
111
|
-
if (typeof p !== "string") {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
if (p === "") {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
// Guard against dangerous Unicode characters
|
|
118
|
-
if (hasDangerousUnicode(p)) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
const normalized = path.normalize(p);
|
|
123
|
-
// Check the entire normalized path for dangerous patterns
|
|
124
|
-
if (hasDangerousUnicode(normalized)) {
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
const { root } = path.parse(normalized);
|
|
128
|
-
// Both Relative paths and invalid windows device names are resulting in an empty root
|
|
129
|
-
// To keep things simple, we don't accept relative paths for Windows server-mode users at all
|
|
130
|
-
|
|
131
|
-
// Invocations with unix-style paths result in "\\" as the root on windows
|
|
132
|
-
// path.parse(path.normalize("/foo/bar"))
|
|
133
|
-
// { root: '\\', dir: '\\foo', base: 'bar', ext: '', name: 'bar' }
|
|
134
|
-
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) {
|
|
135
91
|
return true;
|
|
136
92
|
}
|
|
137
|
-
// Check for device/UNC paths - these should always return false
|
|
138
|
-
if (root.startsWith("\\\\")) {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
// Strict validation for drive letter format
|
|
142
|
-
return isValidDriveRoot(root);
|
|
143
|
-
} catch (_err) {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Checks the given path against the allowed list.
|
|
150
|
-
*
|
|
151
|
-
* @param {string} p Path string to check
|
|
152
|
-
* @returns {boolean} true if the path is present in the allowed paths. false otherwise.
|
|
153
|
-
*/
|
|
154
|
-
export function isAllowedPath(p) {
|
|
155
|
-
if (typeof p !== "string") {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
// Guard against dangerous Unicode characters
|
|
159
|
-
if (hasDangerousUnicode(p)) {
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
if (!process.env.CDXGEN_SERVER_ALLOWED_PATHS) {
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
// Handle CVE-2025-27210 without relying entirely on node blocklists
|
|
166
|
-
if (isWin && !isAllowedWinPath(p)) {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
return (process.env.CDXGEN_SERVER_ALLOWED_PATHS || "")
|
|
170
|
-
.split(",")
|
|
171
|
-
.filter(Boolean)
|
|
172
|
-
.some((ap) => {
|
|
173
|
-
const resolvedP = path.resolve(p);
|
|
174
|
-
const resolvedAp = path.resolve(ap);
|
|
175
|
-
const relativePath = path.relative(resolvedAp, resolvedP);
|
|
176
|
-
return (
|
|
177
|
-
relativePath === "" ||
|
|
178
|
-
(!relativePath.startsWith("..") && !path.isAbsolute(relativePath))
|
|
179
|
-
);
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Determine if the file path could be a remote URL.
|
|
185
|
-
*
|
|
186
|
-
* @param {string} filePath The Git URL or local path
|
|
187
|
-
* @returns {Boolean} True if the file path is a remote URL. false otherwise.
|
|
188
|
-
*/
|
|
189
|
-
export function maybeRemotePath(filePath) {
|
|
190
|
-
return /^[a-zA-Z0-9+.-]+:\/\//.test(filePath) || filePath.startsWith("git@");
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Validates a given Git URL/Path against dangerous protocols and allowed hosts.
|
|
195
|
-
*
|
|
196
|
-
* @param {string} filePath The Git URL or local path
|
|
197
|
-
* @returns {Object|null} Error object if invalid, or null if valid
|
|
198
|
-
*/
|
|
199
|
-
export function validateAndRejectGitSource(filePath) {
|
|
200
|
-
if (/^(ext|fd)::/i.test(filePath)) {
|
|
201
|
-
return {
|
|
202
|
-
status: 400,
|
|
203
|
-
error: "Invalid Protocol",
|
|
204
|
-
details: "The provided protocol is not allowed.",
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
if (maybeRemotePath(filePath)) {
|
|
208
|
-
let gitUrlObj;
|
|
209
|
-
try {
|
|
210
|
-
let urlToParse = filePath;
|
|
211
|
-
if (filePath.startsWith("git@") && !filePath.includes("://")) {
|
|
212
|
-
urlToParse = `ssh://${filePath.replace(":", "/")}`;
|
|
213
|
-
}
|
|
214
|
-
gitUrlObj = new URL(urlToParse);
|
|
215
|
-
} catch (_err) {
|
|
216
|
-
return {
|
|
217
|
-
status: 400,
|
|
218
|
-
error: "Invalid URL Format",
|
|
219
|
-
details: "The provided Git URL is malformed.",
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
const gitAllowProtocol = getGitAllowProtocol();
|
|
223
|
-
const allowedSchemes = gitAllowProtocol
|
|
224
|
-
.split(":")
|
|
225
|
-
.filter(Boolean)
|
|
226
|
-
.map((p) => `${p.toLowerCase()}:`);
|
|
227
|
-
|
|
228
93
|
if (
|
|
229
|
-
|
|
230
|
-
|
|
94
|
+
allowedHost.startsWith("*.") &&
|
|
95
|
+
hostname.endsWith(allowedHost.slice(1))
|
|
231
96
|
) {
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (!allowedSchemes.includes(gitUrlObj.protocol)) {
|
|
236
|
-
return {
|
|
237
|
-
status: 400,
|
|
238
|
-
error: "Protocol Not Allowed",
|
|
239
|
-
details: `The protocol '${gitUrlObj.protocol}' is not permitted by GIT_ALLOW_PROTOCOL.`,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (gitUrlObj.href.includes("::")) {
|
|
244
|
-
return {
|
|
245
|
-
status: 400,
|
|
246
|
-
error: "Invalid URL Syntax",
|
|
247
|
-
details: "Git remote helper syntax (::) is not allowed.",
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (!isAllowedHost(gitUrlObj.hostname)) {
|
|
252
|
-
return {
|
|
253
|
-
status: 403,
|
|
254
|
-
error: "Host Not Allowed",
|
|
255
|
-
details: "The Git URL host is not allowed as per the allowlist.",
|
|
256
|
-
};
|
|
97
|
+
return true;
|
|
257
98
|
}
|
|
258
99
|
}
|
|
259
|
-
|
|
260
|
-
return null;
|
|
100
|
+
return false;
|
|
261
101
|
}
|
|
262
102
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const gitArgs = [
|
|
271
|
-
"-c",
|
|
272
|
-
"alias.clone=",
|
|
273
|
-
"-c",
|
|
274
|
-
"core.fsmonitor=false",
|
|
275
|
-
"-c",
|
|
276
|
-
"safe.bareRepository=explicit",
|
|
277
|
-
"-c",
|
|
278
|
-
"core.hooksPath=/dev/null",
|
|
279
|
-
"clone",
|
|
280
|
-
"--template=",
|
|
281
|
-
repoUrl,
|
|
282
|
-
"--depth",
|
|
283
|
-
"1",
|
|
284
|
-
tempDir,
|
|
285
|
-
];
|
|
286
|
-
if (branch) {
|
|
287
|
-
const firstBranchStr = Array.isArray(branch) ? branch[0] : String(branch);
|
|
288
|
-
if (firstBranchStr.startsWith("-")) {
|
|
289
|
-
console.log(
|
|
290
|
-
`Skipping branch clone: invalid branch name ${firstBranchStr}`,
|
|
291
|
-
);
|
|
292
|
-
} else {
|
|
293
|
-
const cloneIndex = gitArgs.indexOf("clone");
|
|
294
|
-
gitArgs.splice(cloneIndex + 1, 0, "--branch", firstBranchStr);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
console.log(
|
|
298
|
-
`Cloning Repo${branch ? ` with branch ${branch}` : ""} to ${tempDir}`,
|
|
299
|
-
);
|
|
300
|
-
const gitAllowProtocol = getGitAllowProtocol();
|
|
301
|
-
const envConfigs = {
|
|
302
|
-
GIT_CONFIG_COUNT: "2",
|
|
303
|
-
GIT_CONFIG_KEY_0: "core.fsmonitor",
|
|
304
|
-
GIT_CONFIG_VALUE_0: "false",
|
|
305
|
-
GIT_CONFIG_KEY_1: "safe.bareRepository",
|
|
306
|
-
GIT_CONFIG_VALUE_1: "explicit",
|
|
307
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
308
|
-
};
|
|
309
|
-
const env = isSecureMode
|
|
310
|
-
? {
|
|
311
|
-
...process.env,
|
|
312
|
-
...envConfigs,
|
|
313
|
-
GIT_CONFIG_NOSYSTEM: "1",
|
|
314
|
-
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
315
|
-
GIT_ALLOW_PROTOCOL: gitAllowProtocol,
|
|
316
|
-
}
|
|
317
|
-
: {
|
|
318
|
-
...process.env,
|
|
319
|
-
...envConfigs,
|
|
320
|
-
GIT_ALLOW_PROTOCOL: gitAllowProtocol,
|
|
321
|
-
};
|
|
322
|
-
const result = safeSpawnSync("git", gitArgs, {
|
|
323
|
-
shell: false,
|
|
324
|
-
env,
|
|
325
|
-
});
|
|
326
|
-
if (result.status !== 0) {
|
|
327
|
-
console.log(result.stderr);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return tempDir;
|
|
331
|
-
}
|
|
103
|
+
app.use(
|
|
104
|
+
bodyParser.json({
|
|
105
|
+
deflate: true,
|
|
106
|
+
limit: "1mb",
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
app.use(compression());
|
|
332
110
|
|
|
333
111
|
function sanitizeStr(s) {
|
|
334
112
|
return s ? s.replace(/[\r\n]/g, "") : s;
|
|
@@ -512,7 +290,10 @@ const start = (options) => {
|
|
|
512
290
|
process.exit(1);
|
|
513
291
|
}
|
|
514
292
|
}
|
|
515
|
-
if (
|
|
293
|
+
if (
|
|
294
|
+
!process.env.CDXGEN_GIT_ALLOWED_HOSTS &&
|
|
295
|
+
!process.env.CDXGEN_SERVER_ALLOWED_HOSTS
|
|
296
|
+
) {
|
|
516
297
|
console.log(
|
|
517
298
|
"No allowlist for git hosts has been specified. This is a security risk that could expose the system to SSRF vulnerabilities!",
|
|
518
299
|
);
|
|
@@ -520,7 +301,11 @@ const start = (options) => {
|
|
|
520
301
|
process.exit(1);
|
|
521
302
|
}
|
|
522
303
|
}
|
|
523
|
-
if (
|
|
304
|
+
if (
|
|
305
|
+
isSecureMode &&
|
|
306
|
+
!process.env.CDXGEN_ALLOWED_PATHS &&
|
|
307
|
+
!process.env.CDXGEN_SERVER_ALLOWED_PATHS
|
|
308
|
+
) {
|
|
524
309
|
console.log(
|
|
525
310
|
"No allowlist for paths has been specified. This is a security risk that could expose the filesystem and internal secrets!",
|
|
526
311
|
);
|
|
@@ -588,100 +373,204 @@ const start = (options) => {
|
|
|
588
373
|
}),
|
|
589
374
|
);
|
|
590
375
|
}
|
|
591
|
-
|
|
592
|
-
if (validationError) {
|
|
593
|
-
res.writeHead(validationError.status, {
|
|
594
|
-
"Content-Type": "application/json",
|
|
595
|
-
});
|
|
596
|
-
return res.end(
|
|
597
|
-
JSON.stringify({
|
|
598
|
-
error: validationError.error,
|
|
599
|
-
details: validationError.details,
|
|
600
|
-
}),
|
|
601
|
-
);
|
|
602
|
-
}
|
|
376
|
+
let cloneDir;
|
|
603
377
|
let srcDir;
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
+
});
|
|
614
421
|
return res.end(
|
|
615
422
|
JSON.stringify({
|
|
616
|
-
error:
|
|
617
|
-
details:
|
|
423
|
+
error: validationError.error,
|
|
424
|
+
details: validationError.details,
|
|
618
425
|
}),
|
|
619
426
|
);
|
|
620
427
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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" });
|
|
637
478
|
return res.end(
|
|
638
479
|
JSON.stringify({
|
|
639
|
-
error: "
|
|
640
|
-
details: "
|
|
480
|
+
error: "Absolute path needed",
|
|
481
|
+
details: "Relative paths are not supported in server mode.",
|
|
641
482
|
}),
|
|
642
483
|
);
|
|
643
484
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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);
|
|
648
495
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const errorMessages = error.response?.body?.errors;
|
|
657
|
-
if (errorMessages) {
|
|
658
|
-
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" });
|
|
659
503
|
return res.end(
|
|
660
504
|
JSON.stringify({
|
|
661
|
-
error: "
|
|
662
|
-
details:
|
|
505
|
+
error: "Invalid Server URL",
|
|
506
|
+
details: "The Dependency-Track server URL is invalid.",
|
|
663
507
|
}),
|
|
664
508
|
);
|
|
665
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
|
+
}
|
|
666
543
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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);
|
|
677
571
|
}
|
|
678
|
-
}
|
|
679
|
-
res.end("\n");
|
|
680
|
-
if (cleanup && srcDir?.startsWith(getTmpDir()) && fs.rmSync) {
|
|
681
|
-
console.log(`Cleaning up ${srcDir}`);
|
|
682
|
-
fs.rmSync(srcDir, { recursive: true, force: true });
|
|
683
572
|
}
|
|
684
573
|
});
|
|
685
574
|
};
|
|
686
575
|
|
|
687
|
-
export { configureServer,
|
|
576
|
+
export { configureServer, start };
|