@blamejs/core 0.15.12 → 0.15.14

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 (365) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +45 -29
  4. package/lib/acme.js +6 -5
  5. package/lib/agent-event-bus.js +18 -4
  6. package/lib/agent-idempotency.js +7 -6
  7. package/lib/agent-orchestrator.js +2 -5
  8. package/lib/agent-saga.js +3 -5
  9. package/lib/agent-snapshot.js +32 -2
  10. package/lib/agent-tenant.js +2 -5
  11. package/lib/ai-adverse-decision.js +2 -15
  12. package/lib/ai-aedt-bias-audit.js +2 -1
  13. package/lib/ai-capability.js +1 -6
  14. package/lib/ai-content-detect.js +1 -3
  15. package/lib/ai-dp.js +1 -5
  16. package/lib/ai-frontier-protocol.js +1 -1
  17. package/lib/ai-input.js +2 -2
  18. package/lib/ai-model-manifest.js +1 -1
  19. package/lib/ai-output.js +16 -7
  20. package/lib/ai-pref.js +3 -8
  21. package/lib/ai-quota.js +3 -14
  22. package/lib/api-key.js +37 -28
  23. package/lib/api-snapshot.js +4 -1
  24. package/lib/app-shutdown.js +7 -1
  25. package/lib/archive-adapters.js +2 -4
  26. package/lib/archive-entry-policy.js +32 -0
  27. package/lib/archive-gz.js +9 -0
  28. package/lib/archive-read.js +14 -24
  29. package/lib/archive-tar-read.js +56 -24
  30. package/lib/archive.js +6 -12
  31. package/lib/arg-parser.js +7 -6
  32. package/lib/asn1-der.js +70 -22
  33. package/lib/asyncapi-traits.js +2 -6
  34. package/lib/atomic-file.js +312 -33
  35. package/lib/audit-chain.js +183 -54
  36. package/lib/audit-daily-review.js +36 -16
  37. package/lib/audit-emit.js +82 -0
  38. package/lib/audit-sign.js +258 -0
  39. package/lib/audit-tools.js +108 -23
  40. package/lib/audit.js +91 -2
  41. package/lib/auth/access-lock.js +7 -29
  42. package/lib/auth/bot-challenge.js +1 -1
  43. package/lib/auth/ciba.js +43 -4
  44. package/lib/auth/dpop.js +29 -36
  45. package/lib/auth/fido-mds3.js +14 -16
  46. package/lib/auth/jwt-external.js +26 -8
  47. package/lib/auth/jwt.js +15 -17
  48. package/lib/auth/lockout.js +24 -27
  49. package/lib/auth/oauth.js +67 -45
  50. package/lib/auth/oid4vci.js +55 -32
  51. package/lib/auth/oid4vp.js +3 -2
  52. package/lib/auth/openid-federation.js +6 -6
  53. package/lib/auth/passkey.js +4 -4
  54. package/lib/auth/password.js +8 -2
  55. package/lib/auth/saml.js +70 -40
  56. package/lib/auth/sd-jwt-vc-holder.js +2 -14
  57. package/lib/auth/sd-jwt-vc-issuer.js +2 -14
  58. package/lib/auth/sd-jwt-vc.js +25 -5
  59. package/lib/auth/status-list.js +21 -9
  60. package/lib/auth/step-up.js +15 -13
  61. package/lib/auth-bot-challenge.js +27 -39
  62. package/lib/backup/bundle.js +18 -20
  63. package/lib/backup/crypto.js +23 -8
  64. package/lib/backup/index.js +55 -67
  65. package/lib/backup/manifest.js +7 -1
  66. package/lib/bounded-map.js +112 -1
  67. package/lib/breach-deadline.js +1 -11
  68. package/lib/break-glass.js +41 -22
  69. package/lib/cache.js +7 -18
  70. package/lib/cbor.js +36 -12
  71. package/lib/cdn-cache-control.js +15 -12
  72. package/lib/cert.js +8 -13
  73. package/lib/chain-writer.js +162 -47
  74. package/lib/cli.js +10 -2
  75. package/lib/client-hints.js +7 -9
  76. package/lib/cloud-events.js +43 -33
  77. package/lib/cluster-storage.js +9 -41
  78. package/lib/cms-codec.js +2 -1
  79. package/lib/codepoint-class.js +84 -1
  80. package/lib/compliance-ai-act-logging.js +1 -6
  81. package/lib/compliance-ai-act-transparency.js +2 -4
  82. package/lib/compliance-eaa.js +2 -12
  83. package/lib/compliance-sanctions-fetcher.js +21 -28
  84. package/lib/compliance-sanctions.js +11 -21
  85. package/lib/compliance.js +3 -10
  86. package/lib/config-drift.js +24 -20
  87. package/lib/content-credentials.js +11 -8
  88. package/lib/content-digest.js +21 -12
  89. package/lib/cookies.js +15 -13
  90. package/lib/cose.js +39 -11
  91. package/lib/cra-report.js +1 -11
  92. package/lib/crdt.js +2 -1
  93. package/lib/crypto-field.js +34 -33
  94. package/lib/crypto.js +138 -7
  95. package/lib/csp.js +239 -3
  96. package/lib/daemon.js +46 -42
  97. package/lib/data-act.js +46 -13
  98. package/lib/db-file-lifecycle.js +14 -6
  99. package/lib/db-query.js +75 -49
  100. package/lib/db.js +66 -27
  101. package/lib/dbsc.js +5 -6
  102. package/lib/ddl-change-control.js +18 -14
  103. package/lib/deprecate.js +4 -5
  104. package/lib/did.js +6 -2
  105. package/lib/dora.js +43 -13
  106. package/lib/dr-runbook.js +1 -1
  107. package/lib/dsa.js +2 -1
  108. package/lib/dsr.js +30 -20
  109. package/lib/early-hints.js +19 -0
  110. package/lib/eat.js +5 -1
  111. package/lib/external-db-migrate.js +21 -22
  112. package/lib/external-db.js +74 -30
  113. package/lib/fda-21cfr11.js +42 -13
  114. package/lib/fdx.js +22 -18
  115. package/lib/file-upload.js +80 -66
  116. package/lib/flag-evaluation-context.js +5 -8
  117. package/lib/flag-providers.js +6 -2
  118. package/lib/forms.js +1 -1
  119. package/lib/framework-error.js +12 -0
  120. package/lib/framework-schema.js +19 -0
  121. package/lib/fsm.js +7 -12
  122. package/lib/gate-contract.js +911 -39
  123. package/lib/gdpr-ropa.js +22 -22
  124. package/lib/graphql-federation.js +18 -5
  125. package/lib/guard-agent-registry.js +2 -1
  126. package/lib/guard-all.js +3 -2
  127. package/lib/guard-archive.js +9 -30
  128. package/lib/guard-auth.js +23 -80
  129. package/lib/guard-cidr.js +20 -96
  130. package/lib/guard-csv.js +135 -196
  131. package/lib/guard-domain.js +23 -106
  132. package/lib/guard-dsn.js +17 -14
  133. package/lib/guard-email.js +46 -53
  134. package/lib/guard-envelope.js +2 -2
  135. package/lib/guard-event-bus-topic.js +2 -1
  136. package/lib/guard-filename.js +12 -60
  137. package/lib/guard-graphql.js +28 -75
  138. package/lib/guard-html-wcag.js +15 -2
  139. package/lib/guard-html.js +74 -128
  140. package/lib/guard-idempotency-key.js +2 -1
  141. package/lib/guard-image.js +280 -77
  142. package/lib/guard-imap-command.js +9 -10
  143. package/lib/guard-jmap.js +1 -1
  144. package/lib/guard-json.js +101 -109
  145. package/lib/guard-jsonpath.js +20 -88
  146. package/lib/guard-jwt.js +32 -114
  147. package/lib/guard-list-id.js +2 -7
  148. package/lib/guard-list-unsubscribe.js +2 -7
  149. package/lib/guard-mail-compose.js +5 -6
  150. package/lib/guard-mail-move.js +3 -2
  151. package/lib/guard-mail-query.js +5 -3
  152. package/lib/guard-mail-sieve.js +6 -7
  153. package/lib/guard-managesieve-command.js +6 -5
  154. package/lib/guard-markdown.js +76 -110
  155. package/lib/guard-message-id.js +5 -6
  156. package/lib/guard-mime.js +20 -99
  157. package/lib/guard-oauth.js +19 -73
  158. package/lib/guard-pdf.js +65 -72
  159. package/lib/guard-pop3-command.js +13 -14
  160. package/lib/guard-posture-chain.js +2 -1
  161. package/lib/guard-regex.js +24 -99
  162. package/lib/guard-saga-config.js +2 -1
  163. package/lib/guard-shell.js +22 -87
  164. package/lib/guard-smtp-command.js +9 -12
  165. package/lib/guard-sql.js +15 -13
  166. package/lib/guard-stream-args.js +2 -1
  167. package/lib/guard-svg.js +103 -149
  168. package/lib/guard-template.js +23 -80
  169. package/lib/guard-tenant-id.js +2 -1
  170. package/lib/guard-text.js +592 -0
  171. package/lib/guard-time.js +27 -95
  172. package/lib/guard-uuid.js +21 -93
  173. package/lib/guard-xml.js +76 -106
  174. package/lib/guard-yaml.js +24 -60
  175. package/lib/html-balance.js +7 -3
  176. package/lib/http-client-cache.js +5 -12
  177. package/lib/http-client-cookie-jar.js +35 -16
  178. package/lib/http-client.js +233 -74
  179. package/lib/http-message-signature.js +3 -2
  180. package/lib/i18n-messageformat.js +5 -7
  181. package/lib/i18n.js +83 -26
  182. package/lib/iab-tcf.js +3 -2
  183. package/lib/importmap-integrity.js +41 -1
  184. package/lib/inbox.js +8 -8
  185. package/lib/incident-report.js +12 -27
  186. package/lib/ip-utils.js +49 -6
  187. package/lib/jobs.js +3 -2
  188. package/lib/json-patch.js +1 -1
  189. package/lib/json-path.js +24 -3
  190. package/lib/jtd.js +2 -2
  191. package/lib/keychain.js +6 -18
  192. package/lib/legal-hold.js +30 -23
  193. package/lib/log-stream-cloudwatch.js +44 -112
  194. package/lib/log-stream-otlp-grpc.js +17 -14
  195. package/lib/log-stream-otlp.js +16 -80
  196. package/lib/log-stream-webhook.js +20 -92
  197. package/lib/log.js +2 -2
  198. package/lib/mail-agent.js +2 -2
  199. package/lib/mail-arc-sign.js +8 -3
  200. package/lib/mail-arf.js +4 -3
  201. package/lib/mail-auth.js +43 -69
  202. package/lib/mail-bimi.js +35 -55
  203. package/lib/mail-bounce.js +3 -3
  204. package/lib/mail-crypto-pgp.js +3 -2
  205. package/lib/mail-crypto-smime.js +71 -6
  206. package/lib/mail-dav.js +8 -35
  207. package/lib/mail-deploy.js +15 -14
  208. package/lib/mail-dkim.js +19 -38
  209. package/lib/mail-greylist.js +17 -30
  210. package/lib/mail-helo.js +4 -7
  211. package/lib/mail-journal.js +13 -9
  212. package/lib/mail-mdn.js +12 -7
  213. package/lib/mail-rbl.js +7 -12
  214. package/lib/mail-scan.js +5 -7
  215. package/lib/mail-send-deliver.js +33 -20
  216. package/lib/mail-server-imap.js +32 -87
  217. package/lib/mail-server-jmap.js +39 -52
  218. package/lib/mail-server-managesieve.js +29 -83
  219. package/lib/mail-server-mx.js +33 -74
  220. package/lib/mail-server-net.js +177 -0
  221. package/lib/mail-server-pop3.js +30 -83
  222. package/lib/mail-server-rate-limit.js +30 -6
  223. package/lib/mail-server-registry.js +1 -1
  224. package/lib/mail-server-submission.js +34 -73
  225. package/lib/mail-server-tls.js +98 -2
  226. package/lib/mail-spam-score.js +9 -12
  227. package/lib/mail-store-fts.js +1 -1
  228. package/lib/mail-store.js +3 -2
  229. package/lib/mail.js +28 -13
  230. package/lib/markup-escape.js +31 -0
  231. package/lib/markup-tokenizer.js +78 -0
  232. package/lib/mcp-tool-registry.js +26 -23
  233. package/lib/mcp.js +7 -5
  234. package/lib/mdoc.js +36 -14
  235. package/lib/metrics.js +46 -33
  236. package/lib/middleware/age-gate.js +3 -23
  237. package/lib/middleware/api-encrypt.js +13 -24
  238. package/lib/middleware/assetlinks.js +2 -7
  239. package/lib/middleware/bearer-auth.js +2 -1
  240. package/lib/middleware/body-parser.js +41 -52
  241. package/lib/middleware/bot-disclose.js +7 -10
  242. package/lib/middleware/bot-guard.js +28 -28
  243. package/lib/middleware/clear-site-data.js +5 -1
  244. package/lib/middleware/compression.js +9 -0
  245. package/lib/middleware/cors.js +32 -23
  246. package/lib/middleware/csrf-protect.js +73 -23
  247. package/lib/middleware/daily-byte-quota.js +12 -30
  248. package/lib/middleware/deny-response.js +19 -1
  249. package/lib/middleware/fetch-metadata.js +35 -4
  250. package/lib/middleware/idempotency-key.js +4 -20
  251. package/lib/middleware/network-allowlist.js +61 -30
  252. package/lib/middleware/rate-limit.js +45 -44
  253. package/lib/middleware/scim-server.js +5 -3
  254. package/lib/middleware/security-headers.js +33 -7
  255. package/lib/middleware/security-txt.js +2 -6
  256. package/lib/middleware/speculation-rules.js +6 -3
  257. package/lib/middleware/tus-upload.js +14 -29
  258. package/lib/middleware/web-app-manifest.js +2 -7
  259. package/lib/migrations.js +4 -13
  260. package/lib/mime-parse.js +34 -17
  261. package/lib/module-loader.js +44 -0
  262. package/lib/money.js +105 -0
  263. package/lib/mtls-ca.js +125 -41
  264. package/lib/network-byte-quota.js +4 -18
  265. package/lib/network-dane.js +8 -6
  266. package/lib/network-dns-resolver.js +98 -7
  267. package/lib/network-dns.js +9 -2
  268. package/lib/network-dnssec.js +18 -17
  269. package/lib/network-heartbeat.js +3 -2
  270. package/lib/network-smtp-policy.js +52 -46
  271. package/lib/network-tls.js +67 -47
  272. package/lib/network-tsig.js +2 -2
  273. package/lib/nis2-report.js +2 -12
  274. package/lib/nist-crosswalk.js +1 -1
  275. package/lib/nonce-store.js +81 -3
  276. package/lib/ntp-check.js +28 -0
  277. package/lib/numeric-bounds.js +31 -8
  278. package/lib/object-store/azure-blob-bucket-ops.js +2 -6
  279. package/lib/object-store/azure-blob.js +6 -47
  280. package/lib/object-store/gcs-bucket-ops.js +4 -2
  281. package/lib/object-store/gcs.js +14 -75
  282. package/lib/object-store/http-put.js +2 -7
  283. package/lib/object-store/http-request.js +157 -0
  284. package/lib/object-store/local.js +37 -17
  285. package/lib/object-store/sigv4-bucket-ops.js +2 -1
  286. package/lib/object-store/sigv4.js +10 -79
  287. package/lib/observability-otlp-exporter.js +29 -29
  288. package/lib/observability.js +95 -0
  289. package/lib/openapi-paths-builder.js +7 -3
  290. package/lib/otel-export.js +7 -10
  291. package/lib/outbox.js +54 -41
  292. package/lib/pagination.js +11 -15
  293. package/lib/parsers/safe-env.js +5 -4
  294. package/lib/parsers/safe-ini.js +10 -4
  295. package/lib/parsers/safe-toml.js +11 -9
  296. package/lib/parsers/safe-xml.js +3 -3
  297. package/lib/parsers/safe-yaml.js +28 -9
  298. package/lib/pick.js +63 -5
  299. package/lib/pipl-cn.js +69 -59
  300. package/lib/privacy-pass.js +8 -6
  301. package/lib/problem-details.js +2 -5
  302. package/lib/pubsub.js +4 -8
  303. package/lib/queue-local.js +10 -3
  304. package/lib/redact.js +9 -4
  305. package/lib/render.js +4 -2
  306. package/lib/request-helpers.js +334 -29
  307. package/lib/resource-access-lock.js +3 -3
  308. package/lib/restore-bundle.js +46 -18
  309. package/lib/restore-rollback.js +10 -4
  310. package/lib/restore.js +24 -22
  311. package/lib/retention.js +23 -9
  312. package/lib/router.js +17 -4
  313. package/lib/safe-async.js +457 -0
  314. package/lib/safe-buffer.js +137 -6
  315. package/lib/safe-decompress.js +2 -1
  316. package/lib/safe-dns.js +2 -1
  317. package/lib/safe-ical.js +14 -28
  318. package/lib/safe-icap.js +1 -1
  319. package/lib/safe-json.js +51 -8
  320. package/lib/safe-jsonpath.js +6 -5
  321. package/lib/safe-mime.js +25 -29
  322. package/lib/safe-redirect.js +2 -5
  323. package/lib/safe-schema.js +7 -6
  324. package/lib/safe-sieve.js +1 -1
  325. package/lib/safe-sql.js +126 -0
  326. package/lib/safe-vcard.js +13 -27
  327. package/lib/sandbox-worker.js +15 -2
  328. package/lib/sandbox.js +4 -3
  329. package/lib/scheduler.js +22 -13
  330. package/lib/sec-cyber.js +82 -37
  331. package/lib/seeders.js +4 -11
  332. package/lib/self-update-standalone-verifier.js +16 -0
  333. package/lib/self-update.js +92 -69
  334. package/lib/session-device-binding.js +112 -66
  335. package/lib/session.js +219 -7
  336. package/lib/sql.js +228 -81
  337. package/lib/sse.js +6 -10
  338. package/lib/static.js +201 -111
  339. package/lib/storage.js +4 -2
  340. package/lib/structured-fields.js +275 -16
  341. package/lib/template.js +7 -5
  342. package/lib/tenant-quota.js +53 -38
  343. package/lib/tsa.js +16 -17
  344. package/lib/validate-opts.js +154 -8
  345. package/lib/vault/index.js +5 -0
  346. package/lib/vault/passphrase-ops.js +22 -26
  347. package/lib/vault/passphrase-source.js +8 -3
  348. package/lib/vault/rotate.js +13 -18
  349. package/lib/vault/seal-pem-file.js +34 -49
  350. package/lib/vault-aad.js +3 -2
  351. package/lib/vc.js +3 -8
  352. package/lib/vendor/MANIFEST.json +10 -10
  353. package/lib/vendor/public-suffix-list.dat +23 -10
  354. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  355. package/lib/vex.js +35 -13
  356. package/lib/web-push-vapid.js +23 -20
  357. package/lib/webhook-dispatcher.js +706 -0
  358. package/lib/webhook.js +59 -19
  359. package/lib/websocket-channels.js +9 -7
  360. package/lib/websocket.js +8 -4
  361. package/lib/worm.js +3 -4
  362. package/lib/ws-client.js +65 -56
  363. package/lib/x509-chain.js +44 -0
  364. package/package.json +1 -1
  365. package/sbom.cdx.json +6 -6
