@cdxgen/cdxgen 13.1.0 → 13.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/README.md +32 -15
  2. package/bin/cdxgen.js +243 -122
  3. package/bin/evinse.js +7 -0
  4. package/bin/sign.js +88 -14
  5. package/bin/tracebom.js +64 -4
  6. package/bin/validate.js +29 -7
  7. package/bin/verify.js +79 -39
  8. package/data/crypto-oid.json +88 -12
  9. package/data/remediations.json +47 -0
  10. package/data/rules/ai-bom.yaml +78 -0
  11. package/data/rules/cargo-rust.yaml +132 -0
  12. package/data/rules/mcp-servers.yaml +122 -0
  13. package/lib/audit/targets.js +6 -1
  14. package/lib/cli/bomAssembly.js +117 -15
  15. package/lib/cli/cliOptions.js +93 -1
  16. package/lib/cli/index.js +235 -5
  17. package/lib/cli/jsBom.js +108 -26
  18. package/lib/cli/jvmBom.js +68 -12
  19. package/lib/cli/managedBom.js +264 -41
  20. package/lib/cli/nativeBom.js +923 -15
  21. package/lib/core/activity.js +120 -2
  22. package/lib/core/env.js +44 -7
  23. package/lib/core/fs.js +174 -6
  24. package/lib/core/metadataProperties.js +97 -0
  25. package/lib/core/projectConfig.js +31 -4
  26. package/lib/core/userAgent.js +151 -0
  27. package/lib/ecosystems/bunutils.js +269 -101
  28. package/lib/ecosystems/cargoCache.js +364 -0
  29. package/lib/ecosystems/caxa.js +447 -7
  30. package/lib/ecosystems/cmakeResolver.js +66 -0
  31. package/lib/ecosystems/cppEvidence.js +104 -33
  32. package/lib/ecosystems/ecosystems.js +260 -99
  33. package/lib/ecosystems/golem.js +11 -1
  34. package/lib/ecosystems/gradleutils.js +185 -9
  35. package/lib/ecosystems/jsEvidence.js +19 -20
  36. package/lib/ecosystems/kosi.js +1263 -0
  37. package/lib/ecosystems/npmutils.js +110 -9
  38. package/lib/ecosystems/parsers-crystal.js +179 -0
  39. package/lib/ecosystems/parsers-dotnet.js +120 -49
  40. package/lib/ecosystems/parsers-elm.js +816 -0
  41. package/lib/ecosystems/parsers-gleam.js +1 -1
  42. package/lib/ecosystems/parsers-go.js +47 -9
  43. package/lib/ecosystems/parsers-js.js +171 -50
  44. package/lib/ecosystems/parsers-julia.js +268 -0
  45. package/lib/ecosystems/parsers-lua.js +218 -0
  46. package/lib/ecosystems/parsers-misc.js +919 -161
  47. package/lib/ecosystems/parsers-nim.js +221 -0
  48. package/lib/ecosystems/parsers-opam.js +180 -0
  49. package/lib/ecosystems/parsers-perl.js +277 -0
  50. package/lib/ecosystems/parsers-python.js +27 -15
  51. package/lib/ecosystems/parsers-r.js +148 -0
  52. package/lib/ecosystems/parsers-rebar.js +411 -0
  53. package/lib/ecosystems/parsers-rust.js +954 -33
  54. package/lib/ecosystems/parsers-spack.js +204 -0
  55. package/lib/ecosystems/parsers-stack.js +264 -0
  56. package/lib/ecosystems/parsers-terraform.js +194 -0
  57. package/lib/ecosystems/rubyutils.js +1426 -166
  58. package/lib/ecosystems/rusi.js +158 -7
  59. package/lib/evinser/evinser.js +547 -129
  60. package/lib/evinser/swiftsem.js +1914 -302
  61. package/lib/helpers/agenticToolutils.js +925 -0
  62. package/lib/helpers/bomSigner.js +652 -221
  63. package/lib/inventory/agentFormulationParser.js +57 -4
  64. package/lib/inventory/aiCollector.js +504 -10
  65. package/lib/inventory/aiInventory.js +12 -0
  66. package/lib/inventory/analyzer.js +488 -30
  67. package/lib/inventory/astWorker.js +6 -0
  68. package/lib/inventory/atomUtils.js +568 -31
  69. package/lib/inventory/auditCategories.js +8 -0
  70. package/lib/inventory/cbomutils.js +30 -0
  71. package/lib/inventory/cdxrs.js +110 -14
  72. package/lib/inventory/communityAiConfigParser.js +215 -2
  73. package/lib/inventory/depsUtils.js +183 -11
  74. package/lib/inventory/display.js +4 -5
  75. package/lib/inventory/dosai.js +627 -13
  76. package/lib/inventory/dynamic.js +39 -1
  77. package/lib/inventory/envcontext.js +9 -6
  78. package/lib/inventory/evidenceUtils.js +26 -5
  79. package/lib/inventory/fetchBatch.js +190 -19
  80. package/lib/inventory/fetchRate.js +74 -11
  81. package/lib/inventory/forgeEnricher.js +3 -2
  82. package/lib/inventory/jvmToolEnv.js +6 -5
  83. package/lib/inventory/mcpConfigParser.js +166 -2
  84. package/lib/inventory/mcpDiscovery.js +11 -0
  85. package/lib/inventory/mcpServerManifest.js +389 -0
  86. package/lib/inventory/mcpbBundle.js +419 -0
  87. package/lib/inventory/plugins.js +3 -0
  88. package/lib/inventory/protobom.js +62 -89
  89. package/lib/inventory/protobomLoader.js +4 -0
  90. package/lib/inventory/purl.js +152 -3
  91. package/lib/inventory/rustFormulationParser.js +1 -1
  92. package/lib/inventory/sbomDocument.js +2 -70
  93. package/lib/inventory/source.js +47 -23
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/traceRunner.js +210 -21
  97. package/lib/managers/binary.js +115 -24
  98. package/lib/managers/docker.js +440 -20
  99. package/lib/managers/oci.js +39 -1
  100. package/lib/parsers/cmakeLists.js +452 -0
  101. package/lib/parsers/huggingfaceUtils.js +15 -7
  102. package/lib/parsers/largeJson.js +454 -0
  103. package/lib/parsers/npmrc.js +19 -1
  104. package/lib/parsers/onnx.js +294 -0
  105. package/lib/parsers/safetensors.js +114 -0
  106. package/lib/stages/postgen/introspection/markers.js +29 -6
  107. package/lib/stages/postgen/postgen.js +237 -16
  108. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  109. package/lib/validator/bomValidator.js +6 -16
  110. package/lib/validator/index.js +48 -10
  111. package/lib/validator/reporters/console.js +3 -0
  112. package/lib/validator/reporters/json.js +1 -0
  113. package/lib/validator/reporters/sarif.js +7 -0
  114. package/package.json +27 -27
  115. package/types/lib/audit/targets.d.ts.map +1 -1
  116. package/types/lib/cli/bomAssembly.d.ts +2 -1
  117. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  118. package/types/lib/cli/cliOptions.d.ts +33 -1
  119. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  120. package/types/lib/cli/index.d.ts +2 -2
  121. package/types/lib/cli/index.d.ts.map +1 -1
  122. package/types/lib/cli/jsBom.d.ts +23 -0
  123. package/types/lib/cli/jsBom.d.ts.map +1 -1
  124. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  125. package/types/lib/cli/managedBom.d.ts +33 -0
  126. package/types/lib/cli/managedBom.d.ts.map +1 -1
  127. package/types/lib/cli/nativeBom.d.ts +88 -8
  128. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  129. package/types/lib/core/activity.d.ts.map +1 -1
  130. package/types/lib/core/env.d.ts +21 -0
  131. package/types/lib/core/env.d.ts.map +1 -1
  132. package/types/lib/core/fs.d.ts +55 -0
  133. package/types/lib/core/fs.d.ts.map +1 -1
  134. package/types/lib/core/metadataProperties.d.ts +39 -0
  135. package/types/lib/core/metadataProperties.d.ts.map +1 -0
  136. package/types/lib/core/projectConfig.d.ts +6 -2
  137. package/types/lib/core/projectConfig.d.ts.map +1 -1
  138. package/types/lib/core/userAgent.d.ts +56 -0
  139. package/types/lib/core/userAgent.d.ts.map +1 -0
  140. package/types/lib/ecosystems/bunutils.d.ts +12 -5
  141. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  143. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  144. package/types/lib/ecosystems/caxa.d.ts +14 -0
  145. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  146. package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
  147. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  148. package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
  149. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  150. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  151. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  152. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  153. package/types/lib/ecosystems/gradleutils.d.ts +40 -4
  154. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  155. package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
  156. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  157. package/types/lib/ecosystems/kosi.d.ts +86 -0
  158. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  159. package/types/lib/ecosystems/npmutils.d.ts +42 -0
  160. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  161. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  162. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  163. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  164. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  165. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  166. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  167. package/types/lib/ecosystems/parsers-go.d.ts +10 -0
  168. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  169. package/types/lib/ecosystems/parsers-js.d.ts +10 -4
  170. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  171. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  172. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  173. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  174. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
  176. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  177. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  178. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  179. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  180. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  181. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  182. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  183. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  184. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  185. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  187. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  188. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  189. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  190. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  191. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  192. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  193. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  194. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  195. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  196. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  197. package/types/lib/ecosystems/rubyutils.d.ts +143 -2
  198. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  199. package/types/lib/ecosystems/rusi.d.ts +13 -0
  200. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  201. package/types/lib/evinser/evinser.d.ts +81 -2
  202. package/types/lib/evinser/evinser.d.ts.map +1 -1
  203. package/types/lib/evinser/swiftsem.d.ts +280 -15
  204. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  205. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  206. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  207. package/types/lib/helpers/bomSigner.d.ts +106 -13
  208. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  209. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  210. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  211. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  212. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  213. package/types/lib/inventory/atomUtils.d.ts +145 -3
  214. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +4 -0
  216. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  217. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  218. package/types/lib/inventory/cdxrs.d.ts +8 -3
  219. package/types/lib/inventory/cdxrs.d.ts.map +1 -1
  220. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  221. package/types/lib/inventory/depsUtils.d.ts +15 -0
  222. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/display.d.ts.map +1 -1
  224. package/types/lib/inventory/dosai.d.ts +87 -1
  225. package/types/lib/inventory/dosai.d.ts.map +1 -1
  226. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  227. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  228. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  229. package/types/lib/inventory/fetchBatch.d.ts +34 -0
  230. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  231. package/types/lib/inventory/fetchRate.d.ts +9 -6
  232. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  233. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  234. package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
  235. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  236. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  237. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  238. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  239. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  240. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  241. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  242. package/types/lib/inventory/plugins.d.ts.map +1 -1
  243. package/types/lib/inventory/protobom.d.ts +18 -13
  244. package/types/lib/inventory/protobom.d.ts.map +1 -1
  245. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  246. package/types/lib/inventory/purl.d.ts +71 -2
  247. package/types/lib/inventory/purl.d.ts.map +1 -1
  248. package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
  249. package/types/lib/inventory/source.d.ts.map +1 -1
  250. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  251. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  252. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  253. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  254. package/types/lib/inventory/traceRunner.d.ts +47 -1
  255. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  256. package/types/lib/managers/binary.d.ts +30 -1
  257. package/types/lib/managers/binary.d.ts.map +1 -1
  258. package/types/lib/managers/docker.d.ts +29 -0
  259. package/types/lib/managers/docker.d.ts.map +1 -1
  260. package/types/lib/managers/oci.d.ts +14 -0
  261. package/types/lib/managers/oci.d.ts.map +1 -1
  262. package/types/lib/parsers/cmakeLists.d.ts +76 -0
  263. package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
  264. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  265. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  266. package/types/lib/parsers/largeJson.d.ts +52 -0
  267. package/types/lib/parsers/largeJson.d.ts.map +1 -0
  268. package/types/lib/parsers/npmrc.d.ts +5 -0
  269. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  270. package/types/lib/parsers/onnx.d.ts +28 -0
  271. package/types/lib/parsers/onnx.d.ts.map +1 -0
  272. package/types/lib/parsers/safetensors.d.ts +22 -0
  273. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
  275. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
  276. package/types/lib/stages/postgen/postgen.d.ts +33 -0
  277. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  278. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  279. package/types/lib/validator/index.d.ts +10 -1
  280. package/types/lib/validator/index.d.ts.map +1 -1
  281. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  282. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  283. package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
