@cdxgen/cdxgen 13.1.0 → 13.2.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 +8 -7
- package/bin/cdxgen.js +16 -3
- package/bin/evinse.js +2 -0
- package/data/crypto-oid.json +88 -12
- package/data/remediations.json +23 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +12 -0
- package/lib/cli/index.js +192 -1
- package/lib/cli/jsBom.js +59 -2
- package/lib/cli/jvmBom.js +22 -0
- package/lib/cli/managedBom.js +152 -0
- package/lib/cli/nativeBom.js +798 -1
- package/lib/core/activity.js +120 -2
- package/lib/core/env.js +39 -1
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +13 -5
- package/lib/ecosystems/cppEvidence.js +40 -2
- package/lib/ecosystems/ecosystems.js +241 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +143 -2
- package/lib/ecosystems/kosi.js +1256 -0
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +69 -39
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +172 -0
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +1 -1
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +932 -17
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/rusi.js +126 -6
- package/lib/evinser/evinser.js +111 -1
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/auditCategories.js +8 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +7 -2
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/dosai.js +77 -0
- package/lib/inventory/envcontext.js +9 -6
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +74 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +6 -5
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +54 -88
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/source.js +10 -4
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/stages/postgen/introspection/markers.js +29 -6
- package/lib/stages/postgen/postgen.js +169 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/package.json +25 -25
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +21 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +20 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +73 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +29 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +4 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +23 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +34 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +33 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
import { parentPort } from "node:worker_threads";
|
|
10
10
|
|
|
11
11
|
import { parseFileASTTreeCollected } from "./analyzer.js";
|
|
12
|
+
import { getSvelteTemplateParser } from "./svelteUtils.js";
|
|
13
|
+
|
|
14
|
+
// The parse runs synchronously on the worker thread, so the optional Svelte
|
|
15
|
+
// template parser is resolved eagerly here (a silent no-op when the optional
|
|
16
|
+
// package is absent; the built-in script mask covers all scope evidence).
|
|
17
|
+
getSvelteTemplateParser();
|
|
12
18
|
|
|
13
19
|
parentPort.on("message", (task) => {
|
|
14
20
|
try {
|
|
@@ -23,6 +23,12 @@ export const GOLEM_AUDIT_CATEGORIES = Object.freeze([
|
|
|
23
23
|
"golem-compliance",
|
|
24
24
|
]);
|
|
25
25
|
|
|
26
|
+
/** Frozen list of Cargo (Rust) audit categories. */
|
|
27
|
+
export const CARGO_AUDIT_CATEGORIES = Object.freeze([
|
|
28
|
+
"cargo-security",
|
|
29
|
+
"cargo-compliance",
|
|
30
|
+
]);
|
|
31
|
+
|
|
26
32
|
/**
|
|
27
33
|
* Frozen list of audit categories that never activate on their own. These
|
|
28
34
|
* rule packs serve a specific feature (`--introspect` for build-fidelity) and
|
|
@@ -52,10 +58,12 @@ export const BOM_AUDIT_CATEGORY_ALIASES = Object.freeze({
|
|
|
52
58
|
"ai-bom": [...AI_BOM_AUDIT_CATEGORIES, "ai-agent", "mcp-server"],
|
|
53
59
|
"ai-provenance": ["ai-provenance", "ai-oversight"],
|
|
54
60
|
"ai-oversight": ["ai-oversight"],
|
|
61
|
+
cargo: [...CARGO_AUDIT_CATEGORIES],
|
|
55
62
|
cbom: [...CBOM_AUDIT_CATEGORIES],
|
|
56
63
|
"crypto-bom": [...CBOM_AUDIT_CATEGORIES],
|
|
57
64
|
golem: [...GOLEM_AUDIT_CATEGORIES],
|
|
58
65
|
hbom: [...HBOM_AUDIT_CATEGORIES],
|
|
66
|
+
rust: [...CARGO_AUDIT_CATEGORIES],
|
|
59
67
|
host: [...HBOM_AUDIT_CATEGORIES, ...HOST_TOPOLOGY_AUDIT_CATEGORIES],
|
|
60
68
|
});
|
|
61
69
|
|
|
@@ -345,6 +345,36 @@ function normalizeDosaiCryptoNames(cryptoObject) {
|
|
|
345
345
|
names.add("sha-512");
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
|
+
// dosai reports the post-quantum families under their generic names and no
|
|
349
|
+
// generic OID exists for any of them, so the parameter set has to come from
|
|
350
|
+
// the call-site context (dosai's Code field carries the source line, e.g.
|
|
351
|
+
// "MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65)"). Without a parameter set the
|
|
352
|
+
// asset stays unmapped and is skipped rather than emitted with a wrong OID.
|
|
353
|
+
if (/^ML-?DSA$/i.test(cleanName)) {
|
|
354
|
+
const parameterSet = context.match(/(?:MLDsa|ML-DSA-)(44|65|87)\b/i);
|
|
355
|
+
if (parameterSet) {
|
|
356
|
+
names.add(`ml-dsa-${parameterSet[1]}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (/^ML-?KEM$/i.test(cleanName)) {
|
|
360
|
+
const parameterSet = context.match(/(?:MLKem|ML-KEM-)(512|768|1024)\b/i);
|
|
361
|
+
if (parameterSet) {
|
|
362
|
+
names.add(`ml-kem-${parameterSet[1]}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (/^SLH-?DSA$/i.test(cleanName)) {
|
|
366
|
+
// .NET spells it SlhDsaSha2_128s; NIST SLH-DSA-SHA2-128s. The hash family
|
|
367
|
+
// is required: SHA2 and SHAKE have distinct OIDs, so a context naming only
|
|
368
|
+
// the size, such as SLH-DSA-192f, resolves to no single parameter set.
|
|
369
|
+
const parameterSet = context.match(
|
|
370
|
+
/(?:SlhDsa|SLH-DSA-)(SHA2|SHAKE)[-_]?(128|192|256)(s|f)\b/i,
|
|
371
|
+
);
|
|
372
|
+
if (parameterSet) {
|
|
373
|
+
names.add(
|
|
374
|
+
`slh-dsa-${parameterSet[1].toLowerCase()}-${parameterSet[2]}${parameterSet[3].toLowerCase()}`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
348
378
|
return Array.from(names);
|
|
349
379
|
}
|
|
350
380
|
|
package/lib/inventory/cdxrs.js
CHANGED
|
@@ -25,8 +25,13 @@ import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
|
25
25
|
import { safeExistsSync } from "../core/fs.js";
|
|
26
26
|
import { getDefaultPluginRuntime, resolvePluginBinary } from "./plugins.js";
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Major version this bridge expects from the cdxrs binary. cdxgen-plugins-bin
|
|
30
|
+
* versions every tool in lockstep, so this moves with
|
|
31
|
+
* `@cdxgen/cdxgen-plugins-bin` in package.json even when cdxrs itself carries
|
|
32
|
+
* no breaking change (4.0.0 is 3.2.0 renumbered by the release).
|
|
33
|
+
*/
|
|
34
|
+
const CDXRS_VERSION_MAJOR = "4";
|
|
30
35
|
|
|
31
36
|
/** Default timeout for a cdxrs invocation. */
|
|
32
37
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, extname } from "node:path";
|
|
2
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { parse as loadYaml } from "yaml";
|
|
5
5
|
|
|
6
|
+
import { safeExistsSync } from "../core/fs.js";
|
|
6
7
|
import { sanitizeBomPropertyValue } from "../core/propertySanitizer.js";
|
|
7
8
|
import { parseJsonLike } from "./jsonLike.js";
|
|
8
9
|
import {
|
|
@@ -28,6 +29,18 @@ const COMMUNITY_AI_PATTERNS = [
|
|
|
28
29
|
"**/.claude/skills/*/SKILL.md",
|
|
29
30
|
".agents/skills/*/SKILL.md",
|
|
30
31
|
"**/.agents/skills/*/SKILL.md",
|
|
32
|
+
".codex/skills/*/SKILL.md",
|
|
33
|
+
"**/.codex/skills/*/SKILL.md",
|
|
34
|
+
"skills/*/SKILL.md",
|
|
35
|
+
"**/skills/*/SKILL.md",
|
|
36
|
+
".claude-plugin/plugin.json",
|
|
37
|
+
"**/.claude-plugin/plugin.json",
|
|
38
|
+
".claude-plugin/marketplace.json",
|
|
39
|
+
"**/.claude-plugin/marketplace.json",
|
|
40
|
+
".claude/agents/*.md",
|
|
41
|
+
"**/.claude/agents/*.md",
|
|
42
|
+
".claude/commands/**/*.md",
|
|
43
|
+
"**/.claude/commands/**/*.md",
|
|
31
44
|
".cursor/rules/*.{md,mdc}",
|
|
32
45
|
"**/.cursor/rules/*.{md,mdc}",
|
|
33
46
|
".windsurf/rules/**/*.md",
|
|
@@ -145,9 +158,24 @@ function frameworkForFile(filePath, raw = "") {
|
|
|
145
158
|
if (lowerPath.includes("/.claude/skills/")) {
|
|
146
159
|
return "claude-compatible";
|
|
147
160
|
}
|
|
161
|
+
if (lowerPath.includes("/.claude-plugin/")) {
|
|
162
|
+
return "claude-plugin";
|
|
163
|
+
}
|
|
164
|
+
if (lowerPath.includes("/.claude/agents/")) {
|
|
165
|
+
return "claude-subagent";
|
|
166
|
+
}
|
|
167
|
+
if (lowerPath.includes("/.claude/commands/")) {
|
|
168
|
+
return "claude-command";
|
|
169
|
+
}
|
|
170
|
+
if (lowerPath.includes("/.codex/skills/")) {
|
|
171
|
+
return "codex";
|
|
172
|
+
}
|
|
148
173
|
if (lowerPath.includes("/.agents/skills/")) {
|
|
149
174
|
return "agent-compatible";
|
|
150
175
|
}
|
|
176
|
+
if (/(?:^|\/)skills\/[^/]+\/skill\.md$/u.test(lowerPath)) {
|
|
177
|
+
return "agent-skills";
|
|
178
|
+
}
|
|
151
179
|
return "community-ai";
|
|
152
180
|
}
|
|
153
181
|
|
|
@@ -250,6 +278,173 @@ function parseSkillFile(filePath, raw) {
|
|
|
250
278
|
metadata.metadata,
|
|
251
279
|
);
|
|
252
280
|
}
|
|
281
|
+
// Record whether the frontmatter satisfies the open Agent Skills constraints
|
|
282
|
+
// (name: 1-64 lowercase-alnum-hyphen; description: 1-1024 chars) as a single
|
|
283
|
+
// reviewer-friendly boolean rather than copying the raw values twice.
|
|
284
|
+
const specValid =
|
|
285
|
+
typeof metadata?.name === "string" &&
|
|
286
|
+
/^[a-z0-9-]{1,64}$/u.test(metadata.name) &&
|
|
287
|
+
typeof metadata?.description === "string" &&
|
|
288
|
+
metadata.description.length >= 1 &&
|
|
289
|
+
metadata.description.length <= 1024;
|
|
290
|
+
addUniqueProperty(
|
|
291
|
+
component.properties,
|
|
292
|
+
"cdx:skill:specValid",
|
|
293
|
+
specValid ? "true" : "false",
|
|
294
|
+
);
|
|
295
|
+
const skillDir = dirname(filePath);
|
|
296
|
+
for (const [subdir, property] of [
|
|
297
|
+
["scripts", "cdx:skill:hasScripts"],
|
|
298
|
+
["references", "cdx:skill:hasReferences"],
|
|
299
|
+
["assets", "cdx:skill:hasAssets"],
|
|
300
|
+
]) {
|
|
301
|
+
if (safeExistsSync(join(skillDir, subdir))) {
|
|
302
|
+
addUniqueProperty(component.properties, property, "true");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
maybeAddFileSignals(component.properties, filePath, raw);
|
|
306
|
+
return [component];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Parse a Claude Code plugin manifest (`.claude-plugin/plugin.json`) into a
|
|
311
|
+
* single application component describing the plugin.
|
|
312
|
+
*
|
|
313
|
+
* @param {string} filePath Manifest path
|
|
314
|
+
* @param {string} raw File contents
|
|
315
|
+
* @returns {Object[]} Component list (one plugin component or empty)
|
|
316
|
+
*/
|
|
317
|
+
function parsePluginManifest(filePath, raw) {
|
|
318
|
+
let manifest;
|
|
319
|
+
try {
|
|
320
|
+
manifest = parseJsonLike(raw);
|
|
321
|
+
} catch {
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
if (!manifest?.name) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
const component = createComponent(
|
|
328
|
+
filePath,
|
|
329
|
+
"claude-plugin",
|
|
330
|
+
"agent-plugin",
|
|
331
|
+
manifest.name,
|
|
332
|
+
);
|
|
333
|
+
if (manifest.version) {
|
|
334
|
+
component.version = String(manifest.version);
|
|
335
|
+
}
|
|
336
|
+
addUniqueProperty(
|
|
337
|
+
component.properties,
|
|
338
|
+
"cdx:plugin:description",
|
|
339
|
+
manifest.description,
|
|
340
|
+
);
|
|
341
|
+
addUniqueProperty(
|
|
342
|
+
component.properties,
|
|
343
|
+
"cdx:plugin:author",
|
|
344
|
+
typeof manifest.author === "string"
|
|
345
|
+
? manifest.author
|
|
346
|
+
: manifest.author?.name,
|
|
347
|
+
);
|
|
348
|
+
for (const [key, property] of [
|
|
349
|
+
["commands", "cdx:plugin:commandCount"],
|
|
350
|
+
["agents", "cdx:plugin:agentCount"],
|
|
351
|
+
["skills", "cdx:plugin:skillCount"],
|
|
352
|
+
["hooks", "cdx:plugin:hookCount"],
|
|
353
|
+
["mcpServers", "cdx:plugin:mcpServerCount"],
|
|
354
|
+
]) {
|
|
355
|
+
const value = manifest[key];
|
|
356
|
+
if (Array.isArray(value)) {
|
|
357
|
+
addUniqueProperty(component.properties, property, String(value.length));
|
|
358
|
+
} else if (value && typeof value === "object") {
|
|
359
|
+
addUniqueProperty(
|
|
360
|
+
component.properties,
|
|
361
|
+
property,
|
|
362
|
+
String(Object.keys(value).length),
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
maybeAddFileSignals(component.properties, filePath, raw);
|
|
367
|
+
return [component];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Parse a Claude Code plugin marketplace file
|
|
372
|
+
* (`.claude-plugin/marketplace.json`) into one component per listed plugin.
|
|
373
|
+
*
|
|
374
|
+
* @param {string} filePath Marketplace path
|
|
375
|
+
* @param {string} raw File contents
|
|
376
|
+
* @returns {Object[]} Component list
|
|
377
|
+
*/
|
|
378
|
+
function parsePluginMarketplace(filePath, raw) {
|
|
379
|
+
let marketplace;
|
|
380
|
+
try {
|
|
381
|
+
marketplace = parseJsonLike(raw);
|
|
382
|
+
} catch {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
const plugins = Array.isArray(marketplace?.plugins)
|
|
386
|
+
? marketplace.plugins
|
|
387
|
+
: [];
|
|
388
|
+
const components = [];
|
|
389
|
+
for (const plugin of plugins) {
|
|
390
|
+
const name = plugin?.name || plugin?.source;
|
|
391
|
+
if (!name) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const component = createComponent(
|
|
395
|
+
filePath,
|
|
396
|
+
"claude-plugin",
|
|
397
|
+
"agent-plugin-listing",
|
|
398
|
+
String(name),
|
|
399
|
+
);
|
|
400
|
+
addUniqueProperty(
|
|
401
|
+
component.properties,
|
|
402
|
+
"cdx:plugin:description",
|
|
403
|
+
plugin.description,
|
|
404
|
+
);
|
|
405
|
+
addUniqueProperty(
|
|
406
|
+
component.properties,
|
|
407
|
+
"cdx:plugin:source",
|
|
408
|
+
typeof plugin.source === "string" ? plugin.source : undefined,
|
|
409
|
+
);
|
|
410
|
+
components.push(component);
|
|
411
|
+
}
|
|
412
|
+
return components;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Parse a Claude Code subagent definition (`.claude/agents/*.md`) into an
|
|
417
|
+
* agent-definition component using its markdown frontmatter.
|
|
418
|
+
*
|
|
419
|
+
* @param {string} filePath Subagent markdown path
|
|
420
|
+
* @param {string} raw File contents
|
|
421
|
+
* @returns {Object[]} Component list
|
|
422
|
+
*/
|
|
423
|
+
function parseClaudeSubagent(filePath, raw) {
|
|
424
|
+
const { metadata } = parseFrontmatter(raw);
|
|
425
|
+
const name = metadata?.name || basename(filePath, extname(filePath));
|
|
426
|
+
const component = createComponent(
|
|
427
|
+
filePath,
|
|
428
|
+
"claude-subagent",
|
|
429
|
+
"agent-definition",
|
|
430
|
+
name,
|
|
431
|
+
);
|
|
432
|
+
addUniqueProperty(component.properties, "cdx:agent:role", "claude-subagent");
|
|
433
|
+
addUniqueProperty(
|
|
434
|
+
component.properties,
|
|
435
|
+
"cdx:agent:description",
|
|
436
|
+
metadata?.description,
|
|
437
|
+
);
|
|
438
|
+
addUniqueProperty(component.properties, "cdx:agent:model", metadata?.model);
|
|
439
|
+
if (Array.isArray(metadata?.tools)) {
|
|
440
|
+
addUniqueProperty(
|
|
441
|
+
component.properties,
|
|
442
|
+
"cdx:agent:tools",
|
|
443
|
+
metadata.tools.join(","),
|
|
444
|
+
);
|
|
445
|
+
} else if (typeof metadata?.tools === "string") {
|
|
446
|
+
addUniqueProperty(component.properties, "cdx:agent:tools", metadata.tools);
|
|
447
|
+
}
|
|
253
448
|
maybeAddFileSignals(component.properties, filePath, raw);
|
|
254
449
|
return [component];
|
|
255
450
|
}
|
|
@@ -708,10 +903,28 @@ export const communityAiConfigParser = {
|
|
|
708
903
|
components.push(...parseAgentMarkdown(filePath, raw));
|
|
709
904
|
continue;
|
|
710
905
|
}
|
|
906
|
+
if (lowerPath.endsWith("/.claude-plugin/plugin.json")) {
|
|
907
|
+
components.push(...parsePluginManifest(filePath, raw));
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (lowerPath.endsWith("/.claude-plugin/marketplace.json")) {
|
|
911
|
+
components.push(...parsePluginMarketplace(filePath, raw));
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
if (lowerPath.includes("/.claude/agents/")) {
|
|
915
|
+
components.push(...parseClaudeSubagent(filePath, raw));
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
if (lowerPath.includes("/.claude/commands/")) {
|
|
919
|
+
components.push(...parseInstructionMarkdown(filePath, raw));
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
711
922
|
if (
|
|
712
923
|
lowerPath.includes("/.opencode/skills/") ||
|
|
713
924
|
lowerPath.includes("/.claude/skills/") ||
|
|
714
|
-
lowerPath.includes("/.agents/skills/")
|
|
925
|
+
lowerPath.includes("/.agents/skills/") ||
|
|
926
|
+
lowerPath.includes("/.codex/skills/") ||
|
|
927
|
+
lowerPath.endsWith("/skill.md")
|
|
715
928
|
) {
|
|
716
929
|
components.push(...parseSkillFile(filePath, raw));
|
|
717
930
|
continue;
|
package/lib/inventory/dosai.js
CHANGED
|
@@ -26,6 +26,15 @@ const DOTNET_LANGUAGES = new Set([
|
|
|
26
26
|
"cs",
|
|
27
27
|
"dotnet",
|
|
28
28
|
"dotnet-framework",
|
|
29
|
+
// Versioned CLI types (`-t dotnet11`): the project-type aliases map them to
|
|
30
|
+
// csharp for dispatch, but gates such as shouldCollectDosaiCrypto inspect the
|
|
31
|
+
// raw options.projectType, so the versioned spellings must resolve here too.
|
|
32
|
+
"dotnet6",
|
|
33
|
+
"dotnet7",
|
|
34
|
+
"dotnet8",
|
|
35
|
+
"dotnet9",
|
|
36
|
+
"dotnet10",
|
|
37
|
+
"dotnet11",
|
|
29
38
|
"f#",
|
|
30
39
|
"fsharp",
|
|
31
40
|
"fs",
|
|
@@ -429,6 +438,74 @@ export function resolveComponentPurl(purl, purlAliasMap) {
|
|
|
429
438
|
return resolveDosaiComponentPurl(purl, purlAliasMap);
|
|
430
439
|
}
|
|
431
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Copy one dosai PackageReachability fact onto a component as properties.
|
|
443
|
+
*
|
|
444
|
+
* The occurrence/location consumers of PackageReachability read SourceLocations, EdgeIds,
|
|
445
|
+
* and NodeIds only, so dosai's Confidence, EvidenceKinds, ReachabilityKind, and
|
|
446
|
+
* ConfidenceReasons never reached the BOM: an unbuilt tree (Low confidence, unresolved
|
|
447
|
+
* evidence) was indistinguishable from a package that really is only imported. These
|
|
448
|
+
* properties carry that distinction to BOM consumers. They use the consumer-facing
|
|
449
|
+
* `cdx:dosai:reachability:*` namespace - not `internal:`, which is cdxgen's private
|
|
450
|
+
* bookkeeping namespace stripped or ignored by downstream tooling.
|
|
451
|
+
*
|
|
452
|
+
* @param {Object} component BOM component object (mutated; properties created on demand)
|
|
453
|
+
* @param {Object} reachability dosai PackageReachability entry
|
|
454
|
+
*/
|
|
455
|
+
export function addDosaiReachabilityProperties(component, reachability) {
|
|
456
|
+
if (!component || !reachability) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
component.properties = component.properties || [];
|
|
460
|
+
const values = {
|
|
461
|
+
"cdx:dosai:reachability:kind": reachability.ReachabilityKind,
|
|
462
|
+
"cdx:dosai:reachability:confidence": reachability.Confidence,
|
|
463
|
+
"cdx:dosai:reachability:evidence": (reachability.EvidenceKinds || []).join(
|
|
464
|
+
", ",
|
|
465
|
+
),
|
|
466
|
+
"cdx:dosai:reachability:reasons": (
|
|
467
|
+
reachability.ConfidenceReasons || []
|
|
468
|
+
).join(" | "),
|
|
469
|
+
};
|
|
470
|
+
for (const [name, value] of Object.entries(values)) {
|
|
471
|
+
if (!value) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
// First fact wins so a repeated enrichment pass cannot stack duplicate properties.
|
|
475
|
+
if (component.properties.some((p) => p.name === name)) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
component.properties.push({ name, value });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Attach dosai PackageReachability confidence facts to matching BOM components.
|
|
484
|
+
*
|
|
485
|
+
* @param {Object} methodsSlice Parsed dosai methods slice JSON
|
|
486
|
+
* @param {Object[]} [components] BOM components used to resolve purl aliases and mutate
|
|
487
|
+
* @returns {number} Number of distinct components enriched
|
|
488
|
+
*/
|
|
489
|
+
export function applyDosaiReachabilityEvidence(methodsSlice, components = []) {
|
|
490
|
+
const purlAliasMap = buildPurlAliasMap(components);
|
|
491
|
+
const componentsByPurl = new Map(
|
|
492
|
+
components
|
|
493
|
+
.filter((component) => component?.purl)
|
|
494
|
+
.map((component) => [component.purl, component]),
|
|
495
|
+
);
|
|
496
|
+
const enriched = new Set();
|
|
497
|
+
for (const reachability of methodsSlice?.PackageReachability || []) {
|
|
498
|
+
const purl = resolveComponentPurl(reachability.Purl, purlAliasMap);
|
|
499
|
+
const component = purl && componentsByPurl.get(purl);
|
|
500
|
+
if (!component) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
addDosaiReachabilityProperties(component, reachability);
|
|
504
|
+
enriched.add(component);
|
|
505
|
+
}
|
|
506
|
+
return enriched.size;
|
|
507
|
+
}
|
|
508
|
+
|
|
432
509
|
/**
|
|
433
510
|
* Map a dosai methods slice to per-purl occurrence evidence.
|
|
434
511
|
*
|
|
@@ -101,15 +101,18 @@ function hardenedGitEnv() {
|
|
|
101
101
|
* @type {Object<string, string>}
|
|
102
102
|
*/
|
|
103
103
|
export const SDKMAN_JAVA_TOOL_ALIASES = {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
// Identifiers must be downloadable from the sdkman broker: the `sdk list`
|
|
105
|
+
// output advertises Temurin respin ids such as 21.0.12+1.1-tem, but the
|
|
106
|
+
// broker cannot serve ids containing '+', so the plain ids are pinned.
|
|
107
|
+
java8: readEnvironmentVariable("JAVA8_TOOL") || "8.0.504-zulu", // Temurin no longer offers java8 :(
|
|
108
|
+
java11: readEnvironmentVariable("JAVA11_TOOL") || "11.0.32-tem",
|
|
109
|
+
java17: readEnvironmentVariable("JAVA17_TOOL") || "17.0.20-tem",
|
|
110
|
+
java21: readEnvironmentVariable("JAVA21_TOOL") || "21.0.12-tem",
|
|
108
111
|
java22: readEnvironmentVariable("JAVA22_TOOL") || "22.0.2-tem",
|
|
109
112
|
java23: readEnvironmentVariable("JAVA23_TOOL") || "23.0.2-tem",
|
|
110
113
|
java24: readEnvironmentVariable("JAVA24_TOOL") || "24.0.2-tem",
|
|
111
|
-
java25: readEnvironmentVariable("JAVA25_TOOL") || "25.0.
|
|
112
|
-
java26: readEnvironmentVariable("JAVA26_TOOL") || "26.0.
|
|
114
|
+
java25: readEnvironmentVariable("JAVA25_TOOL") || "25.0.4-tem",
|
|
115
|
+
java26: readEnvironmentVariable("JAVA26_TOOL") || "26.0.2-tem",
|
|
113
116
|
};
|
|
114
117
|
|
|
115
118
|
/**
|
|
@@ -6,6 +6,7 @@ import { build } from "@cdxgen/cdx-purl";
|
|
|
6
6
|
|
|
7
7
|
import { safeExistsSync } from "../core/fs.js";
|
|
8
8
|
import { thoughtLog } from "../core/logger.js";
|
|
9
|
+
import { addDosaiReachabilityProperties } from "./dosai.js";
|
|
9
10
|
import {
|
|
10
11
|
addDosaiSetValue,
|
|
11
12
|
buildDosaiPurlAliasMap,
|
|
@@ -256,6 +257,7 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
|
256
257
|
const purlLocationMap = {};
|
|
257
258
|
const purlModulesMap = {};
|
|
258
259
|
const purlMethodsMap = {};
|
|
260
|
+
const purlReachabilityMap = {};
|
|
259
261
|
const purlAliasMap = buildDosaiPurlAliasMap(pkgList);
|
|
260
262
|
for (const apkg of pkgList) {
|
|
261
263
|
if (apkg.properties && Array.isArray(apkg.properties)) {
|
|
@@ -340,6 +342,9 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
|
340
342
|
if (!modPurl) {
|
|
341
343
|
continue;
|
|
342
344
|
}
|
|
345
|
+
// Confidence/evidence facts for this purl, applied to the component below so an
|
|
346
|
+
// unbuilt tree stays distinguishable from an only-imported package.
|
|
347
|
+
purlReachabilityMap[modPurl] ??= reachability;
|
|
343
348
|
let hasExplicitSourceLocations = false;
|
|
344
349
|
for (const sourceLocation of reachability.SourceLocations || []) {
|
|
345
350
|
const location = dosaiSourceLocation(sourceLocation);
|
|
@@ -456,6 +461,14 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
|
|
|
456
461
|
"I didn't find any occurrence evidence or detailed imported modules, even though there is good dependency slice data from dosai. This is surprising.",
|
|
457
462
|
);
|
|
458
463
|
}
|
|
464
|
+
// Reachability confidence facts (kind, confidence, evidence, reasons) as properties.
|
|
465
|
+
// Applied outside the location guard: a Low-confidence unbuilt tree must stay
|
|
466
|
+
// distinguishable from an only-imported package even when no locations were found.
|
|
467
|
+
for (const apkg of pkgList) {
|
|
468
|
+
if (purlReachabilityMap[apkg.purl]) {
|
|
469
|
+
addDosaiReachabilityProperties(apkg, purlReachabilityMap[apkg.purl]);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
459
472
|
return pkgList;
|
|
460
473
|
}
|
|
461
474
|
|
|
@@ -248,21 +248,58 @@ export async function prefetchJson(requests, options = {}) {
|
|
|
248
248
|
* - a non-JSON `responseType`, since the envelope has nowhere to put HTML;
|
|
249
249
|
* - request-specific `headers`, since cdxrs derives its own Authorization from
|
|
250
250
|
* `GITHUB_TOKEN` and would silently drop any other credential, turning an
|
|
251
|
-
* authenticated lookup into an anonymous one
|
|
251
|
+
* authenticated lookup into an anonymous one;
|
|
252
|
+
* - anything addressed to a host whose rate policy only one transport knows.
|
|
253
|
+
* crates.io's crawler policy allows one request per second with no
|
|
254
|
+
* parallelism, and package.elm-lang.org has a JS-only allowance because
|
|
255
|
+
* rate.rs carries no row for it. Both transports keep separate gates, so a
|
|
256
|
+
* batch split across the two would issue twice the agreed rate; the JS pool
|
|
257
|
+
* owns these hosts so there is one gate to reason about.
|
|
252
258
|
*
|
|
253
259
|
* The pool applies the same global and per-host concurrency and rate policy, so
|
|
254
260
|
* the batching win is unaffected; only the on-disk cache is skipped.
|
|
255
261
|
*
|
|
256
|
-
* @param {{responseType?: string, headers?: Object}} request
|
|
262
|
+
* @param {{url?: string, responseType?: string, headers?: Object}} request
|
|
257
263
|
* @returns {boolean}
|
|
258
264
|
*/
|
|
259
265
|
function cdxrsCanServe(request) {
|
|
260
266
|
if (request.responseType && request.responseType !== "json") {
|
|
261
267
|
return false;
|
|
262
268
|
}
|
|
269
|
+
if (isSingleGateHost(extractHost(request.url))) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
263
272
|
return !request.headers || !Object.keys(request.headers).length;
|
|
264
273
|
}
|
|
265
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Whether a hostname belongs to crates.io, including its index and CDN hosts.
|
|
277
|
+
*
|
|
278
|
+
* @param {string|null} host Hostname, or null when the URL did not parse.
|
|
279
|
+
* @returns {boolean}
|
|
280
|
+
*/
|
|
281
|
+
export function isCratesHost(host) {
|
|
282
|
+
if (!host) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const lower = host.toLowerCase();
|
|
286
|
+
return lower === "crates.io" || lower.endsWith(".crates.io");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Whether a host's rate policy lives in the JS table alone and therefore needs
|
|
291
|
+
* every request to pass through the one gate the JS pool holds.
|
|
292
|
+
*
|
|
293
|
+
* @param {string|null} host Hostname, or null when the URL did not parse.
|
|
294
|
+
* @returns {boolean}
|
|
295
|
+
*/
|
|
296
|
+
export function isSingleGateHost(host) {
|
|
297
|
+
if (!host) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
return isCratesHost(host) || host.toLowerCase() === "package.elm-lang.org";
|
|
301
|
+
}
|
|
302
|
+
|
|
266
303
|
/**
|
|
267
304
|
* Run the batch through the `cdxrs fetch` subprocess and translate its
|
|
268
305
|
* envelope into the shared result shape.
|
|
@@ -700,6 +737,41 @@ async function fetchOne(request, gauge) {
|
|
|
700
737
|
}
|
|
701
738
|
}
|
|
702
739
|
|
|
740
|
+
/**
|
|
741
|
+
* Run a caller's own request under the same per-host gate the batch pool uses.
|
|
742
|
+
*
|
|
743
|
+
* Not every registry lookup goes through the pool: a caller that misses the
|
|
744
|
+
* prefetch, or runs with prefetching disabled, issues `cdxgenAgent.get`
|
|
745
|
+
* directly. Those requests reach the same host and count against the same
|
|
746
|
+
* published budget, so they have to queue behind the same limiter rather than
|
|
747
|
+
* a second one — crates.io's one-request-per-second policy is not honoured by
|
|
748
|
+
* two independent gates that each allow one per second.
|
|
749
|
+
*
|
|
750
|
+
* The per-host semaphore and limiter are module state, so this shares one gate
|
|
751
|
+
* per host for the life of the process.
|
|
752
|
+
*
|
|
753
|
+
* @template T
|
|
754
|
+
* @param {string} url The URL about to be requested.
|
|
755
|
+
* @param {() => Promise<T>} issue Issues the request and resolves its result.
|
|
756
|
+
* @returns {Promise<T>} Whatever `issue` resolves to.
|
|
757
|
+
*/
|
|
758
|
+
export async function withHostRateLimit(url, issue) {
|
|
759
|
+
const host = extractHost(url);
|
|
760
|
+
if (!host) {
|
|
761
|
+
return await issue();
|
|
762
|
+
}
|
|
763
|
+
const controls = getHostControls(host);
|
|
764
|
+
const releaseGlobal = await _globalSemaphore.acquire();
|
|
765
|
+
const releaseHost = await controls.sem.acquire();
|
|
766
|
+
try {
|
|
767
|
+
await controls.limiter.wait();
|
|
768
|
+
return await issue();
|
|
769
|
+
} finally {
|
|
770
|
+
releaseHost();
|
|
771
|
+
releaseGlobal();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
703
775
|
/**
|
|
704
776
|
* Dedupe key for an in-flight request: the URL, plus everything that changes
|
|
705
777
|
* which response the caller gets back. That is the headers which change what
|