@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,454 @@
1
+ import { Buffer, constants } from "node:buffer";
2
+ import { closeSync, openSync, readFileSync, readSync, statSync } from "node:fs";
3
+
4
+ /**
5
+ * The largest file that `JSON.parse(readFileSync(file, "utf-8"))` can always
6
+ * decode. V8 caps a string at this many UTF-16 code units, and UTF-8 never
7
+ * decodes to more code units than it has bytes.
8
+ */
9
+ export const MAX_JSON_TEXT_BYTES = constants.MAX_STRING_LENGTH;
10
+
11
+ // A value at most this large is decoded with one JSON.parse. A larger object is
12
+ // decoded member by member, and a larger array in runs of elements of about
13
+ // this size, so no string ever holds more than one run.
14
+ const BATCH_BYTES = 16 * 1024 * 1024;
15
+ const CHUNK_BYTES = 8 * 1024 * 1024;
16
+
17
+ const QUOTE = 0x22;
18
+ const BACKSLASH = 0x5c;
19
+ const COMMA = 0x2c;
20
+ const COLON = 0x3a;
21
+ const OPEN_BRACE = 0x7b;
22
+ const CLOSE_BRACE = 0x7d;
23
+ const OPEN_BRACKET = 0x5b;
24
+ const CLOSE_BRACKET = 0x5d;
25
+
26
+ const isWhitespace = (byte) =>
27
+ byte === 0x20 || byte === 0x0a || byte === 0x0d || byte === 0x09;
28
+
29
+ // "-", a digit, or the first letter of true, false or null.
30
+ const isScalarStart = (byte) =>
31
+ byte === 0x2d ||
32
+ (byte >= 0x30 && byte <= 0x39) ||
33
+ byte === 0x74 ||
34
+ byte === 0x66 ||
35
+ byte === 0x6e;
36
+
37
+ // Assign like JSON.parse does: "__proto__" becomes an own property instead of
38
+ // replacing the prototype.
39
+ function defineMember(target, key, value) {
40
+ if (key === "__proto__") {
41
+ Object.defineProperty(target, key, {
42
+ configurable: true,
43
+ enumerable: true,
44
+ value,
45
+ writable: true,
46
+ });
47
+ } else {
48
+ target[key] = value;
49
+ }
50
+ }
51
+
52
+ function applySpec(value, spec) {
53
+ if (!spec || value === null || typeof value !== "object") {
54
+ return value;
55
+ }
56
+ if (Array.isArray(value)) {
57
+ return spec.filter
58
+ ? value.filter((element) => spec.filter(element))
59
+ : value;
60
+ }
61
+ if (!spec.members) {
62
+ return value;
63
+ }
64
+ const picked = {};
65
+ for (const key of Object.keys(value)) {
66
+ if (Object.hasOwn(spec.members, key)) {
67
+ defineMember(picked, key, applySpec(value[key], spec.members[key]));
68
+ }
69
+ }
70
+ return picked;
71
+ }
72
+
73
+ /**
74
+ * Reads bytes [start, end) of an open file a chunk at a time, and can capture
75
+ * the bytes of a span that crosses chunk boundaries.
76
+ */
77
+ class ByteCursor {
78
+ constructor(fd, start, end, chunkBytes) {
79
+ this.fd = fd;
80
+ this.end = end;
81
+ this.buf = Buffer.allocUnsafe(
82
+ Math.max(1, Math.min(chunkBytes, end - start)),
83
+ );
84
+ this.bufStart = start;
85
+ this.len = 0;
86
+ this.pos = 0;
87
+ this.captureFrom = -1;
88
+ this.parts = [];
89
+ this.partsBytes = 0;
90
+ }
91
+
92
+ get offset() {
93
+ return this.bufStart + this.pos;
94
+ }
95
+
96
+ fill() {
97
+ if (this.captureFrom >= 0) {
98
+ if (this.len > this.captureFrom) {
99
+ const part = Buffer.from(this.buf.subarray(this.captureFrom, this.len));
100
+ this.parts.push(part);
101
+ this.partsBytes += part.length;
102
+ }
103
+ this.captureFrom = 0;
104
+ }
105
+ this.bufStart += this.len;
106
+ const want = Math.min(this.buf.length, this.end - this.bufStart);
107
+ this.len =
108
+ want > 0 ? readSync(this.fd, this.buf, 0, want, this.bufStart) : 0;
109
+ this.pos = 0;
110
+ return this.len > 0;
111
+ }
112
+
113
+ peek() {
114
+ if (this.pos >= this.len && !this.fill()) {
115
+ return -1;
116
+ }
117
+ return this.buf[this.pos];
118
+ }
119
+
120
+ // The next byte that is not whitespace, left unconsumed; -1 at the end.
121
+ peekToken() {
122
+ for (;;) {
123
+ const byte = this.peek();
124
+ if (!isWhitespace(byte)) {
125
+ return byte;
126
+ }
127
+ this.pos++;
128
+ }
129
+ }
130
+
131
+ nextToken() {
132
+ const byte = this.peekToken();
133
+ if (byte >= 0) {
134
+ this.pos++;
135
+ }
136
+ return byte;
137
+ }
138
+
139
+ fail(message) {
140
+ throw new SyntaxError(`${message} at byte ${this.offset} of the JSON file`);
141
+ }
142
+
143
+ beginCapture() {
144
+ this.peek();
145
+ this.captureFrom = this.pos;
146
+ this.parts = [];
147
+ this.partsBytes = 0;
148
+ }
149
+
150
+ capturedBytes() {
151
+ return this.partsBytes + this.pos - this.captureFrom;
152
+ }
153
+
154
+ // The captured text up to `until`, an index into the current chunk.
155
+ endCapture(until = this.pos) {
156
+ const tail = this.buf.subarray(this.captureFrom, until);
157
+ const text = this.parts.length
158
+ ? Buffer.concat([...this.parts, tail]).toString("utf8")
159
+ : tail.toString("utf8");
160
+ this.captureFrom = -1;
161
+ this.parts = [];
162
+ this.partsBytes = 0;
163
+ return text;
164
+ }
165
+
166
+ // Move past one JSON value without decoding it. Brackets are balanced and
167
+ // strings are honoured, but the value is not otherwise validated; whatever
168
+ // decodes it later does that.
169
+ skipValue() {
170
+ const first = this.peekToken();
171
+ if (first === QUOTE) {
172
+ this.pos++;
173
+ this.skipStringBody();
174
+ return;
175
+ }
176
+ if (first === OPEN_BRACE || first === OPEN_BRACKET) {
177
+ this.skipContainer();
178
+ return;
179
+ }
180
+ if (!isScalarStart(first)) {
181
+ this.fail("Unexpected token");
182
+ }
183
+ // A number, true, false or null runs to the next delimiter.
184
+ for (;;) {
185
+ const byte = this.peek();
186
+ if (
187
+ byte < 0 ||
188
+ byte === COMMA ||
189
+ byte === CLOSE_BRACE ||
190
+ byte === CLOSE_BRACKET ||
191
+ isWhitespace(byte)
192
+ ) {
193
+ return;
194
+ }
195
+ this.pos++;
196
+ }
197
+ }
198
+
199
+ skipStringBody() {
200
+ let escaped = false;
201
+ for (;;) {
202
+ if (this.pos >= this.len && !this.fill()) {
203
+ this.fail("Unterminated string");
204
+ }
205
+ const { buf, len } = this;
206
+ for (let i = this.pos; i < len; i++) {
207
+ const byte = buf[i];
208
+ if (escaped) {
209
+ escaped = false;
210
+ } else if (byte === BACKSLASH) {
211
+ escaped = true;
212
+ } else if (byte === QUOTE) {
213
+ this.pos = i + 1;
214
+ return;
215
+ }
216
+ }
217
+ this.pos = len;
218
+ }
219
+ }
220
+
221
+ skipContainer() {
222
+ let depth = 0;
223
+ let inString = false;
224
+ let escaped = false;
225
+ for (;;) {
226
+ if (this.pos >= this.len && !this.fill()) {
227
+ this.fail("Unterminated container");
228
+ }
229
+ const { buf, len } = this;
230
+ for (let i = this.pos; i < len; i++) {
231
+ const byte = buf[i];
232
+ if (inString) {
233
+ if (escaped) {
234
+ escaped = false;
235
+ } else if (byte === BACKSLASH) {
236
+ escaped = true;
237
+ } else if (byte === QUOTE) {
238
+ inString = false;
239
+ }
240
+ } else if (byte === QUOTE) {
241
+ inString = true;
242
+ } else if (byte === OPEN_BRACE || byte === OPEN_BRACKET) {
243
+ depth++;
244
+ } else if (byte === CLOSE_BRACE || byte === CLOSE_BRACKET) {
245
+ depth--;
246
+ if (depth === 0) {
247
+ this.pos = i + 1;
248
+ return;
249
+ }
250
+ }
251
+ }
252
+ this.pos = len;
253
+ }
254
+ }
255
+ }
256
+
257
+ // The members of the object at the cursor, as { key, start, end } byte ranges
258
+ // of their values, in file order. A repeated key keeps its first position and
259
+ // its last range, which is what JSON.parse does.
260
+ function scanObjectMembers(cursor) {
261
+ if (cursor.nextToken() !== OPEN_BRACE) {
262
+ cursor.fail("Expected an object");
263
+ }
264
+ const members = new Map();
265
+ if (cursor.peekToken() === CLOSE_BRACE) {
266
+ cursor.pos++;
267
+ return members;
268
+ }
269
+ for (;;) {
270
+ if (cursor.peekToken() !== QUOTE) {
271
+ cursor.fail("Expected a member name");
272
+ }
273
+ cursor.beginCapture();
274
+ cursor.skipValue();
275
+ const key = JSON.parse(cursor.endCapture());
276
+ if (cursor.nextToken() !== COLON) {
277
+ cursor.fail("Expected ':'");
278
+ }
279
+ cursor.peekToken();
280
+ const start = cursor.offset;
281
+ cursor.skipValue();
282
+ members.set(key, { start, end: cursor.offset });
283
+ const delimiter = cursor.nextToken();
284
+ if (delimiter === CLOSE_BRACE) {
285
+ return members;
286
+ }
287
+ if (delimiter !== COMMA) {
288
+ cursor.fail("Expected ',' or '}'");
289
+ }
290
+ }
291
+ }
292
+
293
+ // Decode the array at the cursor in runs of whole elements, keeping those that
294
+ // `filter` accepts.
295
+ function readArrayElements(cursor, filter, batchBytes) {
296
+ if (cursor.nextToken() !== OPEN_BRACKET) {
297
+ cursor.fail("Expected an array");
298
+ }
299
+ const elements = [];
300
+ if (cursor.peekToken() === CLOSE_BRACKET) {
301
+ cursor.pos++;
302
+ return elements;
303
+ }
304
+ const flush = () => {
305
+ // Everything captured but the delimiter just consumed.
306
+ for (const element of JSON.parse(
307
+ `[${cursor.endCapture(cursor.pos - 1)}]`,
308
+ )) {
309
+ if (!filter || filter(element)) {
310
+ elements.push(element);
311
+ }
312
+ }
313
+ };
314
+ cursor.beginCapture();
315
+ for (;;) {
316
+ cursor.skipValue();
317
+ const delimiter = cursor.nextToken();
318
+ if (delimiter === CLOSE_BRACKET) {
319
+ flush();
320
+ return elements;
321
+ }
322
+ if (delimiter !== COMMA) {
323
+ cursor.fail("Expected ',' or ']'");
324
+ }
325
+ if (cursor.capturedBytes() >= batchBytes) {
326
+ flush();
327
+ cursor.peekToken();
328
+ cursor.beginCapture();
329
+ }
330
+ }
331
+ }
332
+
333
+ function readBytes(fd, start, end) {
334
+ const bytes = Buffer.allocUnsafe(end - start);
335
+ let read = 0;
336
+ while (read < bytes.length) {
337
+ const count = readSync(fd, bytes, read, bytes.length - read, start + read);
338
+ if (!count) {
339
+ throw new SyntaxError("Unexpected end of the JSON file");
340
+ }
341
+ read += count;
342
+ }
343
+ return bytes.toString("utf8");
344
+ }
345
+
346
+ function decodeRange(fd, start, end, spec, options) {
347
+ if (end - start <= options.batchBytes) {
348
+ return applySpec(JSON.parse(readBytes(fd, start, end)), spec);
349
+ }
350
+ const cursor = new ByteCursor(fd, start, end, options.chunkBytes);
351
+ const first = cursor.peekToken();
352
+ let value;
353
+ if (first === OPEN_BRACE) {
354
+ value = {};
355
+ for (const [key, range] of scanObjectMembers(cursor)) {
356
+ if (spec?.members && !Object.hasOwn(spec.members, key)) {
357
+ continue;
358
+ }
359
+ defineMember(
360
+ value,
361
+ key,
362
+ decodeRange(fd, range.start, range.end, spec?.members?.[key], options),
363
+ );
364
+ }
365
+ } else if (first === OPEN_BRACKET) {
366
+ value = readArrayElements(cursor, spec?.filter, options.batchBytes);
367
+ } else {
368
+ // A string or number this large has no smaller parts; JSON.parse reports
369
+ // it the way it would for the whole file.
370
+ return JSON.parse(readBytes(fd, start, end));
371
+ }
372
+ if (cursor.peekToken() !== -1) {
373
+ cursor.fail("Unexpected data after the JSON value");
374
+ }
375
+ return value;
376
+ }
377
+
378
+ function withFile(filePath, callback) {
379
+ const fd = openSync(filePath, "r");
380
+ try {
381
+ return callback(fd);
382
+ } finally {
383
+ closeSync(fd);
384
+ }
385
+ }
386
+
387
+ function resolveOptions(options = {}) {
388
+ return {
389
+ batchBytes: options.batchBytes ?? BATCH_BYTES,
390
+ chunkBytes: options.chunkBytes ?? CHUNK_BYTES,
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Locate the value of each member of the top-level object in a JSON file,
396
+ * without decoding any of them.
397
+ *
398
+ * @param {string} filePath JSON file whose top-level value is an object
399
+ * @param {Object} [options] Chunk size override, for tests
400
+ * @returns {Map<string, {start: number, end: number}>} Byte range of each member's value, in file order. A repeated key keeps its last value, as JSON.parse does.
401
+ * @throws {SyntaxError} When the top-level value is not a well-formed object
402
+ */
403
+ export function indexJsonObject(filePath, options = {}) {
404
+ const { chunkBytes } = resolveOptions(options);
405
+ return withFile(filePath, (fd) => {
406
+ const cursor = new ByteCursor(fd, 0, statSync(filePath).size, chunkBytes);
407
+ const members = scanObjectMembers(cursor);
408
+ if (cursor.peekToken() !== -1) {
409
+ cursor.fail("Unexpected data after the JSON value");
410
+ }
411
+ return members;
412
+ });
413
+ }
414
+
415
+ /**
416
+ * Decode the JSON value stored in a byte range of a file, without ever holding
417
+ * more than a bounded run of it in one string.
418
+ *
419
+ * `spec` narrows what is kept. `{ members: { name: spec } }` keeps only the
420
+ * named members of an object, each narrowed by its own spec. `{ filter }`
421
+ * keeps the elements of an array that `filter(element)` accepts. Anything else
422
+ * keeps the whole value.
423
+ *
424
+ * @param {string} filePath JSON file
425
+ * @param {{start: number, end: number}} range Byte range of the value, such as one returned by indexJsonObject
426
+ * @param {Object} [spec] What to keep
427
+ * @param {Object} [options] Batch and chunk size overrides, for tests
428
+ * @returns {*} The decoded value
429
+ * @throws {SyntaxError} When the value is malformed
430
+ */
431
+ export function readJsonRange(filePath, range, spec = undefined, options = {}) {
432
+ return withFile(filePath, (fd) =>
433
+ decodeRange(fd, range.start, range.end, spec, resolveOptions(options)),
434
+ );
435
+ }
436
+
437
+ /**
438
+ * Parse a JSON file of any size. A file that fits in one string is decoded
439
+ * with JSON.parse, and a larger one is streamed through readJsonRange, so the
440
+ * result is the same either way.
441
+ *
442
+ * @param {string} filePath JSON file
443
+ * @param {Object} [spec] What to keep, as for readJsonRange
444
+ * @param {Object} [options] Size overrides, for tests: maxTextBytes, batchBytes, chunkBytes
445
+ * @returns {*} The decoded value
446
+ * @throws {SyntaxError} When the file is malformed
447
+ */
448
+ export function readJsonFile(filePath, spec = undefined, options = {}) {
449
+ const size = statSync(filePath).size;
450
+ if (size <= (options.maxTextBytes ?? MAX_JSON_TEXT_BYTES)) {
451
+ return applySpec(JSON.parse(readFileSync(filePath, "utf-8")), spec);
452
+ }
453
+ return readJsonRange(filePath, { start: 0, end: size }, spec, options);
454
+ }
@@ -23,9 +23,24 @@ export const DEFAULT_NPMRC_BLOCKLIST = new Set([
23
23
  "rebuild-bundle",
24
24
  ]);
25
25
 
26
+ /**
27
+ * Keys that collide with JavaScript object internals. A crafted .npmrc using
28
+ * them must never reach assignment on the result object: `result.__proto__`
29
+ * reads or replaces Object.prototype instead of creating an own property,
30
+ * which historically crashed the array branch with a TypeError.
31
+ *
32
+ * @type {Set<string>}
33
+ */
34
+ const UNSAFE_NPMRC_KEYS = new Set(["__proto__", "constructor", "prototype"]);
35
+
26
36
  /**
27
37
  * Parse .npmrc content into a plain key-value object.
28
38
  *
39
+ * Keys that collide with JavaScript object internals are skipped: assigning
40
+ * them on a plain object either silently mutates Object.prototype semantics or,
41
+ * for the array syntax, crashed the parser with a TypeError because
42
+ * `result.__proto__` resolves to Object.prototype instead of an own property.
43
+ *
29
44
  * @param {string} content - Raw .npmrc file content
30
45
  * @returns {Object} Parsed key-value pairs
31
46
  */
@@ -46,6 +61,9 @@ export function parseNpmrc(content) {
46
61
  if (isArray) {
47
62
  key = key.slice(0, -2);
48
63
  }
64
+ if (UNSAFE_NPMRC_KEYS.has(key)) {
65
+ continue;
66
+ }
49
67
  if (
50
68
  (value.startsWith('"') && value.endsWith('"')) ||
51
69
  (value.startsWith("'") && value.endsWith("'"))
@@ -53,7 +71,7 @@ export function parseNpmrc(content) {
53
71
  value = value.slice(1, -1);
54
72
  }
55
73
  if (isArray) {
56
- if (!result[key]) result[key] = [];
74
+ if (!Array.isArray(result[key])) result[key] = [];
57
75
  result[key].push(value);
58
76
  } else {
59
77
  result[key] = value;