@cyclonedx/cdxgen 12.3.0 → 12.3.2
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 +15 -5
- package/bin/audit.js +7 -0
- package/bin/cdxgen.js +241 -81
- package/bin/repl.js +138 -0
- package/data/rules/ai-agent-governance.yaml +249 -0
- package/data/rules/dependency-sources.yaml +41 -0
- package/data/rules/mcp-servers.yaml +304 -0
- package/data/rules/package-integrity.yaml +123 -0
- package/lib/audit/index.js +353 -29
- package/lib/audit/index.poku.js +247 -7
- package/lib/audit/reporters.js +26 -0
- package/lib/audit/scoring.js +262 -13
- package/lib/audit/scoring.poku.js +179 -0
- package/lib/audit/targets.js +391 -2
- package/lib/audit/targets.poku.js +416 -3
- package/lib/cli/index.js +588 -45
- package/lib/cli/index.poku.js +735 -1
- package/lib/evinser/evinser.js +8 -5
- package/lib/helpers/agentFormulationParser.js +318 -0
- package/lib/helpers/aiInventory.js +262 -0
- package/lib/helpers/aiInventory.poku.js +111 -0
- package/lib/helpers/analyzer.js +1769 -0
- package/lib/helpers/analyzer.poku.js +284 -3
- package/lib/helpers/auditCategories.js +76 -0
- package/lib/helpers/ciParsers/githubActions.js +140 -16
- package/lib/helpers/ciParsers/githubActions.poku.js +110 -0
- package/lib/helpers/communityAiConfigParser.js +672 -0
- package/lib/helpers/communityAiConfigParser.poku.js +63 -0
- package/lib/helpers/depsUtils.js +108 -0
- package/lib/helpers/depsUtils.poku.js +72 -1
- package/lib/helpers/display.js +325 -3
- package/lib/helpers/display.poku.js +301 -0
- package/lib/helpers/formulationParsers.js +28 -0
- package/lib/helpers/formulationParsers.poku.js +504 -1
- package/lib/helpers/jsonLike.js +102 -0
- package/lib/helpers/jsonLike.poku.js +34 -0
- package/lib/helpers/mcp.js +248 -0
- package/lib/helpers/mcp.poku.js +101 -0
- package/lib/helpers/mcpConfigParser.js +656 -0
- package/lib/helpers/mcpConfigParser.poku.js +126 -0
- package/lib/helpers/mcpDiscovery.js +84 -0
- package/lib/helpers/mcpDiscovery.poku.js +21 -0
- package/lib/helpers/protobom.js +3 -3
- package/lib/helpers/provenanceUtils.js +29 -4
- package/lib/helpers/provenanceUtils.poku.js +29 -3
- package/lib/helpers/registryProvenance.js +210 -0
- package/lib/helpers/registryProvenance.poku.js +144 -0
- package/lib/helpers/rustFormulationParser.js +330 -0
- package/lib/helpers/source.js +21 -2
- package/lib/helpers/source.poku.js +38 -0
- package/lib/helpers/utils.js +1331 -83
- package/lib/helpers/utils.poku.js +599 -188
- package/lib/helpers/vsixutils.js +12 -4
- package/lib/helpers/vsixutils.poku.js +34 -0
- package/lib/managers/binary.js +36 -12
- package/lib/managers/binary.poku.js +68 -0
- package/lib/managers/docker.js +59 -9
- package/lib/managers/docker.poku.js +61 -0
- package/lib/managers/piptree.js +12 -7
- package/lib/managers/piptree.poku.js +44 -0
- package/lib/stages/postgen/annotator.js +2 -1
- package/lib/stages/postgen/annotator.poku.js +15 -0
- package/lib/stages/postgen/auditBom.js +20 -6
- package/lib/stages/postgen/auditBom.poku.js +694 -1
- package/lib/stages/postgen/postgen.js +262 -11
- package/lib/stages/postgen/postgen.poku.js +306 -2
- package/lib/stages/postgen/ruleEngine.js +49 -1
- package/lib/stages/postgen/spdxConverter.poku.js +70 -0
- package/lib/stages/pregen/pregen.js +6 -4
- package/package.json +1 -1
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/reporters.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts +12 -0
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -8
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/helpers/aiInventory.d.ts +23 -0
- package/types/lib/helpers/aiInventory.d.ts.map +1 -0
- package/types/lib/helpers/analyzer.d.ts +10 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/auditCategories.d.ts +12 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +8 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +17 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/jsonLike.d.ts +4 -0
- package/types/lib/helpers/jsonLike.d.ts.map +1 -0
- package/types/lib/helpers/mcp.d.ts +29 -0
- package/types/lib/helpers/mcp.d.ts.map +1 -0
- package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
- package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +5 -3
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -1
- package/types/lib/helpers/registryProvenance.d.ts +9 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -1
- package/types/lib/helpers/rustFormulationParser.d.ts +17 -0
- package/types/lib/helpers/rustFormulationParser.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +31 -1
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/vsixutils.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/piptree.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/pregen/pregen.d.ts.map +1 -1
package/lib/evinser/evinser.js
CHANGED
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
getTmpDir,
|
|
18
18
|
PROJECT_TYPE_ALIASES,
|
|
19
19
|
safeExistsSync,
|
|
20
|
+
safeMkdtempSync,
|
|
21
|
+
safeRmSync,
|
|
22
|
+
safeWriteSync,
|
|
20
23
|
} from "../helpers/utils.js";
|
|
21
24
|
import { postProcess } from "../stages/postgen/postgen.js";
|
|
22
25
|
import { createOrLoad } from "./db.js";
|
|
@@ -192,7 +195,7 @@ export async function createAndStoreSlice(
|
|
|
192
195
|
});
|
|
193
196
|
}
|
|
194
197
|
if (retMap?.tempDir?.startsWith(getTmpDir())) {
|
|
195
|
-
|
|
198
|
+
safeRmSync(retMap.tempDir, { recursive: true, force: true });
|
|
196
199
|
}
|
|
197
200
|
return sliceData;
|
|
198
201
|
}
|
|
@@ -234,7 +237,7 @@ export async function createSlice(
|
|
|
234
237
|
return {};
|
|
235
238
|
}
|
|
236
239
|
|
|
237
|
-
let sliceOutputDir =
|
|
240
|
+
let sliceOutputDir = safeMkdtempSync(join(getTmpDir(), `atom-${sliceType}-`));
|
|
238
241
|
if (options?.output) {
|
|
239
242
|
sliceOutputDir =
|
|
240
243
|
safeExistsSync(options.output) &&
|
|
@@ -256,7 +259,7 @@ export async function createSlice(
|
|
|
256
259
|
const slicesData = createSemanticsSlices(resolve(filePath), options);
|
|
257
260
|
// Write the semantics slices data
|
|
258
261
|
if (slicesData) {
|
|
259
|
-
|
|
262
|
+
safeWriteSync(
|
|
260
263
|
slicesFile,
|
|
261
264
|
JSON.stringify(slicesData, null, options.jsonPretty ? 2 : null),
|
|
262
265
|
);
|
|
@@ -1523,7 +1526,7 @@ export function createEvinseFile(sliceArtefacts, options) {
|
|
|
1523
1526
|
delete bomJson.signature;
|
|
1524
1527
|
// Redo post-processing with evinse data
|
|
1525
1528
|
const bomNSData = postProcess({ bomJson }, options);
|
|
1526
|
-
|
|
1529
|
+
safeWriteSync(
|
|
1527
1530
|
evinseOutFile,
|
|
1528
1531
|
JSON.stringify(bomNSData.bomJson, null, options.jsonPretty ? 2 : null),
|
|
1529
1532
|
);
|
|
@@ -1555,7 +1558,7 @@ export function createEvinseFile(sliceArtefacts, options) {
|
|
|
1555
1558
|
}
|
|
1556
1559
|
}
|
|
1557
1560
|
if (tempDir?.startsWith(getTmpDir())) {
|
|
1558
|
-
|
|
1561
|
+
safeRmSync(tempDir, { recursive: true, force: true });
|
|
1559
1562
|
}
|
|
1560
1563
|
return bomNSData?.bomJson;
|
|
1561
1564
|
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { basename, extname } from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
credentialIndicatorsForText,
|
|
6
|
+
isLocalHost,
|
|
7
|
+
providerNamesForText,
|
|
8
|
+
sanitizeMcpRefToken,
|
|
9
|
+
} from "./mcpDiscovery.js";
|
|
10
|
+
import { scanTextForHiddenUnicode } from "./unicodeScan.js";
|
|
11
|
+
|
|
12
|
+
const AGENT_FILE_PATTERNS = [
|
|
13
|
+
"AGENTS.md",
|
|
14
|
+
"agents.md",
|
|
15
|
+
"CLAUDE.md",
|
|
16
|
+
"Cursor.md",
|
|
17
|
+
".github/copilot-instructions.md",
|
|
18
|
+
".github/instructions/**/*.{md,mdx,markdown,txt}",
|
|
19
|
+
".github/workflows/copilot-setup-steps.yml",
|
|
20
|
+
"**/*.{prompt,mdc}",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const MCP_PACKAGE_REF_PATTERN =
|
|
24
|
+
/@modelcontextprotocol\/[a-z0-9._/-]+|@[a-z0-9._-]+\/mcp[a-z0-9._/-]*/giu;
|
|
25
|
+
const URL_PATTERN = /https?:\/\/[^\s<>"')\]}]+/giu;
|
|
26
|
+
const AUTH_HINT_PATTERNS = [
|
|
27
|
+
["bearer", /\bbearer\b/i],
|
|
28
|
+
["oauth", /\boauth\b|authorization_endpoint|token_endpoint|issuer/i],
|
|
29
|
+
["api-key", /\bapi[_ -]?key\b/i],
|
|
30
|
+
["token", /\btoken\b|authorization:/i],
|
|
31
|
+
];
|
|
32
|
+
const TUNNEL_HOST_PATTERNS = [
|
|
33
|
+
/\.ngrok(?:-free)?\.app$/i,
|
|
34
|
+
/\.ngrok\.io$/i,
|
|
35
|
+
/\.trycloudflare\.com$/i,
|
|
36
|
+
/\.localhost\.run$/i,
|
|
37
|
+
/\.serveo\.net$/i,
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
function syntaxForFile(filePath) {
|
|
41
|
+
const extension = extname(filePath).toLowerCase();
|
|
42
|
+
if ([".md", ".mdx", ".markdown"].includes(extension)) {
|
|
43
|
+
return "markdown";
|
|
44
|
+
}
|
|
45
|
+
if ([".yaml", ".yml"].includes(extension)) {
|
|
46
|
+
return "yaml";
|
|
47
|
+
}
|
|
48
|
+
return "text";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function kindForFile(filePath) {
|
|
52
|
+
const lowerPath = filePath.toLowerCase();
|
|
53
|
+
if (lowerPath.endsWith("copilot-setup-steps.yml")) {
|
|
54
|
+
return "copilot-setup-workflow";
|
|
55
|
+
}
|
|
56
|
+
if (lowerPath.endsWith("copilot-instructions.md")) {
|
|
57
|
+
return "copilot-instructions";
|
|
58
|
+
}
|
|
59
|
+
if (lowerPath.endsWith("agents.md") || lowerPath.endsWith("claude.md")) {
|
|
60
|
+
return "agent-instructions";
|
|
61
|
+
}
|
|
62
|
+
if (lowerPath.endsWith(".prompt") || lowerPath.endsWith(".mdc")) {
|
|
63
|
+
return "skill-file";
|
|
64
|
+
}
|
|
65
|
+
return "ai-agent-file";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function authHintsForText(text) {
|
|
69
|
+
return AUTH_HINT_PATTERNS.flatMap(([name, pattern]) =>
|
|
70
|
+
pattern.test(text) ? [name] : [],
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function packageRefsForText(text) {
|
|
75
|
+
return [...new Set(text.match(MCP_PACKAGE_REF_PATTERN) || [])].sort();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function mcpUrlsForText(text) {
|
|
79
|
+
const urls = [];
|
|
80
|
+
for (const match of text.match(URL_PATTERN) || []) {
|
|
81
|
+
try {
|
|
82
|
+
const parsed = new URL(match);
|
|
83
|
+
if (
|
|
84
|
+
parsed.pathname.toLowerCase().includes("/mcp") ||
|
|
85
|
+
parsed.hostname.toLowerCase().includes("modelcontextprotocol")
|
|
86
|
+
) {
|
|
87
|
+
urls.push(parsed.toString());
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Ignore malformed URLs in untrusted agent instructions.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return [...new Set(urls)].sort();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function buildInferredMcpServices(filePath, mcpUrls, authHints, providerNames) {
|
|
97
|
+
return mcpUrls.map((urlValue, index) => {
|
|
98
|
+
const parsed = new URL(urlValue);
|
|
99
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
100
|
+
const exposureType = isLocalHost(hostname)
|
|
101
|
+
? "local-only"
|
|
102
|
+
: "networked-public";
|
|
103
|
+
const properties = [
|
|
104
|
+
{ name: "SrcFile", value: filePath },
|
|
105
|
+
{ name: "cdx:mcp:serviceType", value: "inferred-endpoint" },
|
|
106
|
+
{ name: "cdx:mcp:inventorySource", value: "agent-file" },
|
|
107
|
+
{ name: "cdx:mcp:usageConfidence", value: "medium" },
|
|
108
|
+
{ name: "cdx:mcp:reviewNeeded", value: "true" },
|
|
109
|
+
{ name: "cdx:mcp:exposureType", value: exposureType },
|
|
110
|
+
{ name: "cdx:mcp:agentReference", value: "true" },
|
|
111
|
+
];
|
|
112
|
+
if (providerNames.length) {
|
|
113
|
+
properties.push({
|
|
114
|
+
name: "cdx:mcp:providerNames",
|
|
115
|
+
value: providerNames.join(","),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (authHints.length) {
|
|
119
|
+
properties.push({
|
|
120
|
+
name: "cdx:mcp:authMode",
|
|
121
|
+
value: authHints.join(","),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
"bom-ref": `urn:service:agent-mcp:${sanitizeMcpRefToken(hostname || basename(filePath))}:${index + 1}`,
|
|
126
|
+
group: "mcp",
|
|
127
|
+
name: hostname || `${basename(filePath)}-mcp-endpoint`,
|
|
128
|
+
endpoints: [urlValue],
|
|
129
|
+
properties,
|
|
130
|
+
version: "inferred",
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Discover AI agent instruction and skill files that can hide MCP/runtime
|
|
137
|
+
* surfaces from package-only inventory.
|
|
138
|
+
*/
|
|
139
|
+
export const agentFormulationParser = {
|
|
140
|
+
id: "agent-formulation",
|
|
141
|
+
patterns: AGENT_FILE_PATTERNS,
|
|
142
|
+
parse(files, _options = {}) {
|
|
143
|
+
const components = [];
|
|
144
|
+
const services = [];
|
|
145
|
+
for (const filePath of files || []) {
|
|
146
|
+
let raw;
|
|
147
|
+
try {
|
|
148
|
+
raw = readFileSync(filePath, "utf-8");
|
|
149
|
+
} catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const hiddenUnicodeScan = scanTextForHiddenUnicode(raw, {
|
|
153
|
+
syntax: syntaxForFile(filePath),
|
|
154
|
+
});
|
|
155
|
+
const packageRefs = packageRefsForText(raw);
|
|
156
|
+
const providerNames = providerNamesForText(raw);
|
|
157
|
+
const mcpUrls = mcpUrlsForText(raw);
|
|
158
|
+
const authHints = authHintsForText(raw);
|
|
159
|
+
const credentialIndicators = credentialIndicatorsForText(raw);
|
|
160
|
+
const mcpHosts = mcpUrls.map((urlValue) => new URL(urlValue).hostname);
|
|
161
|
+
const hasPublicMcpEndpoint = mcpHosts.some((host) => !isLocalHost(host));
|
|
162
|
+
const hasTunnelReference = mcpHosts.some((host) =>
|
|
163
|
+
TUNNEL_HOST_PATTERNS.some((pattern) => pattern.test(host)),
|
|
164
|
+
);
|
|
165
|
+
const hasMcpReferences =
|
|
166
|
+
mcpUrls.length > 0 ||
|
|
167
|
+
packageRefs.length > 0 ||
|
|
168
|
+
/\bmcp\b/i.test(raw) ||
|
|
169
|
+
/modelcontextprotocol/i.test(raw);
|
|
170
|
+
// Inventory all matched agent/instruction files, even when they do not
|
|
171
|
+
// yet contain hidden Unicode or explicit MCP references, so shipped files
|
|
172
|
+
// such as CLAUDE.md and AGENTS.md still surface in build/post-build BOMs.
|
|
173
|
+
if (!raw.trim().length) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const hiddenComponentKinds = [];
|
|
177
|
+
if (mcpUrls.length) {
|
|
178
|
+
hiddenComponentKinds.push("mcp-endpoint");
|
|
179
|
+
}
|
|
180
|
+
if (providerNames.length) {
|
|
181
|
+
hiddenComponentKinds.push("provider");
|
|
182
|
+
}
|
|
183
|
+
if (packageRefs.length) {
|
|
184
|
+
hiddenComponentKinds.push("mcp-package-reference");
|
|
185
|
+
}
|
|
186
|
+
const properties = [
|
|
187
|
+
{ name: "SrcFile", value: filePath },
|
|
188
|
+
{ name: "cdx:file:kind", value: kindForFile(filePath) },
|
|
189
|
+
{ name: "cdx:agent:inventorySource", value: "agent-file" },
|
|
190
|
+
{ name: "cdx:agent:hasMcpReferences", value: String(hasMcpReferences) },
|
|
191
|
+
{
|
|
192
|
+
name: "cdx:agent:hiddenEndpointCount",
|
|
193
|
+
value: String(mcpUrls.length),
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
if (hiddenUnicodeScan.hasHiddenUnicode) {
|
|
197
|
+
properties.push(
|
|
198
|
+
{ name: "cdx:file:hasHiddenUnicode", value: "true" },
|
|
199
|
+
{
|
|
200
|
+
name: "cdx:file:hiddenUnicodeCodePoints",
|
|
201
|
+
value: hiddenUnicodeScan.codePoints.join(","),
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "cdx:file:hiddenUnicodeLineNumbers",
|
|
205
|
+
value: hiddenUnicodeScan.lineNumbers.join(","),
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
if (hiddenUnicodeScan.inComments) {
|
|
209
|
+
properties.push(
|
|
210
|
+
{
|
|
211
|
+
name: "cdx:file:hiddenUnicodeInComments",
|
|
212
|
+
value: "true",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: "cdx:file:hiddenUnicodeCommentCodePoints",
|
|
216
|
+
value: hiddenUnicodeScan.commentCodePoints.join(","),
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (packageRefs.length) {
|
|
222
|
+
properties.push({
|
|
223
|
+
name: "cdx:agent:mcpPackageRefs",
|
|
224
|
+
value: packageRefs.join(","),
|
|
225
|
+
});
|
|
226
|
+
if (
|
|
227
|
+
packageRefs.some((ref) => !ref.startsWith("@modelcontextprotocol/"))
|
|
228
|
+
) {
|
|
229
|
+
properties.push({
|
|
230
|
+
name: "cdx:agent:hasNonOfficialMcpReference",
|
|
231
|
+
value: "true",
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (mcpUrls.length) {
|
|
236
|
+
properties.push(
|
|
237
|
+
{
|
|
238
|
+
name: "cdx:agent:hiddenMcpUrls",
|
|
239
|
+
value: mcpUrls.join(","),
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "cdx:agent:hiddenMcpHosts",
|
|
243
|
+
value: [...new Set(mcpHosts)].sort().join(","),
|
|
244
|
+
},
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
if (providerNames.length) {
|
|
248
|
+
properties.push({
|
|
249
|
+
name: "cdx:agent:providerNames",
|
|
250
|
+
value: providerNames.join(","),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (authHints.length) {
|
|
254
|
+
properties.push({
|
|
255
|
+
name: "cdx:agent:authHints",
|
|
256
|
+
value: authHints.join(","),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (credentialIndicators.length) {
|
|
260
|
+
properties.push(
|
|
261
|
+
{
|
|
262
|
+
name: "cdx:agent:credentialExposure",
|
|
263
|
+
value: "true",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "cdx:agent:credentialRiskIndicators",
|
|
267
|
+
value: credentialIndicators.join(","),
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (hasPublicMcpEndpoint) {
|
|
272
|
+
properties.push({
|
|
273
|
+
name: "cdx:agent:hasPublicMcpEndpoint",
|
|
274
|
+
value: "true",
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (hasTunnelReference) {
|
|
278
|
+
properties.push({
|
|
279
|
+
name: "cdx:agent:hasTunnelReference",
|
|
280
|
+
value: "true",
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (hiddenComponentKinds.length) {
|
|
284
|
+
properties.push({
|
|
285
|
+
name: "cdx:agent:hiddenComponentKinds",
|
|
286
|
+
value: hiddenComponentKinds.join(","),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (
|
|
290
|
+
hiddenUnicodeScan.hasHiddenUnicode ||
|
|
291
|
+
hasPublicMcpEndpoint ||
|
|
292
|
+
hasTunnelReference ||
|
|
293
|
+
packageRefs.length > 0 ||
|
|
294
|
+
credentialIndicators.length > 0
|
|
295
|
+
) {
|
|
296
|
+
properties.push({
|
|
297
|
+
name: "cdx:agent:reviewNeeded",
|
|
298
|
+
value: "true",
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
components.push({
|
|
302
|
+
"bom-ref": `file:${filePath}`,
|
|
303
|
+
name: basename(filePath),
|
|
304
|
+
properties,
|
|
305
|
+
type: "file",
|
|
306
|
+
});
|
|
307
|
+
services.push(
|
|
308
|
+
...buildInferredMcpServices(
|
|
309
|
+
filePath,
|
|
310
|
+
mcpUrls,
|
|
311
|
+
authHints,
|
|
312
|
+
providerNames,
|
|
313
|
+
),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return { components, services };
|
|
317
|
+
},
|
|
318
|
+
};
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { agentFormulationParser } from "./agentFormulationParser.js";
|
|
2
|
+
import { communityAiConfigParser } from "./communityAiConfigParser.js";
|
|
3
|
+
import { mergeServices, trimComponents } from "./depsUtils.js";
|
|
4
|
+
import { classifyMcpReference } from "./mcp.js";
|
|
5
|
+
import { mcpConfigParser } from "./mcpConfigParser.js";
|
|
6
|
+
import { getAllFiles } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
export const AI_INVENTORY_PROJECT_TYPES = ["mcp", "ai-skill"];
|
|
9
|
+
export const AI_INSTRUCTION_FILE_KINDS = new Set([
|
|
10
|
+
"agent-config",
|
|
11
|
+
"agent-definition",
|
|
12
|
+
"agent-instructions",
|
|
13
|
+
"ai-agent-file",
|
|
14
|
+
"copilot-instructions",
|
|
15
|
+
"copilot-setup-workflow",
|
|
16
|
+
"crew-agent",
|
|
17
|
+
"crew-task",
|
|
18
|
+
"crew-tool",
|
|
19
|
+
"custom-command",
|
|
20
|
+
"custom-tool",
|
|
21
|
+
"graph-definition",
|
|
22
|
+
]);
|
|
23
|
+
export const AI_SKILL_FILE_KIND = "skill-file";
|
|
24
|
+
export const MCP_CONFIG_FILE_KIND = "mcp-config";
|
|
25
|
+
|
|
26
|
+
const AI_INVENTORY_FILE_KINDS = new Set([
|
|
27
|
+
"agent-config",
|
|
28
|
+
"agent-definition",
|
|
29
|
+
"agent-instructions",
|
|
30
|
+
"ai-agent-file",
|
|
31
|
+
"copilot-instructions",
|
|
32
|
+
"copilot-setup-workflow",
|
|
33
|
+
"crew-agent",
|
|
34
|
+
"crew-task",
|
|
35
|
+
"crew-tool",
|
|
36
|
+
"custom-command",
|
|
37
|
+
"custom-tool",
|
|
38
|
+
"graph-definition",
|
|
39
|
+
AI_SKILL_FILE_KIND,
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const AI_INVENTORY_PARSERS = [
|
|
43
|
+
{
|
|
44
|
+
id: agentFormulationParser.id,
|
|
45
|
+
parser: agentFormulationParser,
|
|
46
|
+
types: ["mcp", "ai-skill"],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: mcpConfigParser.id,
|
|
50
|
+
parser: mcpConfigParser,
|
|
51
|
+
types: ["mcp"],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: communityAiConfigParser.id,
|
|
55
|
+
parser: communityAiConfigParser,
|
|
56
|
+
types: ["ai-skill"],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export function inventoryPropertyValue(subject, name) {
|
|
61
|
+
return subject?.properties?.find((property) => property.name === name)?.value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasPropertyPrefix(subject, prefix) {
|
|
65
|
+
return (subject?.properties || []).some((property) =>
|
|
66
|
+
property?.name?.startsWith(prefix),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function uniqueNonEmptyTypes(types) {
|
|
71
|
+
return [...new Set((types || []).filter(Boolean))];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function optionIncludesAiInventoryProjectType(optionValue, type) {
|
|
75
|
+
const values = Array.isArray(optionValue)
|
|
76
|
+
? optionValue
|
|
77
|
+
: optionValue
|
|
78
|
+
? [optionValue]
|
|
79
|
+
: [];
|
|
80
|
+
return values.some((value) => {
|
|
81
|
+
const normalizedValue = String(value).toLowerCase();
|
|
82
|
+
if (type === "ai-skill") {
|
|
83
|
+
return ["ai-skill", "skill", "skills"].includes(normalizedValue);
|
|
84
|
+
}
|
|
85
|
+
return normalizedValue === type;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function inventoryTypesForSubject(subject) {
|
|
90
|
+
const types = new Set();
|
|
91
|
+
const fileKind = inventoryPropertyValue(subject, "cdx:file:kind");
|
|
92
|
+
if (
|
|
93
|
+
subject?.group === "mcp" ||
|
|
94
|
+
classifyMcpReference(subject).isMcp ||
|
|
95
|
+
hasPropertyPrefix(subject, "cdx:mcp:") ||
|
|
96
|
+
(subject?.tags || []).some((tag) => String(tag || "").startsWith("mcp"))
|
|
97
|
+
) {
|
|
98
|
+
types.add("mcp");
|
|
99
|
+
}
|
|
100
|
+
if (
|
|
101
|
+
AI_INVENTORY_FILE_KINDS.has(fileKind) ||
|
|
102
|
+
hasPropertyPrefix(subject, "cdx:agent:") ||
|
|
103
|
+
hasPropertyPrefix(subject, "cdx:skill:") ||
|
|
104
|
+
hasPropertyPrefix(subject, "cdx:tool:") ||
|
|
105
|
+
hasPropertyPrefix(subject, "cdx:langgraph:") ||
|
|
106
|
+
hasPropertyPrefix(subject, "cdx:crewai:")
|
|
107
|
+
) {
|
|
108
|
+
types.add("ai-skill");
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
inventoryPropertyValue(subject, "cdx:mcp:inventorySource") === "agent-file"
|
|
112
|
+
) {
|
|
113
|
+
types.add("ai-skill");
|
|
114
|
+
}
|
|
115
|
+
return Array.from(types);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function matchesAiInventoryType(subject, type) {
|
|
119
|
+
return inventoryTypesForSubject(subject).includes(type);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function matchesAiInventoryExcludeType(subject, type) {
|
|
123
|
+
if (type === "mcp") {
|
|
124
|
+
const fileKind = inventoryPropertyValue(subject, "cdx:file:kind");
|
|
125
|
+
return (
|
|
126
|
+
fileKind === MCP_CONFIG_FILE_KIND ||
|
|
127
|
+
subject?.group === "mcp" ||
|
|
128
|
+
inventoryPropertyValue(subject, "cdx:mcp:inventorySource") !==
|
|
129
|
+
undefined ||
|
|
130
|
+
inventoryPropertyValue(subject, "cdx:mcp:role") !== undefined
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return matchesAiInventoryType(subject, type);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function filterInventorySubjectsByTypes(subjects, types) {
|
|
137
|
+
const allowedTypes = uniqueNonEmptyTypes(types);
|
|
138
|
+
if (!allowedTypes.length) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
return (subjects || []).filter((subject) =>
|
|
142
|
+
inventoryTypesForSubject(subject).some((type) =>
|
|
143
|
+
allowedTypes.includes(type),
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function filterInventoryDependencies(
|
|
149
|
+
dependencies,
|
|
150
|
+
components,
|
|
151
|
+
services,
|
|
152
|
+
) {
|
|
153
|
+
const allowedRefs = new Set(
|
|
154
|
+
[]
|
|
155
|
+
.concat(components || [])
|
|
156
|
+
.concat(services || [])
|
|
157
|
+
.map((subject) => subject?.["bom-ref"])
|
|
158
|
+
.filter(Boolean),
|
|
159
|
+
);
|
|
160
|
+
return (dependencies || [])
|
|
161
|
+
.filter((dependency) => allowedRefs.has(dependency.ref))
|
|
162
|
+
.map((dependency) => {
|
|
163
|
+
const filteredDependency = {
|
|
164
|
+
ref: dependency.ref,
|
|
165
|
+
};
|
|
166
|
+
if (dependency.dependsOn?.length) {
|
|
167
|
+
filteredDependency.dependsOn = dependency.dependsOn.filter((ref) =>
|
|
168
|
+
allowedRefs.has(ref),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (dependency.provides?.length) {
|
|
172
|
+
filteredDependency.provides = dependency.provides.filter((ref) =>
|
|
173
|
+
allowedRefs.has(ref),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return filteredDependency;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function collectAiInventory(discoveryPath, options, types) {
|
|
181
|
+
const requestedTypes = uniqueNonEmptyTypes(types);
|
|
182
|
+
if (!requestedTypes.length) {
|
|
183
|
+
return { components: [], dependencies: [], services: [] };
|
|
184
|
+
}
|
|
185
|
+
let components = [];
|
|
186
|
+
const dependencies = [];
|
|
187
|
+
let services = [];
|
|
188
|
+
for (const parserEntry of AI_INVENTORY_PARSERS) {
|
|
189
|
+
if (!parserEntry.types.some((type) => requestedTypes.includes(type))) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const matchedFiles = [];
|
|
193
|
+
for (const pattern of parserEntry.parser.patterns) {
|
|
194
|
+
const found = getAllFiles(discoveryPath, pattern, options);
|
|
195
|
+
if (found?.length) {
|
|
196
|
+
matchedFiles.push(...found);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const uniqueMatchedFiles = [...new Set(matchedFiles)];
|
|
200
|
+
if (!uniqueMatchedFiles.length) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
let result;
|
|
204
|
+
try {
|
|
205
|
+
result = parserEntry.parser.parse(uniqueMatchedFiles, options);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.warn(
|
|
208
|
+
`[aiInventory] Parser "${parserEntry.id}" threw an error:`,
|
|
209
|
+
err.message,
|
|
210
|
+
);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (result?.components?.length) {
|
|
214
|
+
components = components.concat(result.components);
|
|
215
|
+
}
|
|
216
|
+
if (result?.services?.length) {
|
|
217
|
+
services = mergeServices(services, result.services);
|
|
218
|
+
}
|
|
219
|
+
if (result?.dependencies?.length) {
|
|
220
|
+
dependencies.push(...result.dependencies);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
components = trimComponents(
|
|
224
|
+
filterInventorySubjectsByTypes(components, requestedTypes),
|
|
225
|
+
);
|
|
226
|
+
services = mergeServices(
|
|
227
|
+
[],
|
|
228
|
+
filterInventorySubjectsByTypes(services, requestedTypes),
|
|
229
|
+
);
|
|
230
|
+
return {
|
|
231
|
+
components,
|
|
232
|
+
dependencies: filterInventoryDependencies(
|
|
233
|
+
dependencies,
|
|
234
|
+
components,
|
|
235
|
+
services,
|
|
236
|
+
),
|
|
237
|
+
services,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function summarizeAiInventory(inventory) {
|
|
242
|
+
const components = inventory?.components || [];
|
|
243
|
+
const services = inventory?.services || [];
|
|
244
|
+
return {
|
|
245
|
+
instructionCount: components.filter((component) =>
|
|
246
|
+
AI_INSTRUCTION_FILE_KINDS.has(
|
|
247
|
+
inventoryPropertyValue(component, "cdx:file:kind"),
|
|
248
|
+
),
|
|
249
|
+
).length,
|
|
250
|
+
mcpConfigCount: components.filter(
|
|
251
|
+
(component) =>
|
|
252
|
+
inventoryPropertyValue(component, "cdx:file:kind") ===
|
|
253
|
+
MCP_CONFIG_FILE_KIND,
|
|
254
|
+
).length,
|
|
255
|
+
mcpServiceCount: services.length,
|
|
256
|
+
skillCount: components.filter(
|
|
257
|
+
(component) =>
|
|
258
|
+
inventoryPropertyValue(component, "cdx:file:kind") ===
|
|
259
|
+
AI_SKILL_FILE_KIND,
|
|
260
|
+
).length,
|
|
261
|
+
};
|
|
262
|
+
}
|