package/lib/ai-output.js CHANGED
@@ -59,14 +59,18 @@ var NEUTRALIZED_URL = "about:blank#blocked";
59
59
  // matched (we only need the URL to gate it). Reference-style definitions
60
60
  // ([id]: url) are caught by the third pattern so EchoLeak reference-link
61
61
  // payloads don't slip past.
62
- var MD_IMAGE_RE = /!\[[^\]]{0,2048}\]\(\s{0,256}([^)\s]+)/g;
63
- var MD_LINK_RE = /(?<!!)\[[^\]]{0,2048}\]\(\s{0,256}([^)\s]+)/g;
64
- var MD_REF_RE = /^[ \t]{0,3}\[[^\]]+\]:\s*(\S+)/gm;
62
+ // The `d` (hasIndices) flag is required so _rewriteUrls can splice at the
63
+ // captured URL's EXACT offset — when a markdown alt text equals its target URL
64
+ // (`![u](u)`), locating the URL by m[0].indexOf would hit the alt-text copy and
65
+ // leave the real exfiltration target intact (EchoLeak bypass).
66
+ var MD_IMAGE_RE = /!\[[^\]]{0,2048}\]\(\s{0,256}([^)\s]+)/dg;
67
+ var MD_LINK_RE = /(?<!!)\[[^\]]{0,2048}\]\(\s{0,256}([^)\s]+)/dg;
68
+ var MD_REF_RE = /^[ \t]{0,3}\[[^\]]+\]:\s*(\S+)/dgm;
65
69
  // HTML src= / href= attribute URL extractor — the guardHtml pass already
