@cyclonedx/cdxgen 12.3.2 → 12.3.3
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 +6 -0
- package/data/rules/ci-permissions.yaml +132 -0
- package/data/rules/dependency-sources.yaml +65 -5
- package/data/rules/package-integrity.yaml +22 -0
- package/lib/cli/index.js +141 -39
- package/lib/cli/index.poku.js +579 -1
- package/lib/helpers/agentFormulationParser.js +6 -2
- package/lib/helpers/agentFormulationParser.poku.js +42 -0
- package/lib/helpers/analyzer.js +38 -9
- package/lib/helpers/analyzer.poku.js +67 -0
- package/lib/helpers/chromextutils.js +25 -3
- package/lib/helpers/chromextutils.poku.js +68 -0
- package/lib/helpers/ciParsers/githubActions.js +79 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
- package/lib/helpers/communityAiConfigParser.js +15 -5
- package/lib/helpers/communityAiConfigParser.poku.js +71 -0
- package/lib/helpers/depsUtils.js +5 -0
- package/lib/helpers/depsUtils.poku.js +55 -0
- package/lib/helpers/display.js +45 -22
- package/lib/helpers/display.poku.js +47 -60
- package/lib/helpers/mcpConfigParser.js +21 -5
- package/lib/helpers/mcpConfigParser.poku.js +39 -2
- package/lib/helpers/propertySanitizer.js +121 -0
- package/lib/helpers/utils.js +951 -40
- package/lib/helpers/utils.poku.js +882 -0
- package/lib/managers/binary.js +16 -0
- package/lib/managers/binary.poku.js +1 -0
- package/lib/managers/docker.js +240 -16
- package/lib/managers/docker.poku.js +1142 -2
- package/lib/server/server.js +7 -4
- package/lib/server/server.poku.js +36 -1
- package/lib/stages/postgen/auditBom.poku.js +644 -2
- package/package.json +2 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/chromextutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +1 -0
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/mcpConfigParser.d.ts +1 -1
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/helpers/propertySanitizer.d.ts +3 -0
- package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +29 -0
- 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 +3 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
|
@@ -208,6 +208,61 @@ describe("trimComponents()", () => {
|
|
|
208
208
|
{ alg: "SHA-256", content: "def456" },
|
|
209
209
|
]);
|
|
210
210
|
});
|
|
211
|
+
|
|
212
|
+
it("retains identity tool references when merging duplicate components", () => {
|
|
213
|
+
const components = [
|
|
214
|
+
{
|
|
215
|
+
name: "openssl",
|
|
216
|
+
version: "3.0.0",
|
|
217
|
+
purl: "pkg:rpm/redhat/openssl@3.0.0",
|
|
218
|
+
type: "library",
|
|
219
|
+
evidence: {
|
|
220
|
+
identity: [
|
|
221
|
+
{
|
|
222
|
+
field: "purl",
|
|
223
|
+
confidence: 1,
|
|
224
|
+
methods: [
|
|
225
|
+
{
|
|
226
|
+
technique: "binary-analysis",
|
|
227
|
+
confidence: 1,
|
|
228
|
+
value: "openssl",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
tools: ["pkg:generic/trivy@0.1.0"],
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "openssl",
|
|
238
|
+
version: "3.0.0",
|
|
239
|
+
purl: "pkg:rpm/redhat/openssl@3.0.0",
|
|
240
|
+
type: "library",
|
|
241
|
+
evidence: {
|
|
242
|
+
identity: [
|
|
243
|
+
{
|
|
244
|
+
field: "purl",
|
|
245
|
+
confidence: 1,
|
|
246
|
+
methods: [
|
|
247
|
+
{
|
|
248
|
+
technique: "binary-analysis",
|
|
249
|
+
confidence: 1,
|
|
250
|
+
value: "openssl",
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
tools: ["pkg:generic/blint@1.2.3"],
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
const result = trimComponents(components);
|
|
260
|
+
assert.strictEqual(result.length, 1);
|
|
261
|
+
assert.deepStrictEqual(result[0].evidence.identity[0].tools, [
|
|
262
|
+
"pkg:generic/trivy@0.1.0",
|
|
263
|
+
"pkg:generic/blint@1.2.3",
|
|
264
|
+
]);
|
|
265
|
+
});
|
|
211
266
|
});
|
|
212
267
|
|
|
213
268
|
describe("mergeServices()", () => {
|
package/lib/helpers/display.js
CHANGED
|
@@ -65,10 +65,44 @@ const formatComponentName = (component, highlight) => {
|
|
|
65
65
|
return displayName;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Builds the summary and provenance lines printed after the component table.
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
72
|
+
* @param {string[]|undefined} filterTypes Optional list of component types to include
|
|
73
|
+
* @param {string|undefined} summaryText Optional summary message to print after the table
|
|
74
|
+
* @param {number} displayedProvenanceCount Number of displayed components with registry provenance
|
|
75
|
+
* @returns {string[]} Summary lines to print
|
|
76
|
+
*/
|
|
77
|
+
export const buildTableSummaryLines = (
|
|
78
|
+
bomJson,
|
|
79
|
+
filterTypes,
|
|
80
|
+
summaryText,
|
|
81
|
+
displayedProvenanceCount = 0,
|
|
82
|
+
) => {
|
|
83
|
+
const summaryLines = [];
|
|
84
|
+
if (summaryText) {
|
|
85
|
+
summaryLines.push(summaryText);
|
|
86
|
+
} else if (!filterTypes) {
|
|
87
|
+
summaryLines.push(
|
|
88
|
+
`BOM includes ${bomJson?.components?.length || 0} components and ${
|
|
89
|
+
bomJson?.dependencies?.length || 0
|
|
90
|
+
} dependencies`,
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
summaryLines.push(
|
|
94
|
+
`Components filtered based on type: ${filterTypes.join(", ")}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (displayedProvenanceCount > 0) {
|
|
98
|
+
summaryLines.push(
|
|
99
|
+
`Legend: ${REGISTRY_PROVENANCE_ICON} = registry provenance or trusted publishing evidence`,
|
|
100
|
+
);
|
|
101
|
+
summaryLines.push(
|
|
102
|
+
`${REGISTRY_PROVENANCE_ICON} ${displayedProvenanceCount} component(s) include registry provenance or trusted publishing metadata.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return summaryLines;
|
|
72
106
|
};
|
|
73
107
|
|
|
74
108
|
/**
|
|
@@ -247,24 +281,13 @@ export function printTable(
|
|
|
247
281
|
}
|
|
248
282
|
stream.end();
|
|
249
283
|
console.log();
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
bomJson?.dependencies?.length || 0,
|
|
258
|
-
"dependencies",
|
|
259
|
-
);
|
|
260
|
-
} else {
|
|
261
|
-
console.log(`Components filtered based on type: ${filterTypes.join(", ")}`);
|
|
262
|
-
}
|
|
263
|
-
if (displayedProvenanceCount > 0) {
|
|
264
|
-
printProvenanceLegend();
|
|
265
|
-
console.log(
|
|
266
|
-
`${REGISTRY_PROVENANCE_ICON} ${displayedProvenanceCount} component(s) include registry provenance or trusted publishing metadata.`,
|
|
267
|
-
);
|
|
284
|
+
for (const line of buildTableSummaryLines(
|
|
285
|
+
bomJson,
|
|
286
|
+
filterTypes,
|
|
287
|
+
summaryText,
|
|
288
|
+
displayedProvenanceCount,
|
|
289
|
+
)) {
|
|
290
|
+
console.log(line);
|
|
268
291
|
}
|
|
269
292
|
}
|
|
270
293
|
const formatProps = (props) => {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
buildActivitySummaryPayload,
|
|
9
9
|
buildDependencyTreeLegendLines,
|
|
10
10
|
buildDependencyTreeLines,
|
|
11
|
+
buildTableSummaryLines,
|
|
11
12
|
printDependencyTree,
|
|
12
13
|
serializeActivitySummary,
|
|
13
14
|
} from "./display.js";
|
|
@@ -22,75 +23,61 @@ it("print tree test", () => {
|
|
|
22
23
|
|
|
23
24
|
it("prints a provenance icon for registry-backed components", async () => {
|
|
24
25
|
const rows = [];
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const { printTable } = await esmock("./display.js", {
|
|
28
|
-
"./table.js": {
|
|
29
|
-
createStream: () => ({
|
|
30
|
-
end() {
|
|
31
|
-
// intentional no-op for stream stub
|
|
32
|
-
},
|
|
33
|
-
write(row) {
|
|
34
|
-
rows.push(row);
|
|
35
|
-
},
|
|
36
|
-
}),
|
|
37
|
-
table: sinon.stub().returns(""),
|
|
38
|
-
},
|
|
39
|
-
"./utils.js": {
|
|
40
|
-
isSecureMode: false,
|
|
41
|
-
safeExistsSync: sinon.stub(),
|
|
42
|
-
toCamel: sinon.stub(),
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
printTable(
|
|
26
|
+
const bomJson = {
|
|
27
|
+
components: [
|
|
47
28
|
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
name: "left-pad",
|
|
52
|
-
properties: [
|
|
53
|
-
{
|
|
54
|
-
name: "cdx:npm:provenanceUrl",
|
|
55
|
-
value:
|
|
56
|
-
"https://registry.npmjs.org/-/npm/v1/attestations/left-pad",
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
type: "library",
|
|
60
|
-
version: "1.3.0",
|
|
61
|
-
},
|
|
29
|
+
group: "",
|
|
30
|
+
name: "left-pad",
|
|
31
|
+
properties: [
|
|
62
32
|
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
properties: [],
|
|
66
|
-
type: "library",
|
|
67
|
-
version: "4.17.21",
|
|
33
|
+
name: "cdx:npm:provenanceUrl",
|
|
34
|
+
value: "https://registry.npmjs.org/-/npm/v1/attestations/left-pad",
|
|
68
35
|
},
|
|
69
36
|
],
|
|
70
|
-
|
|
37
|
+
type: "library",
|
|
38
|
+
version: "1.3.0",
|
|
71
39
|
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
40
|
+
{
|
|
41
|
+
group: "",
|
|
42
|
+
name: "lodash",
|
|
43
|
+
properties: [],
|
|
44
|
+
type: "library",
|
|
45
|
+
version: "4.17.21",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
dependencies: [],
|
|
49
|
+
};
|
|
50
|
+
const { printTable } = await esmock("./display.js", {
|
|
51
|
+
"./table.js": {
|
|
52
|
+
createStream: () => ({
|
|
53
|
+
end() {
|
|
54
|
+
// intentional no-op for stream stub
|
|
55
|
+
},
|
|
56
|
+
write(row) {
|
|
57
|
+
rows.push(row);
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
table: sinon.stub().returns(""),
|
|
61
|
+
},
|
|
62
|
+
"./utils.js": {
|
|
63
|
+
isSecureMode: false,
|
|
64
|
+
safeExistsSync: sinon.stub(),
|
|
65
|
+
toCamel: sinon.stub(),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
76
68
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
printTable(bomJson, undefined, undefined, "Found 1 trusted component.");
|
|
70
|
+
|
|
71
|
+
assert.strictEqual(rows[1][1], `${REGISTRY_PROVENANCE_ICON} left-pad`);
|
|
72
|
+
assert.strictEqual(rows[2][1], "lodash");
|
|
73
|
+
assert.deepStrictEqual(
|
|
74
|
+
buildTableSummaryLines(bomJson, undefined, "Found 1 trusted component.", 1),
|
|
75
|
+
[
|
|
81
76
|
"Found 1 trusted component.",
|
|
82
|
-
);
|
|
83
|
-
sinon.assert.calledWithExactly(
|
|
84
|
-
consoleLogStub,
|
|
85
77
|
`Legend: ${REGISTRY_PROVENANCE_ICON} = registry provenance or trusted publishing evidence`,
|
|
86
|
-
);
|
|
87
|
-
sinon.assert.calledWithExactly(
|
|
88
|
-
consoleLogStub,
|
|
89
78
|
`${REGISTRY_PROVENANCE_ICON} 1 component(s) include registry provenance or trusted publishing metadata.`,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
consoleLogStub.restore();
|
|
93
|
-
}
|
|
79
|
+
],
|
|
80
|
+
);
|
|
94
81
|
});
|
|
95
82
|
|
|
96
83
|
it("displaySelfThreatModel does not assume a default TLP classification", async () => {
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
providerNamesForText,
|
|
10
10
|
sanitizeMcpRefToken,
|
|
11
11
|
} from "./mcpDiscovery.js";
|
|
12
|
+
import {
|
|
13
|
+
sanitizeBomPropertyValue,
|
|
14
|
+
sanitizeBomUrl,
|
|
15
|
+
} from "./propertySanitizer.js";
|
|
12
16
|
import { scanTextForHiddenUnicode } from "./unicodeScan.js";
|
|
13
17
|
|
|
14
18
|
const MCP_CONFIG_PATTERNS = [
|
|
@@ -40,13 +44,22 @@ const SECRET_FIELD_NAME_PATTERN =
|
|
|
40
44
|
const ENV_REFERENCE_PATTERN = /(?:\$\{?[A-Z0-9_]+\}?|%[A-Z0-9_]+%)/u;
|
|
41
45
|
|
|
42
46
|
function addUniqueProperty(properties, name, value) {
|
|
43
|
-
|
|
47
|
+
const sanitizedValue = sanitizeBomPropertyValue(name, value);
|
|
48
|
+
if (
|
|
49
|
+
sanitizedValue === undefined ||
|
|
50
|
+
sanitizedValue === null ||
|
|
51
|
+
sanitizedValue === ""
|
|
52
|
+
) {
|
|
44
53
|
return;
|
|
45
54
|
}
|
|
46
|
-
if (
|
|
55
|
+
if (
|
|
56
|
+
properties.some(
|
|
57
|
+
(prop) => prop.name === name && prop.value === String(sanitizedValue),
|
|
58
|
+
)
|
|
59
|
+
) {
|
|
47
60
|
return;
|
|
48
61
|
}
|
|
49
|
-
properties.push({ name, value: String(
|
|
62
|
+
properties.push({ name, value: String(sanitizedValue) });
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
function normalizeFilePath(filePath) {
|
|
@@ -380,6 +393,9 @@ function createServiceFromConfig(
|
|
|
380
393
|
const command = normalized.command;
|
|
381
394
|
const args = normalized.args;
|
|
382
395
|
const endpoints = extractEndpoints(serverConfig);
|
|
396
|
+
const sanitizedEndpoints = endpoints.map((endpoint) =>
|
|
397
|
+
sanitizeBomUrl(endpoint),
|
|
398
|
+
);
|
|
383
399
|
const transport = inferTransport(serverConfig, endpoints);
|
|
384
400
|
const authHints = authHintsFromValue(serverConfig);
|
|
385
401
|
const authPosture = authPostureForConfig(serverConfig, endpoints, authHints);
|
|
@@ -396,7 +412,7 @@ function createServiceFromConfig(
|
|
|
396
412
|
JSON.stringify({
|
|
397
413
|
args,
|
|
398
414
|
command,
|
|
399
|
-
endpoints,
|
|
415
|
+
endpoints: sanitizedEndpoints,
|
|
400
416
|
env: serverConfig?.env || serverConfig?.environment || {},
|
|
401
417
|
}),
|
|
402
418
|
);
|
|
@@ -534,7 +550,7 @@ function createServiceFromConfig(
|
|
|
534
550
|
return {
|
|
535
551
|
"bom-ref": `urn:service:mcp:${sanitizeMcpRefToken(serviceName)}:${sanitizeMcpRefToken(version)}`,
|
|
536
552
|
authenticated,
|
|
537
|
-
endpoints,
|
|
553
|
+
endpoints: sanitizedEndpoints,
|
|
538
554
|
group: "mcp",
|
|
539
555
|
name: serviceName,
|
|
540
556
|
properties,
|
|
@@ -69,7 +69,7 @@ describe("mcpConfigParser", () => {
|
|
|
69
69
|
args: [
|
|
70
70
|
"--token",
|
|
71
71
|
"sk_test_super_secret_value",
|
|
72
|
-
"https://docs.example.com/mcp",
|
|
72
|
+
"https://user:pass@docs.example.com/mcp?access_token=secret#frag",
|
|
73
73
|
],
|
|
74
74
|
command: "npx",
|
|
75
75
|
env: {
|
|
@@ -99,7 +99,7 @@ describe("mcpConfigParser", () => {
|
|
|
99
99
|
);
|
|
100
100
|
assert.strictEqual(
|
|
101
101
|
getProp(service, "cdx:mcp:credentialExposureFieldCount"),
|
|
102
|
-
"
|
|
102
|
+
"4",
|
|
103
103
|
);
|
|
104
104
|
assert.strictEqual(
|
|
105
105
|
getProp(service, "cdx:mcp:credentialReferenceCount"),
|
|
@@ -109,6 +109,12 @@ describe("mcpConfigParser", () => {
|
|
|
109
109
|
getProp(component, "cdx:mcp:credentialExposedServiceCount"),
|
|
110
110
|
"1",
|
|
111
111
|
);
|
|
112
|
+
assert.strictEqual(getProp(service, "cdx:mcp:command"), "npx");
|
|
113
|
+
assert.deepStrictEqual(service.endpoints, ["https://docs.example.com/mcp"]);
|
|
114
|
+
assert.strictEqual(
|
|
115
|
+
getProp(component, "cdx:mcp:configuredEndpoints"),
|
|
116
|
+
"https://docs.example.com/mcp",
|
|
117
|
+
);
|
|
112
118
|
assert.strictEqual(
|
|
113
119
|
getProp(service, "cdx:mcp:credentialRiskIndicators"),
|
|
114
120
|
undefined,
|
|
@@ -123,4 +129,35 @@ describe("mcpConfigParser", () => {
|
|
|
123
129
|
undefined,
|
|
124
130
|
);
|
|
125
131
|
});
|
|
132
|
+
|
|
133
|
+
it("summarizes Windows executable paths with spaces safely", async () => {
|
|
134
|
+
const readFileSync = sinon.stub();
|
|
135
|
+
const scanTextForHiddenUnicode = sinon.stub().returns({
|
|
136
|
+
hasHiddenUnicode: false,
|
|
137
|
+
});
|
|
138
|
+
readFileSync.withArgs("/repo/.vscode/mcp.json", "utf-8").returns(
|
|
139
|
+
JSON.stringify({
|
|
140
|
+
mcpServers: {
|
|
141
|
+
releaseDocs: {
|
|
142
|
+
args: ["--inspect"],
|
|
143
|
+
command: "C:\\Program Files\\nodejs\\node.exe --inspect",
|
|
144
|
+
mcp: true,
|
|
145
|
+
transport: "stdio",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
const { mcpConfigParser } = await esmock("./mcpConfigParser.js", {
|
|
151
|
+
"node:fs": { readFileSync },
|
|
152
|
+
"./unicodeScan.js": { scanTextForHiddenUnicode },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const result = mcpConfigParser.parse(["/repo/.vscode/mcp.json"]);
|
|
156
|
+
|
|
157
|
+
assert.strictEqual(
|
|
158
|
+
getProp(result.services[0], "cdx:mcp:command") ||
|
|
159
|
+
getProp(result.components[0], "cdx:mcp:command"),
|
|
160
|
+
"node.exe",
|
|
161
|
+
);
|
|
162
|
+
});
|
|
126
163
|
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
const DANGEROUS_OBJECT_KEYS = new Set([
|
|
4
|
+
"__proto__",
|
|
5
|
+
"constructor",
|
|
6
|
+
"prototype",
|
|
7
|
+
]);
|
|
8
|
+
const INLINE_CREDENTIAL_PATTERNS = [
|
|
9
|
+
/\bAKIA[0-9A-Z]{16}\b/gu,
|
|
10
|
+
/\bbearer\s+[a-z0-9._-]{16,}\b/giu,
|
|
11
|
+
/\b(?:sk|rk|pk)_[a-z0-9_-]{8,}\b/giu,
|
|
12
|
+
/\bgh[pousr]_[a-z0-9]{20,}\b/giu,
|
|
13
|
+
/\bAIza[0-9A-Za-z_-]{20,}\b/gu,
|
|
14
|
+
];
|
|
15
|
+
const JSON_PROPERTY_NAMES = new Set([
|
|
16
|
+
"cdx:agent:permission",
|
|
17
|
+
"cdx:mcp:toolAnnotations",
|
|
18
|
+
"cdx:skill:metadata",
|
|
19
|
+
]);
|
|
20
|
+
const URL_PATTERN = /https?:\/\/[^\s<>"'),\]}]+/giu;
|
|
21
|
+
|
|
22
|
+
function sanitizeUrlForBom(value) {
|
|
23
|
+
const input = String(value || "").trim();
|
|
24
|
+
if (!input) {
|
|
25
|
+
return input;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const parsed = new URL(input);
|
|
29
|
+
parsed.username = "";
|
|
30
|
+
parsed.password = "";
|
|
31
|
+
parsed.search = "";
|
|
32
|
+
parsed.hash = "";
|
|
33
|
+
return parsed.toString();
|
|
34
|
+
} catch {
|
|
35
|
+
return input;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sanitizeTextForBom(value) {
|
|
40
|
+
let sanitized = String(value ?? "");
|
|
41
|
+
sanitized = sanitized.replace(URL_PATTERN, (match) =>
|
|
42
|
+
sanitizeUrlForBom(match),
|
|
43
|
+
);
|
|
44
|
+
for (const pattern of INLINE_CREDENTIAL_PATTERNS) {
|
|
45
|
+
sanitized = sanitized.replace(pattern, "[redacted]");
|
|
46
|
+
}
|
|
47
|
+
return sanitized;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sanitizeStructuredValueForBom(value) {
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
return sanitizeTextForBom(value);
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
return value.map((entry) => sanitizeStructuredValueForBom(entry));
|
|
56
|
+
}
|
|
57
|
+
if (value && typeof value === "object") {
|
|
58
|
+
const sanitized = {};
|
|
59
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
60
|
+
if (DANGEROUS_OBJECT_KEYS.has(key)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
sanitized[key] = sanitizeStructuredValueForBom(entryValue);
|
|
64
|
+
}
|
|
65
|
+
return sanitized;
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function extractCommandExecutable(command) {
|
|
71
|
+
const trimmedCommand = String(command || "").trim();
|
|
72
|
+
if (!trimmedCommand) {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
const quotedMatch = trimmedCommand.match(/^(['"])(.*?)\1/u);
|
|
76
|
+
if (quotedMatch?.[2]) {
|
|
77
|
+
return quotedMatch[2];
|
|
78
|
+
}
|
|
79
|
+
const absolutePathMatch = trimmedCommand.match(
|
|
80
|
+
/^((?:[A-Za-z]:\\|\/).*?\.(?:bat|bin|cjs|cmd|com|exe|jar|js|mjs|ps1|py|rb|sh|ts|tsx))(?=\s|$)/iu,
|
|
81
|
+
);
|
|
82
|
+
if (absolutePathMatch?.[1]) {
|
|
83
|
+
return absolutePathMatch[1];
|
|
84
|
+
}
|
|
85
|
+
return trimmedCommand.split(/\s+/u)[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function summarizeExecutable(command) {
|
|
89
|
+
const executable = extractCommandExecutable(command);
|
|
90
|
+
if (!executable) {
|
|
91
|
+
return "configured";
|
|
92
|
+
}
|
|
93
|
+
if (executable.includes("\\")) {
|
|
94
|
+
return path.win32.basename(executable) || "configured";
|
|
95
|
+
}
|
|
96
|
+
return path.posix.basename(executable) || "configured";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function sanitizeBomUrl(value) {
|
|
100
|
+
return sanitizeUrlForBom(value);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function sanitizeBomPropertyValue(name, value) {
|
|
104
|
+
if (value === undefined || value === null || value === "") {
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
if (name === "cdx:mcp:command") {
|
|
108
|
+
const sanitizedCommand = sanitizeTextForBom(value).trim();
|
|
109
|
+
if (!sanitizedCommand) {
|
|
110
|
+
return sanitizedCommand;
|
|
111
|
+
}
|
|
112
|
+
return summarizeExecutable(sanitizedCommand);
|
|
113
|
+
}
|
|
114
|
+
if (JSON_PROPERTY_NAMES.has(name) || typeof value === "object") {
|
|
115
|
+
return JSON.stringify(sanitizeStructuredValueForBom(value));
|
|
116
|
+
}
|
|
117
|
+
if (typeof value === "string") {
|
|
118
|
+
return sanitizeTextForBom(value);
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
}
|