@cyclonedx/cdxgen 12.1.5 → 12.2.1
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 +51 -40
- package/bin/cdxgen.js +194 -97
- package/bin/evinse.js +4 -4
- package/bin/repl.js +1 -1
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +449 -429
- package/lib/cli/index.poku.js +117 -0
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +2 -14
- package/lib/helpers/analyzer.js +606 -3
- package/lib/helpers/analyzer.poku.js +230 -0
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +219 -0
- package/lib/helpers/depsUtils.poku.js +207 -0
- package/lib/helpers/display.js +426 -5
- package/lib/helpers/envcontext.js +18 -3
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +9 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/utils.js +865 -416
- package/lib/helpers/utils.poku.js +172 -265
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +3 -9
- package/lib/parsers/npmrc.js +17 -13
- package/lib/parsers/npmrc.poku.js +41 -5
- package/lib/server/openapi.yaml +34 -1
- package/lib/server/server.js +50 -13
- package/lib/server/server.poku.js +332 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +196 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -9
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +10 -1
- package/types/lib/helpers/pythonutils.d.ts.map +1 -1
- 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/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +533 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +4 -1
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +22 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/lib/stages/pregen/env-audit.js +0 -34
- package/lib/stages/pregen/env-audit.poku.js +0 -290
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/lib/stages/pregen/env-audit.d.ts +0 -2
- package/types/lib/stages/pregen/env-audit.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import esmock from "esmock";
|
|
1
5
|
import { afterEach, assert, beforeEach, describe, it } from "poku";
|
|
6
|
+
import sinon from "sinon";
|
|
2
7
|
|
|
3
8
|
import { isWin } from "../helpers/utils.js";
|
|
4
9
|
import {
|
|
@@ -11,53 +16,71 @@ import {
|
|
|
11
16
|
validateAndRejectGitSource,
|
|
12
17
|
} from "./server.js";
|
|
13
18
|
|
|
19
|
+
function nullProtoObj(obj) {
|
|
20
|
+
if (obj === null || typeof obj !== "object") {
|
|
21
|
+
return obj;
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(obj)) {
|
|
24
|
+
return obj.map(nullProtoObj);
|
|
25
|
+
}
|
|
26
|
+
if (Object.prototype.toString.call(obj) === "[object Object]") {
|
|
27
|
+
const result = Object.create(null);
|
|
28
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
29
|
+
result[key] = nullProtoObj(value);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
return obj;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function checkEqual(actual, expected, message) {
|
|
37
|
+
assert.deepStrictEqual(nullProtoObj(actual), nullProtoObj(expected), message);
|
|
38
|
+
}
|
|
39
|
+
|
|
14
40
|
it("parseValue tests", () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
checkEqual(parseValue("foo"), "foo");
|
|
42
|
+
checkEqual(parseValue("foo\n"), "foo");
|
|
43
|
+
checkEqual(parseValue("foo\r\n"), "foo");
|
|
44
|
+
checkEqual(parseValue(1), 1);
|
|
45
|
+
checkEqual(parseValue("true"), true);
|
|
46
|
+
checkEqual(parseValue("false"), false);
|
|
47
|
+
checkEqual(parseValue(["foo", "bar", 42]), ["foo", "bar", 42]);
|
|
22
48
|
assert.throws(() => parseValue({ foo: "bar" }), TypeError);
|
|
23
49
|
assert.throws(() => parseValue([42, "foo", { foo: "bar" }]), TypeError);
|
|
24
50
|
assert.throws(() => parseValue([42, "foo", new Error()]), TypeError);
|
|
25
51
|
assert.throws(() => parseValue(["foo", "bar", new String(42)]), TypeError);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
checkEqual(parseValue(true), true);
|
|
53
|
+
checkEqual(parseValue(false), false);
|
|
54
|
+
checkEqual(parseValue(null), null);
|
|
55
|
+
checkEqual(parseValue(undefined), undefined);
|
|
56
|
+
checkEqual(parseValue([null, undefined, null]), [null, undefined, null]);
|
|
57
|
+
checkEqual(parseValue(""), "");
|
|
58
|
+
checkEqual(parseValue(" \n"), " ");
|
|
59
|
+
checkEqual(parseValue("42"), "42");
|
|
60
|
+
checkEqual(parseValue("0"), "0");
|
|
61
|
+
checkEqual(parseValue("-1"), "-1");
|
|
62
|
+
checkEqual(parseValue("True"), "True");
|
|
63
|
+
checkEqual(parseValue("False"), "False");
|
|
64
|
+
checkEqual(parseValue(" TRUE "), " TRUE ");
|
|
65
|
+
checkEqual(parseValue(["true", "false", 0, "0", null, undefined]), [
|
|
66
|
+
true,
|
|
67
|
+
false,
|
|
68
|
+
0,
|
|
69
|
+
"0",
|
|
31
70
|
null,
|
|
32
71
|
undefined,
|
|
33
|
-
null,
|
|
34
72
|
]);
|
|
35
|
-
assert.deepStrictEqual(parseValue(""), "");
|
|
36
|
-
assert.deepStrictEqual(parseValue(" \n"), " ");
|
|
37
|
-
assert.deepStrictEqual(parseValue("42"), "42");
|
|
38
|
-
assert.deepStrictEqual(parseValue("0"), "0");
|
|
39
|
-
assert.deepStrictEqual(parseValue("-1"), "-1");
|
|
40
|
-
assert.deepStrictEqual(parseValue("True"), "True");
|
|
41
|
-
assert.deepStrictEqual(parseValue("False"), "False");
|
|
42
|
-
assert.deepStrictEqual(parseValue(" TRUE "), " TRUE ");
|
|
43
|
-
assert.deepStrictEqual(
|
|
44
|
-
parseValue(["true", "false", 0, "0", null, undefined]),
|
|
45
|
-
[true, false, 0, "0", null, undefined],
|
|
46
|
-
);
|
|
47
73
|
assert.throws(() => parseValue([["nested"]]), TypeError);
|
|
48
74
|
assert.throws(() => parseValue(Symbol("test")), TypeError);
|
|
49
75
|
assert.throws(() => parseValue(BigInt(42)), TypeError);
|
|
50
76
|
// biome-ignore-start lint/suspicious/noEmptyBlockStatements: test
|
|
51
77
|
assert.throws(() => parseValue(() => {}), TypeError);
|
|
52
78
|
// biome-ignore-end lint/suspicious/noEmptyBlockStatements: test
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
parseValue(Number.POSITIVE_INFINITY),
|
|
56
|
-
Number.POSITIVE_INFINITY,
|
|
57
|
-
);
|
|
79
|
+
checkEqual(parseValue(Number.NaN), Number.NaN);
|
|
80
|
+
checkEqual(parseValue(Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
58
81
|
const obj = { toString: () => "foo" };
|
|
59
82
|
assert.throws(() => parseValue(obj), TypeError);
|
|
60
|
-
|
|
83
|
+
checkEqual(parseValue("hello\r\n"), "hello");
|
|
61
84
|
});
|
|
62
85
|
|
|
63
86
|
describe("parseQueryString tests", () => {
|
|
@@ -70,22 +93,42 @@ describe("parseQueryString tests", () => {
|
|
|
70
93
|
};
|
|
71
94
|
const options = {};
|
|
72
95
|
const result = parseQueryString(q, body, options);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
96
|
+
checkEqual(result.foo, undefined);
|
|
97
|
+
checkEqual(result.excludeType, ["2"]);
|
|
98
|
+
checkEqual(result.technique, ["manifest-analysis"]);
|
|
76
99
|
});
|
|
77
100
|
|
|
78
101
|
it("splits type into projectType and removes type", () => {
|
|
79
102
|
const options = { type: "a,b,c" };
|
|
80
103
|
const result = parseQueryString({}, {}, options);
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
checkEqual(result.projectType, ["a", "b", "c"]);
|
|
105
|
+
checkEqual(result.type, undefined);
|
|
83
106
|
});
|
|
84
107
|
|
|
85
108
|
it("sets installDeps to false for pre-build lifecycle", () => {
|
|
86
109
|
const options = { lifecycle: "pre-build" };
|
|
87
110
|
const result = parseQueryString({}, {}, options);
|
|
88
|
-
|
|
111
|
+
checkEqual(result.installDeps, false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("parses parentProjectName and parentProjectVersion", () => {
|
|
115
|
+
const q = {
|
|
116
|
+
parentProjectName: "parent-app",
|
|
117
|
+
parentProjectVersion: "1.2.3",
|
|
118
|
+
};
|
|
119
|
+
const result = parseQueryString(q, {}, {});
|
|
120
|
+
checkEqual(result.parentProjectName, "parent-app");
|
|
121
|
+
checkEqual(result.parentProjectVersion, "1.2.3");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("parses autoCreate and isLatest boolean options", () => {
|
|
125
|
+
const q = {
|
|
126
|
+
autoCreate: "false",
|
|
127
|
+
isLatest: "true",
|
|
128
|
+
};
|
|
129
|
+
const result = parseQueryString(q, {}, {});
|
|
130
|
+
checkEqual(result.autoCreate, false);
|
|
131
|
+
checkEqual(result.isLatest, true);
|
|
89
132
|
});
|
|
90
133
|
});
|
|
91
134
|
|
|
@@ -102,23 +145,23 @@ describe("isAllowedHost()", () => {
|
|
|
102
145
|
|
|
103
146
|
it("returns true if CDXGEN_SERVER_ALLOWED_HOSTS is not set", () => {
|
|
104
147
|
delete process.env.CDXGEN_SERVER_ALLOWED_HOSTS;
|
|
105
|
-
|
|
148
|
+
checkEqual(isAllowedHost("anything"), true);
|
|
106
149
|
});
|
|
107
150
|
|
|
108
151
|
it("returns true for a hostname that is in the list", () => {
|
|
109
152
|
process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "foo.com,bar.com";
|
|
110
|
-
|
|
111
|
-
|
|
153
|
+
checkEqual(isAllowedHost("foo.com"), true);
|
|
154
|
+
checkEqual(isAllowedHost("bar.com"), true);
|
|
112
155
|
});
|
|
113
156
|
|
|
114
157
|
it("returns false for a hostname not in the list", () => {
|
|
115
158
|
process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "foo.com,bar.com";
|
|
116
|
-
|
|
159
|
+
checkEqual(isAllowedHost("baz.com"), false);
|
|
117
160
|
});
|
|
118
161
|
|
|
119
162
|
it("treats an empty-string env var as unset (returns true)", () => {
|
|
120
163
|
process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "";
|
|
121
|
-
|
|
164
|
+
checkEqual(isAllowedHost("whatever"), true);
|
|
122
165
|
});
|
|
123
166
|
});
|
|
124
167
|
|
|
@@ -203,15 +246,15 @@ describe("isAllowedPath()", () => {
|
|
|
203
246
|
describe("isAllowedWinPath windows tests()", () => {
|
|
204
247
|
it("returns false for windows device name paths", () => {
|
|
205
248
|
if (isWin) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
249
|
+
checkEqual(isAllowedWinPath("CON:../foo"), false);
|
|
250
|
+
checkEqual(isAllowedWinPath("X:\\foo\\..\\bar"), true);
|
|
251
|
+
checkEqual(isAllowedWinPath("C:\\Users"), true);
|
|
252
|
+
checkEqual(isAllowedWinPath("C:\\🚀"), true);
|
|
253
|
+
checkEqual(isAllowedWinPath("C:"), true);
|
|
254
|
+
checkEqual(isAllowedWinPath("c:"), true);
|
|
255
|
+
checkEqual(isAllowedWinPath("CON:"), false);
|
|
256
|
+
checkEqual(isAllowedWinPath("COM¹:"), false);
|
|
257
|
+
checkEqual(isAllowedWinPath("COM¹:../foo"), false);
|
|
215
258
|
for (const d of [
|
|
216
259
|
"PRN:.\\..\\bar",
|
|
217
260
|
"LpT5:/another/path",
|
|
@@ -244,7 +287,7 @@ describe("isAllowedWinPath windows tests()", () => {
|
|
|
244
287
|
"🚀:\\",
|
|
245
288
|
"⚡:\\",
|
|
246
289
|
]) {
|
|
247
|
-
|
|
290
|
+
checkEqual(isAllowedWinPath(d), false);
|
|
248
291
|
}
|
|
249
292
|
}
|
|
250
293
|
});
|
|
@@ -264,7 +307,7 @@ describe("getQueryParams", () => {
|
|
|
264
307
|
);
|
|
265
308
|
const result = getQueryParams(req);
|
|
266
309
|
|
|
267
|
-
|
|
310
|
+
checkEqual(result, {
|
|
268
311
|
url: "https://example.com",
|
|
269
312
|
multiProject: "true",
|
|
270
313
|
type: "js",
|
|
@@ -277,7 +320,7 @@ describe("getQueryParams", () => {
|
|
|
277
320
|
);
|
|
278
321
|
const result = getQueryParams(req);
|
|
279
322
|
|
|
280
|
-
|
|
323
|
+
checkEqual(result, {
|
|
281
324
|
q: "hello world",
|
|
282
325
|
filter: "category=tech",
|
|
283
326
|
});
|
|
@@ -288,7 +331,7 @@ describe("getQueryParams", () => {
|
|
|
288
331
|
const result = getQueryParams(req);
|
|
289
332
|
|
|
290
333
|
// URLSearchParams.entries() returns the first value when there are duplicates
|
|
291
|
-
|
|
334
|
+
checkEqual(result, {
|
|
292
335
|
tags: ["javascript", "react", "node"],
|
|
293
336
|
});
|
|
294
337
|
});
|
|
@@ -297,21 +340,21 @@ describe("getQueryParams", () => {
|
|
|
297
340
|
const req = createMockRequest("/sbom");
|
|
298
341
|
const result = getQueryParams(req);
|
|
299
342
|
|
|
300
|
-
|
|
343
|
+
checkEqual(result, {});
|
|
301
344
|
});
|
|
302
345
|
|
|
303
346
|
it("should handle query string with only question mark", () => {
|
|
304
347
|
const req = createMockRequest("/sbom?");
|
|
305
348
|
const result = getQueryParams(req);
|
|
306
349
|
|
|
307
|
-
|
|
350
|
+
checkEqual(result, {});
|
|
308
351
|
});
|
|
309
352
|
|
|
310
353
|
it("should handle parameters without values", () => {
|
|
311
354
|
const req = createMockRequest("/api?flag1&flag2¶m=value");
|
|
312
355
|
const result = getQueryParams(req);
|
|
313
356
|
|
|
314
|
-
|
|
357
|
+
checkEqual(result, {
|
|
315
358
|
flag1: "",
|
|
316
359
|
flag2: "",
|
|
317
360
|
param: "value",
|
|
@@ -325,7 +368,7 @@ describe("getQueryParams", () => {
|
|
|
325
368
|
);
|
|
326
369
|
const result = getQueryParams(req);
|
|
327
370
|
|
|
328
|
-
|
|
371
|
+
checkEqual(result, {
|
|
329
372
|
param1: "value1",
|
|
330
373
|
});
|
|
331
374
|
});
|
|
@@ -338,7 +381,7 @@ describe("getQueryParams", () => {
|
|
|
338
381
|
);
|
|
339
382
|
const result = getQueryParams(req);
|
|
340
383
|
|
|
341
|
-
|
|
384
|
+
checkEqual(result, {
|
|
342
385
|
token: "abc123",
|
|
343
386
|
});
|
|
344
387
|
});
|
|
@@ -349,7 +392,7 @@ describe("getQueryParams", () => {
|
|
|
349
392
|
);
|
|
350
393
|
const result = getQueryParams(req);
|
|
351
394
|
|
|
352
|
-
|
|
395
|
+
checkEqual(result, {
|
|
353
396
|
name: "john",
|
|
354
397
|
age: "25",
|
|
355
398
|
active: "true",
|
|
@@ -362,7 +405,7 @@ describe("getQueryParams", () => {
|
|
|
362
405
|
);
|
|
363
406
|
const result = getQueryParams(req);
|
|
364
407
|
|
|
365
|
-
|
|
408
|
+
checkEqual(result, {
|
|
366
409
|
q: "hello world!",
|
|
367
410
|
category: "web development",
|
|
368
411
|
});
|
|
@@ -372,14 +415,14 @@ describe("getQueryParams", () => {
|
|
|
372
415
|
const req = createMockRequest(undefined);
|
|
373
416
|
const result = getQueryParams(req);
|
|
374
417
|
|
|
375
|
-
|
|
418
|
+
checkEqual(result, {});
|
|
376
419
|
});
|
|
377
420
|
|
|
378
421
|
it("should handle numeric values as strings", () => {
|
|
379
422
|
const req = createMockRequest("/calculate?x=10&y=20&operation=add");
|
|
380
423
|
const result = getQueryParams(req);
|
|
381
424
|
|
|
382
|
-
|
|
425
|
+
checkEqual(result, {
|
|
383
426
|
x: "10",
|
|
384
427
|
y: "20",
|
|
385
428
|
operation: "add",
|
|
@@ -390,7 +433,7 @@ describe("getQueryParams", () => {
|
|
|
390
433
|
const req = createMockRequest("/config?debug=true&verbose=false&enabled=1");
|
|
391
434
|
const result = getQueryParams(req);
|
|
392
435
|
|
|
393
|
-
|
|
436
|
+
checkEqual(result, {
|
|
394
437
|
debug: "true",
|
|
395
438
|
verbose: "false",
|
|
396
439
|
enabled: "1",
|
|
@@ -402,7 +445,7 @@ describe("getQueryParams", () => {
|
|
|
402
445
|
const req = createMockRequest("not-a-valid-url");
|
|
403
446
|
const result = getQueryParams(req);
|
|
404
447
|
|
|
405
|
-
|
|
448
|
+
checkEqual(result, {});
|
|
406
449
|
});
|
|
407
450
|
|
|
408
451
|
it("should handle empty host gracefully", () => {
|
|
@@ -413,7 +456,7 @@ describe("getQueryParams", () => {
|
|
|
413
456
|
};
|
|
414
457
|
const result = getQueryParams(req);
|
|
415
458
|
|
|
416
|
-
|
|
459
|
+
checkEqual(result, {
|
|
417
460
|
param: "value",
|
|
418
461
|
});
|
|
419
462
|
});
|
|
@@ -426,7 +469,7 @@ describe("getQueryParams", () => {
|
|
|
426
469
|
};
|
|
427
470
|
const result = getQueryParams(req);
|
|
428
471
|
|
|
429
|
-
|
|
472
|
+
checkEqual(result, {
|
|
430
473
|
param: "value",
|
|
431
474
|
});
|
|
432
475
|
});
|
|
@@ -450,84 +493,60 @@ describe("validateGitSource() tests", () => {
|
|
|
450
493
|
});
|
|
451
494
|
|
|
452
495
|
it("should reject ext:: and fd:: outright", () => {
|
|
453
|
-
|
|
496
|
+
checkEqual(
|
|
454
497
|
validateAndRejectGitSource("ext::sh -c id").error,
|
|
455
498
|
"Invalid Protocol",
|
|
456
499
|
);
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
"Invalid Protocol",
|
|
460
|
-
);
|
|
461
|
-
assert.deepStrictEqual(
|
|
500
|
+
checkEqual(validateAndRejectGitSource("fd::123").error, "Invalid Protocol");
|
|
501
|
+
checkEqual(
|
|
462
502
|
validateAndRejectGitSource("EXT::sh -c id").error,
|
|
463
503
|
"Invalid Protocol",
|
|
464
504
|
);
|
|
465
505
|
});
|
|
466
506
|
|
|
467
507
|
it("should allow standard local paths to bypass validation", () => {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
validateAndRejectGitSource("C:\\Users\\local"),
|
|
471
|
-
null,
|
|
472
|
-
);
|
|
508
|
+
checkEqual(validateAndRejectGitSource("/tmp/local-path"), null);
|
|
509
|
+
checkEqual(validateAndRejectGitSource("C:\\Users\\local"), null);
|
|
473
510
|
});
|
|
474
511
|
|
|
475
512
|
it("should handle ssh git@ format gracefully", () => {
|
|
476
|
-
|
|
477
|
-
validateAndRejectGitSource("git@github.com:foo/bar.git"),
|
|
478
|
-
null,
|
|
479
|
-
);
|
|
513
|
+
checkEqual(validateAndRejectGitSource("git@github.com:foo/bar.git"), null);
|
|
480
514
|
});
|
|
481
515
|
|
|
482
516
|
it("should reject malformed git URLs", () => {
|
|
483
517
|
// invalid URL format (can't parse via node's new URL object)
|
|
484
|
-
|
|
518
|
+
checkEqual(
|
|
485
519
|
validateAndRejectGitSource("http://[:::1]/bad-ipv6").error,
|
|
486
520
|
"Invalid URL Format",
|
|
487
521
|
);
|
|
488
522
|
});
|
|
489
523
|
|
|
490
524
|
it("should enforce GIT_ALLOW_PROTOCOL default schemes", () => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
details: "The protocol 'http:' is not permitted by GIT_ALLOW_PROTOCOL.",
|
|
501
|
-
},
|
|
502
|
-
);
|
|
503
|
-
assert.deepStrictEqual(
|
|
504
|
-
validateAndRejectGitSource("git://github.com/repo"),
|
|
505
|
-
null,
|
|
506
|
-
);
|
|
507
|
-
assert.deepStrictEqual(
|
|
508
|
-
validateAndRejectGitSource("ssh://github.com/repo"),
|
|
509
|
-
null,
|
|
510
|
-
);
|
|
511
|
-
assert.deepStrictEqual(
|
|
512
|
-
validateAndRejectGitSource("git+ssh://github.com/repo"),
|
|
513
|
-
null,
|
|
514
|
-
);
|
|
525
|
+
checkEqual(validateAndRejectGitSource("https://github.com/repo"), null);
|
|
526
|
+
checkEqual(validateAndRejectGitSource("http://github.com/repo"), {
|
|
527
|
+
status: 400,
|
|
528
|
+
error: "Protocol Not Allowed",
|
|
529
|
+
details: "The protocol 'http:' is not permitted by GIT_ALLOW_PROTOCOL.",
|
|
530
|
+
});
|
|
531
|
+
checkEqual(validateAndRejectGitSource("git://github.com/repo"), null);
|
|
532
|
+
checkEqual(validateAndRejectGitSource("ssh://github.com/repo"), null);
|
|
533
|
+
checkEqual(validateAndRejectGitSource("git+ssh://github.com/repo"), null);
|
|
515
534
|
|
|
516
535
|
// ftp is not allowed by default
|
|
517
536
|
const res = validateAndRejectGitSource("ftp://github.com/repo");
|
|
518
|
-
|
|
519
|
-
|
|
537
|
+
checkEqual(res.error, "Protocol Not Allowed");
|
|
538
|
+
checkEqual(
|
|
520
539
|
res.details,
|
|
521
540
|
"The protocol 'ftp:' is not permitted by GIT_ALLOW_PROTOCOL.",
|
|
522
541
|
);
|
|
523
542
|
});
|
|
524
543
|
|
|
525
544
|
it("should reject protocol smuggling techniques", () => {
|
|
526
|
-
|
|
545
|
+
checkEqual(
|
|
527
546
|
validateAndRejectGitSource("git+ext://github.com/repo").error,
|
|
528
547
|
"Protocol Not Allowed",
|
|
529
548
|
);
|
|
530
|
-
|
|
549
|
+
checkEqual(
|
|
531
550
|
validateAndRejectGitSource("http+ext://github.com/repo").error,
|
|
532
551
|
"Protocol Not Allowed",
|
|
533
552
|
);
|
|
@@ -535,30 +554,24 @@ describe("validateGitSource() tests", () => {
|
|
|
535
554
|
|
|
536
555
|
it("should respect custom CDXGEN_SERVER_GIT_ALLOW_PROTOCOL configs", () => {
|
|
537
556
|
process.env.CDXGEN_SERVER_GIT_ALLOW_PROTOCOL = "https:git";
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
null,
|
|
541
|
-
);
|
|
542
|
-
assert.deepStrictEqual(
|
|
543
|
-
validateAndRejectGitSource("git://github.com/repo"),
|
|
544
|
-
null,
|
|
545
|
-
);
|
|
557
|
+
checkEqual(validateAndRejectGitSource("https://github.com/repo"), null);
|
|
558
|
+
checkEqual(validateAndRejectGitSource("git://github.com/repo"), null);
|
|
546
559
|
|
|
547
560
|
// http is no longer allowed
|
|
548
561
|
const res = validateAndRejectGitSource("http://github.com/repo");
|
|
549
|
-
|
|
550
|
-
|
|
562
|
+
checkEqual(res.error, "Protocol Not Allowed");
|
|
563
|
+
checkEqual(
|
|
551
564
|
res.details,
|
|
552
565
|
"The protocol 'http:' is not permitted by GIT_ALLOW_PROTOCOL.",
|
|
553
566
|
);
|
|
554
567
|
});
|
|
555
568
|
|
|
556
569
|
it("should reject remote helper syntax (::) inside valid schemes", () => {
|
|
557
|
-
|
|
570
|
+
checkEqual(
|
|
558
571
|
validateAndRejectGitSource("https://github.com/ext::sh -c id").error,
|
|
559
572
|
"Invalid URL Syntax",
|
|
560
573
|
);
|
|
561
|
-
|
|
574
|
+
checkEqual(
|
|
562
575
|
validateAndRejectGitSource("git://foo::bar/repo").error,
|
|
563
576
|
"Invalid URL Format",
|
|
564
577
|
);
|
|
@@ -566,44 +579,219 @@ describe("validateGitSource() tests", () => {
|
|
|
566
579
|
|
|
567
580
|
it("should validate allowed hosts", () => {
|
|
568
581
|
process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "github.com,gitlab.com";
|
|
569
|
-
|
|
570
|
-
validateAndRejectGitSource("https://github.com/repo"),
|
|
571
|
-
null,
|
|
572
|
-
);
|
|
582
|
+
checkEqual(validateAndRejectGitSource("https://github.com/repo"), null);
|
|
573
583
|
|
|
574
584
|
const res = validateAndRejectGitSource("https://evil.com/repo");
|
|
575
|
-
|
|
576
|
-
|
|
585
|
+
checkEqual(res.error, "Host Not Allowed");
|
|
586
|
+
checkEqual(res.status, 403);
|
|
577
587
|
});
|
|
578
588
|
});
|
|
579
589
|
it("should correctly normalize and validate various git@ (SCP-like) formats", () => {
|
|
580
|
-
|
|
590
|
+
checkEqual(
|
|
581
591
|
validateAndRejectGitSource("git@gitlab.com:group/project.git"),
|
|
582
592
|
null,
|
|
583
593
|
);
|
|
584
|
-
|
|
594
|
+
checkEqual(
|
|
585
595
|
validateAndRejectGitSource("git@bitbucket.org:workspace/repo:name.git"),
|
|
586
596
|
null,
|
|
587
597
|
);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
null,
|
|
591
|
-
);
|
|
592
|
-
assert.deepStrictEqual(
|
|
598
|
+
checkEqual(validateAndRejectGitSource("git@github.com/user/repo.git"), null);
|
|
599
|
+
checkEqual(
|
|
593
600
|
validateAndRejectGitSource("ssh://git@github.com/user/repo.git"),
|
|
594
601
|
null,
|
|
595
602
|
);
|
|
596
603
|
process.env.CDXGEN_SERVER_ALLOWED_HOSTS = "github.com,bitbucket.org";
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
null,
|
|
600
|
-
);
|
|
601
|
-
assert.deepStrictEqual(
|
|
604
|
+
checkEqual(validateAndRejectGitSource("git@github.com:user/repo.git"), null);
|
|
605
|
+
checkEqual(
|
|
602
606
|
validateAndRejectGitSource("git@bitbucket.org:workspace/repo.git"),
|
|
603
607
|
null,
|
|
604
608
|
);
|
|
605
609
|
const deniedRes = validateAndRejectGitSource("git@evil.com:foo/bar.git");
|
|
606
|
-
|
|
607
|
-
|
|
610
|
+
checkEqual(deniedRes.status, 403);
|
|
611
|
+
checkEqual(deniedRes.error, "Host Not Allowed");
|
|
608
612
|
delete process.env.CDXGEN_SERVER_ALLOWED_HOSTS;
|
|
609
613
|
});
|
|
614
|
+
|
|
615
|
+
describe("gitClone() hardening tests", () => {
|
|
616
|
+
it("passes core.hooksPath=/dev/null and --template= flags to git", async () => {
|
|
617
|
+
const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
|
|
618
|
+
const mkdtempStub = sinon
|
|
619
|
+
.stub()
|
|
620
|
+
.returns(path.join(os.tmpdir(), "fake-repo"));
|
|
621
|
+
|
|
622
|
+
const { gitClone } = await esmock("./server.js", {
|
|
623
|
+
"../helpers/utils.js": {
|
|
624
|
+
safeSpawnSync: spawnStub,
|
|
625
|
+
isSecureMode: false,
|
|
626
|
+
hasDangerousUnicode: sinon.stub().returns(false),
|
|
627
|
+
getTmpDir: sinon.stub().returns(os.tmpdir()),
|
|
628
|
+
},
|
|
629
|
+
"node:fs": {
|
|
630
|
+
mkdtempSync: mkdtempStub,
|
|
631
|
+
existsSync: sinon.stub().returns(false),
|
|
632
|
+
readdirSync: sinon.stub().returns([]),
|
|
633
|
+
statSync: sinon.stub().returns({ isDirectory: () => true }),
|
|
634
|
+
readFileSync: sinon.stub().returns(""),
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
gitClone("https://example.com/repo.git");
|
|
639
|
+
|
|
640
|
+
sinon.assert.calledOnce(spawnStub);
|
|
641
|
+
const [cmd, args] = spawnStub.firstCall.args;
|
|
642
|
+
assert.strictEqual(cmd, "git");
|
|
643
|
+
|
|
644
|
+
// core.hooksPath=/dev/null must be present as a -c flag
|
|
645
|
+
const hooksPathIdx = args.indexOf("core.hooksPath=/dev/null");
|
|
646
|
+
assert.ok(
|
|
647
|
+
hooksPathIdx > 0 && args[hooksPathIdx - 1] === "-c",
|
|
648
|
+
"expected -c core.hooksPath=/dev/null in git args",
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
// --template= must appear after "clone"
|
|
652
|
+
const cloneIdx = args.indexOf("clone");
|
|
653
|
+
assert.ok(cloneIdx >= 0, "expected 'clone' subcommand in git args");
|
|
654
|
+
assert.ok(
|
|
655
|
+
args.slice(cloneIdx).includes("--template="),
|
|
656
|
+
"expected --template= after clone in git args",
|
|
657
|
+
);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("uses GIT_CONFIG_GLOBAL=/dev/null instead of invalid GIT_CONFIG_NOGLOBAL in secure mode", async () => {
|
|
661
|
+
const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
|
|
662
|
+
const mkdtempStub = sinon
|
|
663
|
+
.stub()
|
|
664
|
+
.returns(path.join(os.tmpdir(), "fake-repo"));
|
|
665
|
+
|
|
666
|
+
const { gitClone } = await esmock("./server.js", {
|
|
667
|
+
"../helpers/utils.js": {
|
|
668
|
+
safeSpawnSync: spawnStub,
|
|
669
|
+
isSecureMode: true,
|
|
670
|
+
hasDangerousUnicode: sinon.stub().returns(false),
|
|
671
|
+
getTmpDir: sinon.stub().returns(os.tmpdir()),
|
|
672
|
+
},
|
|
673
|
+
"node:fs": {
|
|
674
|
+
mkdtempSync: mkdtempStub,
|
|
675
|
+
existsSync: sinon.stub().returns(false),
|
|
676
|
+
readdirSync: sinon.stub().returns([]),
|
|
677
|
+
statSync: sinon.stub().returns({ isDirectory: () => true }),
|
|
678
|
+
readFileSync: sinon.stub().returns(""),
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
gitClone("https://example.com/repo.git");
|
|
683
|
+
|
|
684
|
+
sinon.assert.calledOnce(spawnStub);
|
|
685
|
+
const opts = spawnStub.firstCall.args[2];
|
|
686
|
+
assert.ok(opts.env, "expected env to be set");
|
|
687
|
+
assert.ok(
|
|
688
|
+
!("GIT_CONFIG_NOGLOBAL" in opts.env),
|
|
689
|
+
"GIT_CONFIG_NOGLOBAL must not be set (it is not a valid Git env var)",
|
|
690
|
+
);
|
|
691
|
+
assert.strictEqual(
|
|
692
|
+
opts.env.GIT_CONFIG_GLOBAL,
|
|
693
|
+
"/dev/null",
|
|
694
|
+
"GIT_CONFIG_GLOBAL must be /dev/null in secure mode",
|
|
695
|
+
);
|
|
696
|
+
assert.strictEqual(
|
|
697
|
+
opts.env.GIT_CONFIG_NOSYSTEM,
|
|
698
|
+
"1",
|
|
699
|
+
"GIT_CONFIG_NOSYSTEM must be '1' in secure mode",
|
|
700
|
+
);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it("sets GIT_TERMINAL_PROMPT=0 in both secure and non-secure mode", async () => {
|
|
704
|
+
for (const secureMode of [false, true]) {
|
|
705
|
+
const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
|
|
706
|
+
const mkdtempStub = sinon
|
|
707
|
+
.stub()
|
|
708
|
+
.returns(path.join(os.tmpdir(), "fake-repo"));
|
|
709
|
+
|
|
710
|
+
const { gitClone } = await esmock("./server.js", {
|
|
711
|
+
"../helpers/utils.js": {
|
|
712
|
+
safeSpawnSync: spawnStub,
|
|
713
|
+
isSecureMode: secureMode,
|
|
714
|
+
hasDangerousUnicode: sinon.stub().returns(false),
|
|
715
|
+
getTmpDir: sinon.stub().returns(os.tmpdir()),
|
|
716
|
+
},
|
|
717
|
+
"node:fs": {
|
|
718
|
+
mkdtempSync: mkdtempStub,
|
|
719
|
+
existsSync: sinon.stub().returns(false),
|
|
720
|
+
readdirSync: sinon.stub().returns([]),
|
|
721
|
+
statSync: sinon.stub().returns({ isDirectory: () => true }),
|
|
722
|
+
readFileSync: sinon.stub().returns(""),
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
gitClone("https://example.com/repo.git");
|
|
727
|
+
|
|
728
|
+
const opts = spawnStub.firstCall.args[2];
|
|
729
|
+
assert.strictEqual(
|
|
730
|
+
opts.env.GIT_TERMINAL_PROMPT,
|
|
731
|
+
"0",
|
|
732
|
+
`GIT_TERMINAL_PROMPT must be '0' when isSecureMode=${secureMode}`,
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("inserts --branch before repoUrl when a valid branch is specified", async () => {
|
|
738
|
+
const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
|
|
739
|
+
const mkdtempStub = sinon
|
|
740
|
+
.stub()
|
|
741
|
+
.returns(path.join(os.tmpdir(), "fake-repo"));
|
|
742
|
+
|
|
743
|
+
const { gitClone } = await esmock("./server.js", {
|
|
744
|
+
"../helpers/utils.js": {
|
|
745
|
+
safeSpawnSync: spawnStub,
|
|
746
|
+
isSecureMode: false,
|
|
747
|
+
hasDangerousUnicode: sinon.stub().returns(false),
|
|
748
|
+
getTmpDir: sinon.stub().returns(os.tmpdir()),
|
|
749
|
+
},
|
|
750
|
+
"node:fs": {
|
|
751
|
+
mkdtempSync: mkdtempStub,
|
|
752
|
+
existsSync: sinon.stub().returns(false),
|
|
753
|
+
readdirSync: sinon.stub().returns([]),
|
|
754
|
+
statSync: sinon.stub().returns({ isDirectory: () => true }),
|
|
755
|
+
readFileSync: sinon.stub().returns(""),
|
|
756
|
+
},
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
gitClone("https://example.com/repo.git", "main");
|
|
760
|
+
|
|
761
|
+
const [, args] = spawnStub.firstCall.args;
|
|
762
|
+
const branchIdx = args.indexOf("--branch");
|
|
763
|
+
assert.ok(branchIdx >= 0, "expected --branch in git args");
|
|
764
|
+
assert.strictEqual(args[branchIdx + 1], "main");
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it("skips --branch when the branch name starts with a dash", async () => {
|
|
768
|
+
const spawnStub = sinon.stub().returns({ status: 0, stderr: "" });
|
|
769
|
+
const mkdtempStub = sinon
|
|
770
|
+
.stub()
|
|
771
|
+
.returns(path.join(os.tmpdir(), "fake-repo"));
|
|
772
|
+
|
|
773
|
+
const { gitClone } = await esmock("./server.js", {
|
|
774
|
+
"../helpers/utils.js": {
|
|
775
|
+
safeSpawnSync: spawnStub,
|
|
776
|
+
isSecureMode: false,
|
|
777
|
+
hasDangerousUnicode: sinon.stub().returns(false),
|
|
778
|
+
getTmpDir: sinon.stub().returns(os.tmpdir()),
|
|
779
|
+
},
|
|
780
|
+
"node:fs": {
|
|
781
|
+
mkdtempSync: mkdtempStub,
|
|
782
|
+
existsSync: sinon.stub().returns(false),
|
|
783
|
+
readdirSync: sinon.stub().returns([]),
|
|
784
|
+
statSync: sinon.stub().returns({ isDirectory: () => true }),
|
|
785
|
+
readFileSync: sinon.stub().returns(""),
|
|
786
|
+
},
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
gitClone("https://example.com/repo.git", "--malicious");
|
|
790
|
+
|
|
791
|
+
const [, args] = spawnStub.firstCall.args;
|
|
792
|
+
assert.ok(
|
|
793
|
+
!args.includes("--branch"),
|
|
794
|
+
"must not include --branch for dash-prefixed branch names",
|
|
795
|
+
);
|
|
796
|
+
});
|
|
797
|
+
});
|