66
70
  // strips dangerous markup, but a surviving same-origin-looking src that
67
71
  // points at an internal / metadata host must still be neutralized for
68
72
  // the auto-fetch exfiltration class.
69
- var HTML_URL_ATTR_RE = /\b(?:src|href)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^"'>\s]+))/gi;
73
+ var HTML_URL_ATTR_RE = /\b(?:src|href)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^"'>\s]+))/dgi;
70
74
 
71
75
  // SQL-shaped fragment signal. Composes safe-sql's reserved-word stance:
72
76
  // a leading SQL keyword followed by a clause keyword is the executable
@@ -143,11 +147,16 @@ function _rewriteUrls(text, re, onUrl) {
143
147
  var last = 0;
144
148
  var m;
145
149
  while ((m = re.exec(text)) !== null) {
146
- var url = m[1] || m[2] || m[3];
147
- if (!url) continue;
150
+ var g = m[1] !== undefined ? 1 : (m[2] !== undefined ? 2 : (m[3] !== undefined ? 3 : 0));
151
+ var url = g ? m[g] : null;
152
+ if (!url) { if (re.lastIndex === m.index) re.lastIndex += 1; continue; }
148
153
  var replacement = onUrl(url);
149
154
  if (replacement !== null && replacement !== url) {
150
- var idx = m.index + m[0].indexOf(url);
155
+ // Splice at the captured group's EXACT offset (hasIndices), never
156
+ // m[0].indexOf(url) — when a markdown alt text equals its target URL the
157
+ // indexOf would hit the alt copy and leave the real URL intact (EchoLeak).
158
+ var span = m.indices && m.indices[g];
159
+ var idx = span ? span[0] : (m.index + m[0].lastIndexOf(url));
151
160
  out += text.slice(last, idx) + replacement;
152
161
  last = idx + url.length;
153
162
  }
package/lib/ai-pref.js CHANGED
@@ -152,13 +152,8 @@ function parseHeader(value) {
152
152
  label: "aiPref.parseHeader",
153
153
  });
154
154
  var out = { train: null, infer: null, snippet: null, price: null };
155
- var pairs = value.split(",");
156
- for (var i = 0; i < pairs.length; i += 1) {
157
- var p = pairs[i].trim();
158
- var eq = p.indexOf("=");
159
- if (eq === -1) continue;
160
- var k = p.slice(0, eq).trim().toLowerCase();
161
- var val = p.slice(eq + 1).trim();
155
+ var kvps = structuredFields.parseKeyValuePieces(value.split(","));
156
+ structuredFields.forEachKeyValue(kvps, function (k, val) {
162
157
  if (k === "train" && TRAIN_VALUES.indexOf(val) !== -1) out.train = val;
163
158
  else if (k === "infer" && INFER_VALUES.indexOf(val) !== -1) out.infer = val;
164
159
  else if (k === "snippet" && SNIPPET_VALUES.indexOf(val) !== -1) out.snippet = val;
@@ -169,7 +164,7 @@ function parseHeader(value) {
169
164
  var pt = parseInt(val, 10);
170
165
  if (isFinite(pt) && pt > 0) out.price = Object.assign({ perTokens: pt }, out.price || {});
171
166
  }
172
- }
167
+ });
173
168
  return out;
174
169
  }