@@ -0,0 +1,294 @@
1
+ import { closeSync, openSync, readSync, statSync } from "node:fs";
2
+
3
+ // An ONNX model is a protobuf-encoded ModelProto. Only the header fields are
4
+ // read here; the graph payload is skipped by offset, so inspection stays
5
+ // cheap for multi-gigabyte files and never loads tensors.
6
+ //
7
+ // ModelProto fields (https://onnx.ai/onnx/repo-docs/Model.html):
8
+ // 1 ir_version (varint), 2 producer_name (string),
9
+ // 3 producer_version (string), 4 domain (string),
10
+ // 5 model_version (varint), 6 doc_string (string),
11
+ // 7 graph (message, skipped), 8 opset_import (repeated message).
12
+ // Each opset_import entry is an OperatorSetIdProto:
13
+ // 1 domain (string), 2 version (varint).
14
+
15
+ /** Fields are walked from the front; a hostile file cannot force a scan
16
+ * deeper than this many top-level entries. */
17
+ const MAX_TOP_LEVEL_FIELDS = 128;
18
+ /** Largest single field payload that will actually be read into memory. */
19
+ const MAX_FIELD_BYTES = 4096;
20
+
21
+ /**
22
+ * Read the identifying header fields of an ONNX model file.
23
+ *
24
+ * Only `ir_version`, `producer_name`, `producer_version`, `model_version`,
25
+ * and the `opset_import` entries are extracted. The graph and every other
26
+ * payload are skipped by offset without being read, so no tensor data is
27
+ * ever loaded and no model code is executed.
28
+ *
29
+ * @param {string} filePath Path to the `.onnx` file
30
+ * @returns {{
31
+ * irVersion: number,
32
+ * producerName: string,
33
+ * producerVersion: string,
34
+ * modelVersion: number,
35
+ * opsets: Array<{domain: string, version: number}>,
36
+ * } | undefined} Header summary, or undefined when it cannot be read
37
+ */
38
+ export function readOnnxHeader(filePath) {
39
+ let fd;
40
+ try {
41
+ const stats = statSync(filePath);
42
+ if (!stats.isFile() || stats.size < 2) {
43
+ return undefined;
44
+ }
45
+ fd = openSync(filePath, "r");
46
+ // The whole file is not buffered; fields are fetched by offset. A small
47
+ // sliding buffer is re-used for varints and short strings.
48
+ const reader = new OffsetReader(fd, stats.size);
49
+ const result = {
50
+ irVersion: 0,
51
+ producerName: "",
52
+ producerVersion: "",
53
+ modelVersion: 0,
54
+ opsets: [],
55
+ };
56
+ let fieldsRead = 0;
57
+ for (let i = 0; i < MAX_TOP_LEVEL_FIELDS; i += 1) {
58
+ const tag = reader.readVarint();
59
+ if (tag === undefined) {
60
+ break;
61
+ }
62
+ const fieldNumber = Number(tag >> 3n);
63
+ const wireType = Number(tag & 7n);
64
+ if (fieldNumber === 0) {
65
+ break;
66
+ }
67
+ if (wireType === 0) {
68
+ const value = reader.readVarint();
69
+ if (value === undefined) {
70
+ break;
71
+ }
72
+ fieldsRead += 1;
73
+ if (fieldNumber === 1) {
74
+ result.irVersion = Number(value);
75
+ } else if (fieldNumber === 5) {
76
+ result.modelVersion = Number(value);
77
+ }
78
+ continue;
79
+ }
80
+ if (wireType !== 2) {
81
+ // Group and fixed-width wire types do not appear in ModelProto at the
82
+ // top level; stop rather than guess their length.
83
+ break;
84
+ }
85
+ const length = reader.readVarint();
86
+ if (length === undefined) {
87
+ break;
88
+ }
89
+ const byteLength = Number(length);
90
+ if (!Number.isSafeInteger(byteLength) || byteLength < 0) {
91
+ break;
92
+ }
93
+ if (fieldNumber === 8 && byteLength <= MAX_FIELD_BYTES) {
94
+ const payload = reader.readBytes(byteLength);
95
+ if (!payload) {
96
+ break;
97
+ }
98
+ const opset = parseOperatorSetEntry(payload);
99
+ if (opset) {
100
+ result.opsets.push(opset);
101
+ }
102
+ fieldsRead += 1;
103
+ continue;
104
+ }
105
+ if (
106
+ (fieldNumber === 2 || fieldNumber === 3) &&
107
+ byteLength <= MAX_FIELD_BYTES
108
+ ) {
109
+ const payload = reader.readBytes(byteLength);
110
+ if (!payload) {
111
+ break;
112
+ }
113
+ if (fieldNumber === 2) {
114
+ result.producerName = payload.toString("utf-8");
115
+ } else {
116
+ result.producerVersion = payload.toString("utf-8");
117
+ }
118
+ fieldsRead += 1;
119
+ continue;
120
+ }
121
+ // Skip anything else (notably the graph) by offset.
122
+ if (!reader.skip(byteLength)) {
123
+ break;
124
+ }
125
+ fieldsRead += 1;
126
+ }
127
+ // A real ModelProto always carries at least an ir_version; zero consumed
128
+ // fields means the file is not an ONNX model at all.
129
+ if (fieldsRead === 0) {
130
+ return undefined;
131
+ }
132
+ return result;
133
+ } catch {
134
+ return undefined;
135
+ } finally {
136
+ if (fd !== undefined) {
137
+ closeSync(fd);
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Reader that fetches bytes from a file descriptor by absolute offset.
144
+ *
145
+ * @param {number} fd Open file descriptor
146
+ * @param {number} fileSize Total file size
147
+ */
148
+ class OffsetReader {
149
+ constructor(fd, fileSize) {
150
+ this.fd = fd;
151
+ this.fileSize = fileSize;
152
+ this.offset = 0;
153
+ this.buffer = Buffer.alloc(16);
154
+ }
155
+
156
+ /**
157
+ * Read a protobuf varint at the cursor.
158
+ *
159
+ * @returns {bigint|undefined} Value, or undefined at end of input
160
+ */
161
+ readVarint() {
162
+ let result = 0n;
163
+ let shift = 0n;
164
+ for (let i = 0; i < 10; i += 1) {
165
+ if (!this.fillBuffer(1)) {
166
+ return undefined;
167
+ }
168
+ const byte = this.buffer[0];
169
+ this.offset += 1;
170
+ result |= BigInt(byte & 0x7f) << shift;
171
+ if ((byte & 0x80) === 0) {
172
+ return result;
173
+ }
174
+ shift += 7n;
175
+ }
176
+ return undefined;
177
+ }
178
+
179
+ /**
180
+ * Read a length-delimited payload at the cursor.
181
+ *
182
+ * @param {number} length Payload length
183
+ * @returns {Buffer|undefined} Payload, or undefined when unavailable
184
+ */
185
+ readBytes(length) {
186
+ const payload = Buffer.alloc(length);
187
+ const bytesRead = readSync(this.fd, payload, 0, length, this.offset);
188
+ if (bytesRead !== length) {
189
+ return undefined;
190
+ }
191
+ this.offset += length;
192
+ return payload;
193
+ }
194
+
195
+ /**
196
+ * Advance the cursor without reading the payload.
197
+ *
198
+ * @param {number} length Bytes to skip
199
+ * @returns {boolean} true when the skip stayed within the file
200
+ */
201
+ skip(length) {
202
+ const next = this.offset + length;
203
+ if (next > this.fileSize) {
204
+ return false;
205
+ }
206
+ this.offset = next;
207
+ return true;
208
+ }
209
+
210
+ /**
211
+ * Ensure up to `length` bytes are available in the scratch buffer.
212
+ *
213
+ * @param {number} length Bytes needed
214
+ * @returns {boolean} true when the read succeeded
215
+ */
216
+ fillBuffer(length) {
217
+ if (this.offset + length > this.fileSize) {
218
+ return false;
219
+ }
220
+ const bytesRead = readSync(this.fd, this.buffer, 0, length, this.offset);
221
+ return bytesRead === length;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Parse one OperatorSetIdProto payload.
227
+ *
228
+ * @param {Buffer} payload Field payload
229
+ * @returns {{domain: string, version: number}|undefined} Parsed entry
230
+ */
231
+ function parseOperatorSetEntry(payload) {
232
+ let pos = 0;
233
+ let domain = "";
234
+ let version = 0;
235
+ while (pos < payload.length) {
236
+ const tag = readVarintFrom(payload, pos);
237
+ if (!tag) {
238
+ break;
239
+ }
240
+ pos = tag.next;
241
+ const fieldNumber = Number(tag.value >> 3n);
242
+ const wireType = Number(tag.value & 7n);
243
+ if (wireType === 0) {
244
+ const value = readVarintFrom(payload, pos);
245
+ if (!value) {
246
+ break;
247
+ }
248
+ pos = value.next;
249
+ if (fieldNumber === 2) {
250
+ version = Number(value.value);
251
+ }
252
+ } else if (wireType === 2) {
253
+ const length = readVarintFrom(payload, pos);
254
+ if (!length) {
255
+ break;
256
+ }
257
+ pos = length.next;
258
+ const byteLength = Number(length.value);
259
+ if (pos + byteLength > payload.length) {
260
+ break;
261
+ }
262
+ if (fieldNumber === 1) {
263
+ domain = payload.toString("utf-8", pos, pos + byteLength);
264
+ }
265
+ pos += byteLength;
266
+ } else {
267
+ break;
268
+ }
269
+ }
270
+ return { domain, version };
271
+ }
272
+
273
+ /**
274
+ * Read a varint from a buffer at a position.
275
+ *
276
+ * @param {Buffer} buffer Source buffer
277
+ * @param {number} pos Start position
278
+ * @returns {{value: bigint, next: number}|undefined} Result, or undefined
279
+ */
280
+ function readVarintFrom(buffer, pos) {
281
+ let value = 0n;
282
+ let shift = 0n;
283
+ let cursor = pos;
284
+ for (let i = 0; i < 10 && cursor < buffer.length; i += 1) {
285
+ const byte = buffer[cursor];
286
+ cursor += 1;
287
+ value |= BigInt(byte & 0x7f) << shift;
288
+ if ((byte & 0x80) === 0) {
289
+ return { value, next: cursor };
290
+ }
291
+ shift += 7n;
292
+ }
293
+ return undefined;
294
+ }
@@ -0,0 +1,114 @@
1
+ import { closeSync, openSync, readSync, statSync } from "node:fs";
2
+
3
+ // A safetensors file begins with an 8-byte little-endian unsigned integer
4
+ // giving the byte length of a JSON header, followed by that JSON header, and
5
+ // then the raw tensor buffers. Only the header is parsed here: tensor data is
6
+ // never read, so inspection stays cheap and side-effect free.
7
+ const HEADER_LENGTH_BYTES = 8;
8
+ // A header longer than this almost certainly indicates a non-safetensors file
9
+ // or a corrupt/hostile input; refuse to allocate for it.
10
+ const MAX_HEADER_BYTES = 100 * 1024 * 1024;
11
+
12
+ /**
13
+ * Read and parse the JSON header of a safetensors model file.
14
+ *
15
+ * Only the leading header is read; tensor payloads are ignored. The returned
16
+ * object separates the optional `__metadata__` map from the per-tensor
17
+ * descriptors.
18
+ *
19
+ * @param {string} filePath Path to a `.safetensors` file
20
+ * @returns {{
21
+ * metadata: Object,
22
+ * tensorCount: number,
23
+ * dtypes: string[],
24
+ * totalParameters: number,
25
+ * } | undefined} Parsed header summary, or undefined when it cannot be read
26
+ */
27
+ export function readSafetensorsHeader(filePath) {
28
+ let fd;
29
+ try {
30
+ const stats = statSync(filePath);
31
+ if (stats.size < HEADER_LENGTH_BYTES) {
32
+ return undefined;
33
+ }
34
+ fd = openSync(filePath, "r");
35
+ const lengthBuffer = Buffer.alloc(HEADER_LENGTH_BYTES);
36
+ readSync(fd, lengthBuffer, 0, HEADER_LENGTH_BYTES, 0);
37
+ const headerLength = Number(lengthBuffer.readBigUInt64LE(0));
38
+ if (
39
+ !Number.isSafeInteger(headerLength) ||
40
+ headerLength <= 0 ||
41
+ headerLength > MAX_HEADER_BYTES ||
42
+ headerLength + HEADER_LENGTH_BYTES > stats.size
43
+ ) {
44
+ return undefined;
45
+ }
46
+ const headerBuffer = Buffer.alloc(headerLength);
47
+ readSync(fd, headerBuffer, 0, headerLength, HEADER_LENGTH_BYTES);
48
+ const header = JSON.parse(headerBuffer.toString("utf-8"));
49
+ if (!header || typeof header !== "object") {
50
+ return undefined;
51
+ }
52
+ return summarizeHeader(header);
53
+ } catch {
54
+ return undefined;
55
+ } finally {
56
+ if (fd !== undefined) {
57
+ try {
58
+ closeSync(fd);
59
+ } catch {
60
+ // ignore close failures
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Summarize a parsed safetensors header into metadata and tensor statistics.
68
+ *
69
+ * @param {Object} header Parsed safetensors JSON header
70
+ * @returns {{
71
+ * metadata: Object,
72
+ * tensorCount: number,
73
+ * dtypes: string[],
74
+ * totalParameters: number,
75
+ * }} Header summary
76
+ */
77
+ function summarizeHeader(header) {
78
+ const metadata =
79
+ header.__metadata__ && typeof header.__metadata__ === "object"
80
+ ? header.__metadata__
81
+ : {};
82
+ const dtypes = new Set();
83
+ let tensorCount = 0;
84
+ let totalParameters = 0;
85
+ for (const [key, descriptor] of Object.entries(header)) {
86
+ if (
87
+ key === "__metadata__" ||
88
+ !descriptor ||
89
+ typeof descriptor !== "object"
90
+ ) {
91
+ continue;
92
+ }
93
+ tensorCount++;
94
+ if (typeof descriptor.dtype === "string") {
95
+ dtypes.add(descriptor.dtype);
96
+ }
97
+ if (Array.isArray(descriptor.shape) && descriptor.shape.length) {
98
+ const elements = descriptor.shape.reduce(
99
+ (product, dimension) =>
100
+ Number.isFinite(dimension) ? product * dimension : product,
101
+ 1,
102
+ );
103
+ if (Number.isFinite(elements)) {
104
+ totalParameters += elements;
105
+ }
106
+ }
107
+ }
108
+ return {
109
+ metadata,
110
+ tensorCount,
111
+ dtypes: Array.from(dtypes).sort(),
112
+ totalParameters,
113
+ };
114
+ }
@@ -28,8 +28,9 @@ const MAX_SUBDIRS_SCANNED = 100;
28
28
  * Marker table: ecosystem → manifest and lockfile names that indicate the
29
29
  * ecosystem is part of the project. Entries are either exact file names or
30
30
  * `{ suffix }` matchers for extension-driven ecosystems. Ecosystems cdxgen
31
- * cannot parse (elm, crystal, nim, perl, r) are included so the reflection
32
- * can report them as coverage gaps instead of silently ignoring them.
31
+ * cannot parse (currently only dub, the D package manager) are included so
32
+ * the reflection can report them as coverage gaps instead of silently
33
+ * ignoring them.
33
34
  *
34
35
  * @type {Readonly<Record<string, {names: string[], suffixes: string[]}>>}
35
36
  */
@@ -103,18 +104,40 @@ export const ECOSYSTEM_MARKERS = Object.freeze({
103
104
  swift: { names: ["Package.swift", "Package.resolved"], suffixes: [] },
104
105
  elixir: { names: ["mix.exs", "mix.lock"], suffixes: [] },
105
106
  c: {
106
- names: ["CMakeLists.txt", "conanfile.txt", "conanfile.py"],
107
+ names: [
108
+ "CMakeLists.txt",
109
+ "conanfile.txt",
110
+ "conanfile.py",
111
+ "xmake-requires.lock",
112
+ ],
107
113
  suffixes: [],
108
114
  },
109
- haskell: { names: ["cabal.project", "stack.yaml"], suffixes: [".cabal"] },
110
- elm: { names: ["elm.json"], suffixes: [] },
115
+ haskell: {
116
+ names: ["cabal.project", "stack.yaml", "stack.yaml.lock", "stack.lock"],
117
+ suffixes: [".cabal"],
118
+ },
119
+ elm: { names: ["elm.json", "elm-package.json"], suffixes: [] },
120
+ dub: { names: ["dub.json", "dub.sdl"], suffixes: [] },
111
121
  crystal: { names: ["shard.yml", "shard.lock"], suffixes: [] },
112
122
  nim: { names: ["nim.cfg", "config.nims"], suffixes: [".nimble"] },
113
123
  perl: {
114
- names: ["cpanfile", "META.json", "META.yml", "Makefile.PL", "Build.PL"],
124
+ names: [
125
+ "cpanfile",
126
+ "cpanfile.snapshot",
127
+ "META.json",
128
+ "META.yml",
129
+ "Makefile.PL",
130
+ "Build.PL",
131
+ ],
115
132
  suffixes: [],
116
133
  },
117
134
  r: { names: ["DESCRIPTION", "renv.lock"], suffixes: [] },
135
+ julia: { names: ["Project.toml", "Manifest.toml"], suffixes: [] },
136
+ erlang: { names: ["rebar.config", "rebar.lock"], suffixes: [] },
137
+ ocaml: { names: ["dune-project"], suffixes: [".opam"] },
138
+ lua: { names: ["luarocks.lock"], suffixes: [".rockspec"] },
139
+ spack: { names: ["spack.yaml", "spack.lock"], suffixes: [] },
140
+ terraform: { names: [".terraform.lock.hcl"], suffixes: [] },
118
141
  });
119
142
 
120
143
  /** Marker file name → ecosystems claiming it, and marker suffix → claiming