175
170
 
package/lib/ai-quota.js CHANGED
@@ -318,12 +318,7 @@ function create(opts) {
318
318
  return null;
319
319
  }
320
320
 
321
- function _emitAudit(action, outcome, metadata) {
322
- if (!auditOn) return;
323
- try {
324
- audit().safeEmit({ action: action, outcome: outcome, metadata: metadata || {} });
325
- } catch (_e) { /* audit best-effort — drop-silent */ }
326
- }
321
+ var _emitAudit = audit().namespaced(null, { audit: auditOn });
327
322
 
328
323
  function _emitMetric(name, n) {
329
324
  try { observability().safeEvent(name, n || 1, {}); }
@@ -507,14 +502,8 @@ function _validateLimitMap(map, label) {
507
502
  }
508
503
 
509
504
  function _validateStore(store) {
510
- if (!store || typeof store !== "object" ||
511
- typeof store.reserve !== "function" ||
512
- typeof store.add !== "function" ||
513
- typeof store.get !== "function" ||
514
- typeof store.reset !== "function") {
515
- throw new AiQuotaError("ai-quota/bad-store",
516
- "ai.quota.create: store must expose reserve / add / get / reset functions");
517
- }
505
+ validateOpts.requireMethods(store, ["reserve", "add", "get", "reset"],
506
+ "ai.quota.create: store", AiQuotaError, "ai-quota/bad-store");
518
507
  }
519
508
 
520
509
  module.exports = {
package/lib/api-key.js CHANGED
@@ -134,37 +134,46 @@ function _validateIdentifier(name, value) {
134
134
  }
135
135
 
136
136
  function _validateCreateOpts(opts) {
137
- validateOpts.requireObject(opts, "apiKey.create", ApiKeyError);
138
- _validateIdentifier("apiKey.create: namespace", opts.namespace);
139
- if (opts.prefix !== undefined) _validateIdentifier("apiKey.create: prefix", opts.prefix);
140
- validateOpts.optionalPositiveInt(opts.idBytes, "apiKey.create: idBytes", ApiKeyError);
141
- validateOpts.optionalPositiveInt(opts.secretBytes, "apiKey.create: secretBytes", ApiKeyError);
142
- validateOpts.optionalBoolean(opts.trackLastUsedAt, "apiKey.create: trackLastUsedAt", ApiKeyError);
143
- validateOpts.optionalBoolean(opts.auditFailures, "apiKey.create: auditFailures", ApiKeyError);
144
- validateOpts.optionalBoolean(opts.auditSuccess, "apiKey.create: auditSuccess", ApiKeyError);
145
- validateOpts.optionalFiniteNonNegative(opts.purgeAfterMs, "apiKey.create: purgeAfterMs", ApiKeyError);
146
- if (opts.hashAlgo !== undefined) {
147
- if (typeof opts.hashAlgo !== "string" ||
148
- (opts.hashAlgo !== "shake256" && opts.hashAlgo !== "argon2id")) {
149
- throw _err("BAD_OPT", "apiKey.create: hashAlgo must be 'shake256' or 'argon2id', got " +
150
- JSON.stringify(opts.hashAlgo));
151
- }
152
- }
153
- validateOpts.auditShape(opts.audit, "apiKey.create", ApiKeyError);
154
- validateOpts.optionalFunction(opts.clock, "apiKey.create: clock", ApiKeyError);
137
+ validateOpts.shape(opts, {
138
+ namespace: function (v, l) { _validateIdentifier(l, v); },
139
+ prefix: function (v, l) { if (v !== undefined) _validateIdentifier(l, v); },
140
+ idBytes: "optional-positive-int",
141
+ secretBytes: "optional-positive-int",
142
+ trackLastUsedAt: "optional-boolean",
143
+ auditFailures: "optional-boolean",
144
+ auditSuccess: "optional-boolean",
145
+ purgeAfterMs: "optional-non-negative",
146
+ hashAlgo: function (v) {
147
+ if (v !== undefined) {
148
+ if (typeof v !== "string" || (v !== "shake256" && v !== "argon2id")) {
149
+ throw _err("BAD_OPT", "apiKey.create: hashAlgo must be 'shake256' or 'argon2id', got " +
150
+ JSON.stringify(v));
151
+ }
152
+ }
153
+ },
154
+ audit: function (v) { validateOpts.auditShape(v, "apiKey.create", ApiKeyError); },
155
+ clock: "optional-function",
156
+ }, "apiKey.create", ApiKeyError);
155
157
  }
156
158
 
157
159
  function _validateIssueOpts(opts) {
158
- validateOpts.requireObject(opts, "apiKey.issue", ApiKeyError);
159
- validateOpts.requireNonEmptyString(opts.ownerId, "apiKey.issue: ownerId", ApiKeyError, "MISSING_OWNER");
160
- validateOpts.optionalNonEmptyStringArray(opts.scopes, "apiKey.issue: scopes", ApiKeyError, "BAD_SCOPES");
161
- validateOpts.optionalPlainObject(opts.metadata, "apiKey.issue: metadata",
162
- ApiKeyError, "BAD_METADATA");
163
- if (opts.expiresAt !== undefined && opts.expiresAt !== null) {
164
- if (typeof opts.expiresAt !== "number" || !isFinite(opts.expiresAt) || opts.expiresAt < 0) {
165
- throw _err("BAD_OPT", "apiKey.issue: expiresAt must be a non-negative finite number (unix ms) or null");
166
- }
167
- }
160
+ validateOpts.shape(opts, {
161
+ ownerId: { rule: "required-string", code: "MISSING_OWNER" },
162
+ scopes: { rule: "optional-string-array", code: "BAD_SCOPES" },
163
+ metadata: { rule: "optional-plain-object", code: "BAD_METADATA" },
164
+ expiresAt: function (v) {
165
+ if (v !== undefined && v !== null) {
166
+ if (typeof v !== "number" || !isFinite(v) || v < 0) {
167
+ throw _err("BAD_OPT", "apiKey.issue: expiresAt must be a non-negative finite number (unix ms) or null");
168
+ }
169
+ }
170
+ },
171
+ }, "apiKey.issue", ApiKeyError, undefined, {
172
+ // `req` / `context` are the audit-actor pass-through bag — forwarded
173
+ // verbatim to requestHelpers.resolveActorWithOverride (via _actor)
174
+ // to populate the issue audit's 5 W's, not validated locally.
175
+ allow: ["req", "context"],
176
+ });
168
177
  }
169
178
 
170
179
  // ---- Token format ----
@@ -51,6 +51,7 @@
51
51
  */
52
52
 
53
53
  var nodeFs = require("node:fs");
54
+ var atomicFile = require("./atomic-file");
54
55
  var numericBounds = require("./numeric-bounds");
55
56
  var safeJson = require("./safe-json");
56
57
  var { FrameworkError } = require("./framework-error");
@@ -199,7 +200,9 @@ function write(snapshot, filePath) {
199
200
  createdAt: snapshot.createdAt,
200
201
  exports: snapshot.exports,
201
202
  };
202
- nodeFs.writeFileSync(filePath, JSON.stringify(canonical, null, 2) + "\n", { mode: 0o644 });
203
+ // Atomic, symlink-refusing write (0o644 the snapshot is a public,
204
+ // committed artifact). writeSync stages into a no-follow exclusive temp.
205
+ atomicFile.writeSync(filePath, JSON.stringify(canonical, null, 2) + "\n", { fileMode: 0o644 });
203
206
  return filePath;
204
207
  }
205
208
 
@@ -511,6 +511,7 @@ function standardPhases(components) {
511
511
  // IS the lock.
512
512
  var nodeFs = require("node:fs");
513
513
  var nodePath = require("node:path");
514
+ var atomicFile = require("./atomic-file");
514
515
 
515
516
  /**
516
517
  * @primitive b.appShutdown.pidLock
@@ -552,7 +553,12 @@ function pidLock(lockPath) {
552
553
 
553
554
  function _readExisting() {
554
555
  try {
555
- var raw = nodeFs.readFileSync(lockPath, "utf8");
556
+ // fd-safe + capped + symlink-refusing read: a PID lockfile is never a
557
+ // legitimate symlink (unlike a k8s/certbot secret mount), so refuseSymlink
558
+ // is safe here and stops a planted symlink/oversized file from redirecting
559
+ // or OOM-ing the read. Any throw (symlink/too-large/enoent) → null, the
560
+ // existing "no live lock" semantic.
561
+ var raw = atomicFile.fdSafeReadSync(lockPath, { maxBytes: C.BYTES.kib(1), refuseSymlink: true, encoding: "utf8" });
556
562
  var pid = parseInt(String(raw).trim(), 10);
557
563
  return isFinite(pid) && pid > 0 ? pid : null;
558
564
  } catch (_e) { return null; }
@@ -547,10 +547,8 @@ function http(url, opts) {
547
547
  * for await (var e of reader.entries()) entries.push(e);
548
548
  */
549
549
  function trustedStream(readable, opts) {
550
- if (!readable || typeof readable.pipe !== "function" || typeof readable.on !== "function") {
551
- throw new AdapterError("adapter/bad-arg",
552
- "trustedStream: arg must be a Readable (or pipe/on-compatible stream)");
553
- }
550
+ validateOpts.requireMethods(readable, ["pipe", "on"],
551
+ "trustedStream: arg (Readable / pipe-on-compatible stream)", AdapterError, "adapter/bad-arg");
554
552
  if (!(readable instanceof nodeStream.Readable) && !readable.readable) {
555
553
  // Accept stream-like duck-typed objects; many libraries return
556
554
  // Readable-flavored bytes via Symbol.asyncIterator only.
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ /**
3
+ * lib/archive-entry-policy.js — the entry-type extraction policy shared by the
4
+ * ZIP (archive-read) and TAR (archive-tar-read) readers.
5
+ *
6
+ * Special archive entry types — symlinks, hardlinks, device nodes, FIFOs, and
7
+ * sockets — are a malicious-archive vector (symlink traversal out of the
8
+ * extraction root, device-node creation, etc.), so every type is DENIED by
9
+ * default; an operator opts a type in explicitly per archive. Both readers
10
+ * normalized the same default the same way, so the policy + its overlay live
11
+ * here once rather than drifting between the two formats.
12
+ */
13
+
14
+ var DEFAULT_ENTRY_TYPE_POLICY = Object.freeze({
15
+ symlinks: false,
16
+ hardlinks: false,
17
+ devices: false,
18
+ fifos: false,
19
+ sockets: false,
20
+ });
21
+
22
+ // normalize(p) — overlay an operator policy onto the all-denied default and
23
+ // freeze it. A falsy `p` returns the shared default object (no allocation).
24
+ function normalize(p) {
25
+ if (!p) return DEFAULT_ENTRY_TYPE_POLICY;
26
+ return Object.freeze(Object.assign({}, DEFAULT_ENTRY_TYPE_POLICY, p));
27
+ }
28
+
29
+ module.exports = {
30
+ DEFAULT_ENTRY_TYPE_POLICY: DEFAULT_ENTRY_TYPE_POLICY,
31
+ normalize: normalize,
32
+ };
package/lib/archive-gz.js CHANGED
@@ -149,6 +149,15 @@ function readGz(adapter, opts) {
149
149
  throw new ArchiveGzError("archive-gz/empty-input",
150
150
  "read.gz: adapter reports empty payload");
151
151
  }
152
+ // Cap the raw COMPRESSED read before allocating — `adapter.range(0, size)`
153
+ // does Buffer.allocUnsafe(size) of an fstat/HEAD-reported length, which a
154
+ // hostile .gz (or an objectStore/HTTP source advertising a huge size) can
155
+ // drive to OOM. maxOutputBytes bounds the DECOMPRESSED output only; this
156
+ // bounds the compressed read too, matching archive-tar-read's _collectAdapterBytes.
157
+ if (typeof maxOutputBytes === "number" && size > maxOutputBytes) {
158
+ throw new ArchiveGzError("archive-gz/source-too-large",
159
+ "read.gz: random-access source size=" + size + " exceeds the read cap " + maxOutputBytes);
160
+ }
152
161
  return adapter.range(0, size);
153
162
  }
154
163
  if (adapter.kind === "trusted-sequential") {
@@ -54,6 +54,8 @@ var guardArchive = lazyRequire(function () { return require("./guard-archive");
54
54
  var safeDecompress = lazyRequire(function () { return require("./safe-decompress"); });
55
55
  var safeBuffer = lazyRequire(function () { return require("./safe-buffer"); });
56
56
  var archiveAdapters = lazyRequire(function () { return require("./archive-adapters"); });
57
+ var archiveEntryPolicy = require("./archive-entry-policy");
58
+ var auditEmit = require("./audit-emit");
57
59
 
58
60
  // ---- Wire-format constants ------------------------------------------------
59
61
  // Aligned with the write-side `lib/archive.js`. APPNOTE.TXT § references
@@ -120,13 +122,7 @@ var DEFAULT_BOMB_POLICY = Object.freeze({
120
122
  maxExpansionRatio: 100, // compressed → decompressed ratio cap
121
123
  });
122
124
 
123
- var DEFAULT_ENTRY_TYPE_POLICY = Object.freeze({
124
- symlinks: false,
125
- hardlinks: false,
126
- devices: false,
127
- fifos: false,
128
- sockets: false,
129
- });
125
+ var DEFAULT_ENTRY_TYPE_POLICY = archiveEntryPolicy.DEFAULT_ENTRY_TYPE_POLICY;
130
126
 
131
127
  // ---- Helpers --------------------------------------------------------------
132
128
 
@@ -629,17 +625,9 @@ function _normalizeBombPolicy(p) {
629
625
  return Object.freeze(Object.assign({}, DEFAULT_BOMB_POLICY, p));
630
626
  }
631
627
 
632
- function _normalizeEntryTypePolicy(p) {
633
- if (!p) return DEFAULT_ENTRY_TYPE_POLICY;
634
- return Object.freeze(Object.assign({}, DEFAULT_ENTRY_TYPE_POLICY, p));
635
- }
628
+ var _normalizeEntryTypePolicy = archiveEntryPolicy.normalize;
636
629
 
637
- function _emitAudit(opts, action, outcome, metadata) {
638
- if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
639
- try {
640
- opts.audit.safeEmit({ action: action, outcome: outcome, metadata: metadata });
641
- } catch (_e) { /* drop-silent — audit sinks must never crash the reader */ }
642
- }
630
+ var _emitAudit = auditEmit.emitToSink; // operator-sink audit emit (opts.audit)
643
631
 
644
632
  /**
645
633
  * @primitive b.archive.read.zip
@@ -920,13 +908,15 @@ function zip(adapter, opts) {
920
908
  "cumulative uncompressed=" + totalDecompressed +
921
909
  " exceeds maxTotalDecompressedBytes during extract");
922
910
  }
923
- // Write entry to disk. Atomic rename via a tmp file so a
924
- // partial write during inflate doesn't leave a half-file at
925
- // the canonical name. Pre-existence check above guarantees
926
- // the rename targets a non-existent path.
927
- var tmpPath = resolvedPath + ".__blamejs-archive-read-tmp__";
928
- nodeFs.writeFileSync(tmpPath, body);
929
- atomicFile.renameWithRetry(tmpPath, resolvedPath);
911
+ // Write entry to disk atomically. The previous hand-rolled form
912
+ // staged into a PREDICTABLE temp name (resolvedPath +
913
+ // ".__blamejs-archive-read-tmp__") via a plain writeFileSync, so a
914
+ // symlink pre-planted at that exact path would be followed (CWE-59
915
+ // arbitrary write outside the extract dir). writeSync stages into a
916
+ // CSPRNG temp opened O_EXCL | O_NOFOLLOW, then renames — a partial
917
+ // write during inflate also never leaves a half-file at the canonical
918
+ // name. The pre-existence check above keeps the rename non-clobbering.
919
+ atomicFile.writeSync(resolvedPath, body);
930
920
  written.push({ name: entry.name, bytesWritten: body.length, path: resolvedPath });
931
921
  bytesExtracted += body.length;
932
922
  }
@@ -14,7 +14,9 @@ var C = require("./constants");
14
14
  var lazyRequire = require("./lazy-require");
15
15
  var safeBuffer = require("./safe-buffer");
16
16
  var archiveTar = require("./archive-tar");
17
+ var archiveEntryPolicy = require("./archive-entry-policy");
17
18
  var atomicFile = require("./atomic-file");
19
+ var auditEmit = require("./audit-emit");
18
20
 
19
21
  var TarError = archiveTar.TarError;
20
22
  var _parseHeader = archiveTar._parseHeader;
@@ -45,29 +47,16 @@ var DEFAULT_BOMB_POLICY = Object.freeze({
45
47
  maxExpansionRatio: 100, // tar has no compression-ratio concept, but keep field for orchestrator policy parity
46
48
  });
47
49
 
48
- var DEFAULT_ENTRY_TYPE_POLICY = Object.freeze({
49
- symlinks: false,
50
- hardlinks: false,
51
- devices: false,
52
- fifos: false,
53
- sockets: false,
54
- });
50
+ var DEFAULT_ENTRY_TYPE_POLICY = archiveEntryPolicy.DEFAULT_ENTRY_TYPE_POLICY;
55
51
 
56
52
  function _normalizeBombPolicy(p) {
57
53
  if (!p) return DEFAULT_BOMB_POLICY;
58
54
  return Object.freeze(Object.assign({}, DEFAULT_BOMB_POLICY, p));
59
55
  }
60
56
 
61
- function _normalizeEntryTypePolicy(p) {
62
- if (!p) return DEFAULT_ENTRY_TYPE_POLICY;
63
- return Object.freeze(Object.assign({}, DEFAULT_ENTRY_TYPE_POLICY, p));
64
- }
57
+ var _normalizeEntryTypePolicy = archiveEntryPolicy.normalize;
65
58
 
66
- function _emitAudit(opts, action, outcome, metadata) {
67
- if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
68
- try { opts.audit.safeEmit({ action: action, outcome: outcome, metadata: metadata }); }
69
- catch (_e) { /* drop-silent */ }
70
- }
59
+ var _emitAudit = auditEmit.emitToSink; // operator-sink audit emit (opts.audit)
71
60
 
72
61
  function _isZeroBlock(buf) {
73
62
  for (var i = 0; i < BLOCK_SIZE; i += 1) {
@@ -110,13 +99,43 @@ function _parsePaxRecords(buf) {
110
99
  return out;
111
100
  }
112
101
 
113
- async function _collectAdapterBytes(adapter) {
102
+ // A PAX `size` attribute is an attacker-controlled ASCII string. `parseInt` of
103
+ // a malformed value ("", "abc", "1e9") yields NaN, which then SILENTLY bypasses
104
+ // the entry-size bomb check (`NaN > maxEntryDecompressedBytes` is false) AND
105
+ // desyncs the block walker (`Math.ceil(NaN / BLOCK_SIZE)` is NaN, so `pos`
106
+ // advances by NaN). Reject anything that is not a plain non-negative integer.
107
+ function _paxSize(raw) {
108
+ var s = String(raw).trim();
109
+ var n = parseInt(s, 10);
110
+ // Round-trip: a clean non-negative integer survives parseInt → String
111
+ // unchanged. This rejects "", "abc" (NaN), "1e9", "100abc" (parseInt stops
112
+ // early), negatives, leading zeros, and astronomically long strings that
113
+ // overflow to Infinity (String(Infinity) !== s) — every shape that would
114
+ // otherwise yield NaN/garbage and bypass the bomb check + desync the walker.
115
+ if (!Number.isFinite(n) || n < 0 || String(n) !== s) {
116
+ throw new TarError("archive-tar/bad-pax-size",
117
+ "PAX size attribute " + JSON.stringify(raw) + " is not a non-negative integer");
118
+ }
119
+ return n;
120
+ }
121
+
122
+ async function _collectAdapterBytes(adapter, maxBytes) {
114
123
  if (adapter.kind === "random-access") {
115
124
  var size = adapter.size;
116
125
  if (size == null && typeof adapter.resolveSize === "function") {
117
126
  size = await adapter.resolveSize();
118
127
  }
119
- if (typeof size !== "number" || size === 0) return Buffer.alloc(0);
128
+ if (typeof size !== "number" || !Number.isFinite(size) || size <= 0) return Buffer.alloc(0);
129
+ // Cap the random-access read the same way the trusted-sequential branch
130
+ // caps its collector — a multi-GiB adapter.range(0, size) is an OOM lever
131
+ // (the size comes from the adapter, e.g. an on-disk file's stat or an
132
+ // operator-supplied length). The default ceiling is the bomb policy's
133
+ // total-decompressed cap.
134
+ if (typeof maxBytes === "number" && size > maxBytes) {
135
+ throw new TarError("archive-tar/source-too-large",
136
+ "read.tar: random-access source size=" + size +
137
+ " exceeds the read cap " + maxBytes);
138
+ }
120
139
  return adapter.range(0, size);
121
140
  }
122
141
  if (adapter.kind === "trusted-sequential") {
@@ -183,7 +202,7 @@ function tar(adapter, opts) {
183
202
  var entryTypePolicy = _normalizeEntryTypePolicy(opts.entryTypePolicy);
184
203
 
185
204
  async function _walk() {
186
- var bytes = await _collectAdapterBytes(adapter);
205
+ var bytes = await _collectAdapterBytes(adapter, bombPolicy.maxTotalDecompressedBytes);
187
206
  if (bytes.length === 0) return { entries: [], bytes: bytes };
188
207
  var pos = 0;
189
208
  var entries = [];
@@ -201,6 +220,16 @@ function tar(adapter, opts) {
201
220
  zeroBlockCount = 0;
202
221
  var hdr = _parseHeader(block);
203
222
  if (hdr.typeflag === TF_PAX_EXTENDED || hdr.typeflag === TF_PAX_GLOBAL) {
223
+ // The per-entry bomb cap below (line ~252) is AFTER the PAX `continue`,
224
+ // so a PAX header body (its size is the attacker-controlled ustar octal
225
+ // field, up to ~8 GiB) escaped it — a multi-hundred-MiB UTF-8 string +
226
+ // record Object materialization above the cap operators set. Legitimate
227
+ // PAX/global bodies are tiny, so bound them by the same per-entry cap.
228
+ if (hdr.size > bombPolicy.maxEntryDecompressedBytes) {
229
+ throw new TarError("archive-tar/entry-too-large",
230
+ "pax header body size=" + hdr.size + " exceeds maxEntryDecompressedBytes=" +
231
+ bombPolicy.maxEntryDecompressedBytes);
232
+ }
204
233
  var bodyEnd = pos + Math.ceil(hdr.size / BLOCK_SIZE) * BLOCK_SIZE;
205
234
  if (bodyEnd > bytes.length) {
206
235
  throw new TarError("archive-tar/truncated-entry",
@@ -217,12 +246,12 @@ function tar(adapter, opts) {
217
246
  }
218
247
  if (globalPax) {
219
248
  if (globalPax.path) hdr.name = globalPax.path;
220
- if (globalPax.size) hdr.size = parseInt(globalPax.size, 10);
249
+ if (globalPax.size) hdr.size = _paxSize(globalPax.size);
221
250
  if (globalPax.linkpath) hdr.linkname = globalPax.linkpath;
222
251
  }
223
252
  if (pendingPax) {
224
253
  if (pendingPax.path) hdr.name = pendingPax.path;
225
- if (pendingPax.size) hdr.size = parseInt(pendingPax.size, 10);
254
+ if (pendingPax.size) hdr.size = _paxSize(pendingPax.size);
226
255
  if (pendingPax.linkpath) hdr.linkname = pendingPax.linkpath;
227
256
  pendingPax = null;
228
257
  }
@@ -427,9 +456,12 @@ function tar(adapter, opts) {
427
456
  "cumulative uncompressed=" + totalDecompressed +
428
457
  " exceeds maxTotalDecompressedBytes during extract");
429
458
  }
430
- var tmpPath = resolvedPath + ".__blamejs-archive-tar-tmp__";
431
- nodeFs.writeFileSync(tmpPath, body);
432
- atomicFile.renameWithRetry(tmpPath, resolvedPath);
459
+ // Atomic, symlink-refusing write. The previous hand-rolled form staged
460
+ // into a PREDICTABLE temp name (resolvedPath +
461
+ // ".__blamejs-archive-tar-tmp__") via a plain writeFileSync, so a
462
+ // symlink pre-planted at that exact path would be followed (CWE-59).
463
+ // writeSync uses a CSPRNG temp opened O_EXCL | O_NOFOLLOW + rename.
464
+ atomicFile.writeSync(resolvedPath, body);
433
465
  written.push({ name: entry.name, bytesWritten: body.length, path: resolvedPath });
434
466
  bytesExtracted += body.length;
435
467
  }
package/lib/archive.js CHANGED
@@ -52,12 +52,13 @@
52
52
  * ZIP archive creation primitive.
53
53
  */
54
54
  var zlib = require("node:zlib");
55
- var nodeFs = require("node:fs");
56
55
  var nodeCrypto = require("node:crypto");
57
56
  var nodeStream = require("node:stream");
58
57
  var streamPromises = require("node:stream/promises");
59
58
  var C = require("./constants");
60
59
  var { defineClass } = require("./framework-error");
60
+ var auditEmit = require("./audit-emit");
61
+ var atomicFile = require("./atomic-file");
61
62
 
62
63
  var ArchiveError = defineClass("ArchiveError", { alwaysPermanent: true });
63
64
 
@@ -477,20 +478,13 @@ function zip() {
477
478
 
478
479
  function writeTo(filepath) {
479
480
  var buf = toBuffer();
480
- nodeFs.writeFileSync(filepath, buf);
481
+ // Atomic, symlink-refusing write (a bare writeFileSync follows a symlink
482
+ // planted at filepath and can leave a torn archive on a crash).
483
+ atomicFile.writeSync(filepath, buf, { fileMode: 0o600 });
481
484
  return buf.length;
482
485
  }
483
486
 
484
- function _emitAudit(opts, action, outcome, metadata) {
485
- if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
486
- try {
487
- opts.audit.safeEmit({
488
- action: action,
489
- outcome: outcome,
490
- metadata: metadata,
491
- });
492
- } catch (_e) { /* drop-silent — audit sinks must never crash the producer */ }
493
- }
487
+ var _emitAudit = auditEmit.emitToSink; // operator-sink audit emit (opts.audit)
494
488
 
495
489
  function _writeChunk(writable, chunk) {
496
490
  return new Promise(function (resolve, reject) {
package/lib/arg-parser.js CHANGED
@@ -36,6 +36,7 @@
36
36
  */
37
37
 
38
38
  var { defineClass } = require("./framework-error");
39
+ var pick = require("./pick");
39
40
 
40
41
  var ArgParserError = defineClass("ArgParserError", { alwaysPermanent: true });
41
42
 
@@ -44,8 +45,8 @@ var SUPPORTED_TYPES = ["string", "number", "boolean", "list"];
44
45
  // Names operators can never use for a flag. Prototype-pollution defense:
45
46
  // even though the parser internally uses Object.create(null), downstream
46
47
  // callers spreading parsed.flags into a normal object would otherwise
47
- // overwrite the prototype.
48
- var FORBIDDEN_NAMES = ["__proto__", "constructor", "prototype"];
48
+ // overwrite the prototype. The forbidden-name set is the framework's one
49
+ // prototype-pollution key guard pick.isPoisonedKey.
49
50
 
50
51
  function _isPlainNonEmpty(s) {
51
52
  return typeof s === "string" && s.length > 0;
@@ -56,7 +57,7 @@ function _validateFlagName(name) {
56
57
  throw new ArgParserError("arg-parser/flag-name-invalid",
57
58
  "flag name must be a non-empty string");
58
59
  }
59
- if (FORBIDDEN_NAMES.indexOf(name) !== -1) {
60
+ if (pick.isPoisonedKey(name)) {
60
61
  throw new ArgParserError("arg-parser/flag-name-forbidden",
61
62
  "flag name '" + name + "' is reserved");
62
63
  }
@@ -298,7 +299,7 @@ function _consumeFlags(index, tokens) {
298
299
 
299
300
  if (tok.indexOf("--") === 0) {
300
301
  var rest = tok.slice(2);
301
- if (FORBIDDEN_NAMES.indexOf(rest.split("=")[0]) !== -1) {
302
+ if (pick.isPoisonedKey(rest.split("=")[0])) {
302
303
  throw new ArgParserError("arg-parser/argv-forbidden-name",
303
304
  "flag '--" + rest.split("=")[0] + "' is reserved");
304
305
  }
@@ -672,14 +673,14 @@ function parseRaw(argv) {
672
673
  } else {
673
674
  val = true;
674
675
  }
675
- if (FORBIDDEN_NAMES.indexOf(name) !== -1) {
676
+ if (pick.isPoisonedKey(name)) {
676
677
  throw new ArgParserError("arg-parser/argv-forbidden-name",
677
678
  "flag '--" + name + "' is reserved");
678
679
  }
679
680
  flags[name] = val;
680
681
  } else if (tok.indexOf("-") === 0 && tok.length === 2) {
681
682
  var s = tok.slice(1);
682
- if (FORBIDDEN_NAMES.indexOf(s) !== -1) {
683
+ if (pick.isPoisonedKey(s)) {
683
684
  throw new ArgParserError("arg-parser/argv-forbidden-name",
684
685
  "flag '-" + s + "' is reserved");
685
686
  }