@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/guard-json.js CHANGED
@@ -72,10 +72,11 @@
72
72
 
73
73
  var codepointClass = require("./codepoint-class");
74
74
  var lazyRequire = require("./lazy-require");
75
+ var pick = require("./pick");
75
76
  var gateContract = require("./gate-contract");
76
77
  var C = require("./constants");
77
- var numericBounds = require("./numeric-bounds");
78
78
  var safeJson = require("./safe-json");
79
+ var safeBuffer = require("./safe-buffer");
79
80
  var { GuardJsonError } = require("./framework-error");
80
81
 
81
82
  var observability = lazyRequire(function () { return require("./observability"); });
@@ -87,14 +88,9 @@ var _err = GuardJsonError.factory;
87
88
 
88
89
  var BIDI_RE = codepointClass.BIDI_RE;
89
90
  var C0_CTRL_RE = codepointClass.C0_CTRL_RE;
90
- var ZW_RE = codepointClass.ZERO_WIDTH_RE;
91
91
  var NULL_BYTE = codepointClass.NULL_BYTE;
92
92
  var BOM_CHAR = codepointClass.BOM_CHAR;
93
93
 
94
- // Prototype-pollution key denylist. Operator-supplied JSON containing
95
- // any of these keys at any depth is refused under strict.
96
- var POLLUTION_KEYS = Object.freeze(["__proto__", "constructor", "prototype"]);
97
-
98
94
  // Comment / NaN / Infinity / hex / single-quote markers — pre-parse
99
95
  // scan on the raw source. JSON.parse rejects most of these, but JSON5
100
96
  // / JSONC parsers accept and silently coerce; we surface the source
@@ -132,10 +128,7 @@ var PROFILES = Object.freeze({
132
128
  trailingCommaPolicy: "reject",
133
129
  json5SyntaxPolicy: "reject", // single-quoted / hex / unquoted-key
134
130
  bomPolicy: "reject",
135
- bidiPolicy: "reject",
136
- controlPolicy: "reject",
137
- nullBytePolicy: "reject",
138
- zeroWidthPolicy: "reject",
131
+ ...gateContract.CHAR_THREATS_REJECT_ALL,
139
132
  numericPrecisionPolicy: "reject",
140
133
  requireTopLevelKeyAllowlist: false, // operator opts in via topLevelKeyAllowlist
141
134
  topLevelKeyAllowlist: null,
@@ -192,26 +185,12 @@ var PROFILES = Object.freeze({
192
185
  },
193
186
  });
194
187
 
195
- var DEFAULTS = Object.freeze(Object.assign({}, PROFILES["strict"], {
196
- mode: "enforce",
188
+ var DEFAULTS = gateContract.strictDefaults(PROFILES, {
197
189
  maxRuntimeMs: C.TIME.seconds(10),
198
- }));
199
-
200
- var COMPLIANCE_POSTURES = Object.freeze({
201
- "hipaa": Object.assign({}, PROFILES["strict"], {
202
- forensicSnippetBytes: C.BYTES.bytes(256),
203
- }),
204
- "pci-dss": Object.assign({}, PROFILES["strict"], {
205
- forensicSnippetBytes: C.BYTES.bytes(256),
206
- }),
207
- "gdpr": Object.assign({}, PROFILES["balanced"], {
208
- forensicSnippetBytes: C.BYTES.bytes(128),
209
- }),
210
- "soc2": Object.assign({}, PROFILES["strict"], {
211
- forensicSnippetBytes: C.BYTES.bytes(512),
212
- }),
213
190
  });
214
191
 
192
+ var COMPLIANCE_POSTURES = gateContract.compliancePostures(PROFILES, { base: 256 });
193
+
215
194
  // ---- Helpers ----
216
195
 
217
196
  function _resolveOpts(opts) {
@@ -225,7 +204,10 @@ function _resolveOpts(opts) {
225
204
  }
226
205
 
227
206
  function _isPollutionKey(key) {
228
- return POLLUTION_KEYS.indexOf(key) !== -1;
207
+ // The framework's single prototype-pollution predicate (core JS vectors
208
+ // plus any operator-registered defense-in-depth extensions) — strict JSON
209
+ // refuses / strips every key it names, at any depth.
210
+ return pick.isPoisonedKey(key);
229
211
  }
230
212
 
231
213
  // _scanPollutionKeys — walks parsed JSON tree counting prototype-
@@ -248,12 +230,19 @@ function _scanTree(value, opts, ctx) {
248
230
  return ctx;
249
231
  }
250
232
  if (value === null || typeof value !== "object") {
251
- if (typeof value === "string" && value.length > opts.maxStringLength) {
252
- ctx.stringTooLongHits.push({
253
- kind: "string-too-long",
254
- snippet: "string length " + value.length +
255
- " exceeds maxStringLength " + opts.maxStringLength,
256
- });
233
+ // maxStringLength is a BYTE cap (profiles set it via C.BYTES.*). Measure
234
+ // UTF-8 byte length, not value.length (UTF-16 code units) — otherwise a
235
+ // multibyte string (emoji / CJK / accented) under-enforces the cap by up
236
+ // to ~3x and the snippet mislabels the count.
237
+ if (typeof value === "string") {
238
+ var strBytes = safeBuffer.byteLengthOf(value);
239
+ if (strBytes > opts.maxStringLength) {
240
+ ctx.stringTooLongHits.push({
241
+ kind: "string-too-long",
242
+ snippet: "string byte length " + strBytes +
243
+ " exceeds maxStringLength " + opts.maxStringLength + " bytes",
244
+ });
245
+ }
257
246
  }
258
247
  return ctx;
259
248
  }
@@ -382,31 +371,19 @@ function _scanRawSource(text, opts) {
382
371
  });
383
372
  }
384
373
  }
385
- // Bidi / null / control via shared codepoint class.
386
- issues.push.apply(issues, codepointClass.detectCharThreats(text, opts, "json"));
387
- if (opts.zeroWidthPolicy !== "allow" && opts.zeroWidthPolicy !== "strip" &&
388
- ZW_RE.test(text)) { // allow:regex-no-length-cap — text bounded by maxBytes above
389
- issues.push({
390
- kind: "zero-width", severity: "warn", ruleId: "json.zero-width",
391
- snippet: "zero-width / invisible-formatting char in JSON source",
392
- });
393
- }
374
+ // Bidi / null / control / zero-width via the shared codepoint class. JSON
375
+ // source treats an invisible-formatting char as a `warn` (cosmetic, not a
376
+ // structural threat) passed as the zero-width severity.
377
+ issues.push.apply(issues, codepointClass.detectCharThreats(text, opts, "json", "warn"));
394
378
  return issues;
395
379
  }
396
380
 
397
381
  // _detectIssues — full validate path: raw-source pre-scan + parse +
398
382
  // tree walk.
399
383
  function _detectIssues(input, opts) {
400
- var issues = [];
401
- if (typeof input !== "string") {
402
- return [{ kind: "bad-input", severity: "high",
403
- snippet: "input is not a string" }];
404
- }
405
- if (input.length > opts.maxBytes) {
406
- return [{ kind: "too-large", severity: "high", ruleId: "json.too-large",
407
- snippet: "input " + input.length +
408
- " bytes exceeds maxBytes " + opts.maxBytes }];
409
- }
384
+ var pre = gateContract.detectStringInput(input, opts, { name: "json", noun: "input", emptyMode: "skip", scanCodepoints: false, cap: { bytes: opts.maxBytes, kind: "too-large", snippet: function (byteLen, max) { return "input " + byteLen + " bytes exceeds maxBytes " + max; } } });
385
+ if (pre.done) return pre.issues;
386
+ var issues = pre.issues;
410
387
 
411
388
  // Raw-source pre-scan.
412
389
  issues = issues.concat(_scanRawSource(input, opts));
@@ -616,21 +593,13 @@ function _stripPollutionTree(value, opts, depth) {
616
593
  * rv.ok; // → false
617
594
  * rv.issues.some(function (i) { return i.kind === "prototype-pollution-key"; }); // → true
618
595
  */
619
- function validate(input, opts) {
620
- opts = _resolveOpts(opts);
621
- numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
622
- ["maxBytes", "maxDepth", "maxKeysPerObject", "maxArrayLength",
623
- "maxStringLength", "maxTotalNodes"],
624
- "guardJson.validate", GuardJsonError, "json.bad-opt");
625
- if (typeof input !== "string") {
626
- return {
627
- ok: false,
628
- issues: [{ kind: "bad-input", severity: "high",
629
- snippet: "input is not a string" }],
630
- };
631
- }
632
- return gateContract.aggregateIssues(_detectIssues(input, opts));
633
- }
596
+ // validate is assembled by gateContract.defineGuard from `detect`
597
+ // (_detectIssues) below — `validate(input, opts) = aggregateIssues(detect(
598
+ // input, resolveOpts(opts)))`, with the maxBytes/maxDepth/maxKeysPerObject/
599
+ // maxArrayLength/maxStringLength/maxTotalNodes caps declared via `intOpts`.
600
+ // Non-string input reduces to the same single `bad-input` issue _detectIssues
601
+ // already emits, so the prior explicit early-return is subsumed. The
602
+ // @primitive block above documents the resulting ABI.
634
603
 
635
604
  /**
636
605
  * @primitive b.guardJson.parse
@@ -779,45 +748,65 @@ function _policyKeyForRuleId(ruleId) {
779
748
  * var verdict = await jsonGate.check({ bytes: hostile });
780
749
  * verdict.action; // → "refuse"
781
750
  */
751
+ // Disposition of each json finding = what the operator's policy for that class
752
+ // selected. The RFC-deviation findings (comments / trailing commas / NaN /
753
+ // JSON5 syntax / BOM / prototype-pollution / duplicate keys) sanitize by
754
+ // re-parsing under a mitigation policy and refuse under `reject`; the bidi /
755
+ // null / control char threats follow their shared policies; structural caps,
756
+ // a parse failure, and an allowlist miss always refuse; the big-integer
757
+ // precision and zero-width notes are audit-only. Exhaustive over every kind
758
+ // _detectIssues emits (the gate-disposition coverage test enforces it).
759
+ function _gateDispositionFor(issue, opts) {
760
+ var shared = gateContract.charThreatDisposition(issue, opts);
761
+ if (shared) return shared;
762
+ switch (issue.kind) {
763
+ case "bom-leading":
764
+ case "bom-mid-stream": return gateContract.policyDisposition(opts.bomPolicy);
765
+ case "comment-block":
766
+ case "comment-line": return gateContract.policyDisposition(opts.commentPolicy);
767
+ case "nan-infinity": return gateContract.policyDisposition(opts.nanInfinityPolicy);
768
+ case "trailing-comma": return gateContract.policyDisposition(opts.trailingCommaPolicy);
769
+ case "single-quoted-key":
770
+ case "hex-literal": return gateContract.policyDisposition(opts.json5SyntaxPolicy);
771
+ case "prototype-pollution-key": return gateContract.policyDisposition(opts.pollutionPolicy);
772
+ case "duplicate-key": return gateContract.policyDisposition(opts.duplicateKeyPolicy);
773
+ // zero-width is classified by charThreatDisposition above (its
774
+ // zeroWidthPolicy). numeric-precision-loss follows its own policy like every
775
+ // other RFC-deviation finding — under numericPrecisionPolicy:reject it
776
+ // refuses, not audits.
777
+ case "numeric-precision-loss": return gateContract.policyDisposition(opts.numericPrecisionPolicy);
778
+ case "node-count-cap":
779
+ case "depth-cap":
780
+ case "string-too-long":
781
+ case "array-length-cap":
782
+ case "key-count-cap":
783
+ case "bad-input":
784
+ case "too-large":
785
+ case "parse-failed":
786
+ case "missing-allowlist":
787
+ case "top-level-key-not-allowlisted": return "refuse";
788
+ default: return null;
789
+ }
790
+ }
791
+
782
792
  function gate(opts) {
783
793
  opts = _resolveOpts(opts);
784
- return gateContract.buildGuardGate(
785
- opts.name || "guardJson:" + (opts.profile || "default"),
786
- opts,
787
- async function (ctx) {
788
- var text = gateContract.extractBytesAsText(ctx);
789
- if (!text) return { ok: true, action: "serve" };
790
- var rv = validate(text, opts);
791
- if (rv.issues.length === 0) return { ok: true, action: "serve" };
792
- var hasCritical = rv.issues.some(function (i) {
793
- return i.severity === "critical" || i.severity === "high";
794
- });
795
- if (!hasCritical) return { ok: true, action: "audit-only", issues: rv.issues };
796
-
797
- // Sanitize-eligibility: every reject-policy must be off.
798
- var canSanitize = opts.pollutionPolicy !== "reject" &&
799
- opts.duplicateKeyPolicy !== "reject" &&
800
- opts.nanInfinityPolicy !== "reject" &&
801
- opts.commentPolicy !== "reject" &&
802
- opts.trailingCommaPolicy !== "reject" &&
803
- opts.json5SyntaxPolicy !== "reject" &&
804
- opts.bomPolicy !== "reject" &&
805
- opts.bidiPolicy !== "reject" &&
806
- opts.controlPolicy !== "reject" &&
807
- opts.nullBytePolicy !== "reject";
808
- if (canSanitize) {
809
- try {
810
- var clean = parse(text, opts);
811
- var emitted = JSON.stringify(clean);
812
- return {
813
- ok: true, action: "sanitize",
814
- sanitized: Buffer.from(emitted, "utf8"),
815
- issues: rv.issues,
816
- };
817
- } catch (_e) { /* fall through */ }
818
- }
819
- return { ok: false, action: "refuse", issues: rv.issues };
820
- });
794
+ return gateContract.buildContentGate({
795
+ name: opts.name || "guardJson:" + (opts.profile || "default"),
796
+ opts: opts,
797
+ validate: module.exports.validate,
798
+ dispositionFor: _gateDispositionFor,
799
+ // A sanitize-disposition finding (a class set to a mitigation) is repaired in
800
+ // two passes: first the char-strip policies remove bidi / control / null /
801
+ // zero-width per policy (so a strip-policy char threat is excised even when
802
+ // it sits inside a string value), then a parse + re-serialize drops
803
+ // __proto__ / comments / NaN / trailing commas per the active policy. Under a
804
+ // reject policy the finding is already refuse-disposition, so this is not
805
+ // reached for that class.
806
+ produceSanitized: function (text, o) {
807
+ return JSON.stringify(parse(codepointClass.applyCharStripPolicies(text, o), o));
808
+ },
809
+ });
821
810
  }
822
811
 
823
812
  // buildProfile / compliancePosture / loadRulePack are assembled by
@@ -840,8 +829,8 @@ var INTEGRATION_FIXTURES = Object.freeze({
840
829
  // exports (NAME / KIND / MIME_TYPES / EXTENSIONS / INTEGRATION_FIXTURES),
841
830
  // buildProfile / compliancePosture / loadRulePack wiring, plus the
842
831
  // per-guard inspection surface (validate / parse) and JSON extras
843
- // (POLLUTION_KEYS) passed through verbatim. The bespoke `gate` carries
844
- // JSON's sanitize-reparse-reserialize chain unchanged.
832
+ // (POLLUTION_KEYS, surfaced from the framework's canonical pick.POISONED_KEYS).
833
+ // The bespoke `gate` carries JSON's sanitize-reparse-reserialize chain unchanged.
845
834
  module.exports = gateContract.defineGuard({
846
835
  name: "json",
847
836
  kind: "content",
@@ -852,11 +841,14 @@ module.exports = gateContract.defineGuard({
852
841
  mimeTypes: ["application/json", "application/ld+json", "application/vnd.api+json"],
853
842
  extensions: [".json", ".jsonld"],
854
843
  integrationFixtures: INTEGRATION_FIXTURES,
855
- validate: validate,
844
+ detect: _detectIssues,
845
+ intOpts: ["maxBytes", "maxDepth", "maxKeysPerObject", "maxArrayLength",
846
+ "maxStringLength", "maxTotalNodes"],
856
847
  gate: gate,
857
848
  extra: {
849
+ _gateDispositionForTest: _gateDispositionFor,
858
850
  parse: parse,
859
- POLLUTION_KEYS: POLLUTION_KEYS,
851
+ POLLUTION_KEYS: pick.POISONED_KEYS,
860
852
  },
861
853
  });
862
854
 
@@ -50,11 +50,9 @@
50
50
  * JSONPath content-safety guard — refuses user-supplied JSONPath query strings that exhibit dynamic-code-execution shapes BEFORE they reach a JSONPath evaluator.
51
51
  */
52
52
 
53
- var codepointClass = require("./codepoint-class");
54
53
  var lazyRequire = require("./lazy-require");
55
54
  var gateContract = require("./gate-contract");
56
55
  var C = require("./constants");
57
- var numericBounds = require("./numeric-bounds");
58
56
  var { GuardJsonpathError } = require("./framework-error");
59
57
 
60
58
  var observability = lazyRequire(function () { return require("./observability"); });
@@ -81,10 +79,7 @@ var RECURSIVE_DESCENT_RE = /\.\.\[?\*\]?/g;
81
79
 
82
80
  var PROFILES = Object.freeze({
83
81
  "strict": {
84
- bidiPolicy: "reject",
85
- controlPolicy: "reject",
86
- nullBytePolicy: "reject",
87
- zeroWidthPolicy: "reject",
82
+ ...gateContract.CHAR_THREATS_REJECT_ALL,
88
83
  filterExprPolicy: "reject",
89
84
  scriptExprPolicy: "reject",
90
85
  dynamicHintPolicy: "reject",
@@ -96,10 +91,7 @@ var PROFILES = Object.freeze({
96
91
  maxRuntimeMs: C.TIME.seconds(2),
97
92
  },
98
93
  "balanced": {
99
- bidiPolicy: "reject",
100
- controlPolicy: "reject",
101
- nullBytePolicy: "reject",
102
- zeroWidthPolicy: "reject",
94
+ ...gateContract.CHAR_THREATS_REJECT_ALL,
103
95
  filterExprPolicy: "reject", // RCE class — refused at every profile
104
96
  scriptExprPolicy: "reject", // RCE class — refused at every profile
105
97
  dynamicHintPolicy: "reject", // RCE class — refused at every profile
@@ -111,10 +103,7 @@ var PROFILES = Object.freeze({
111
103
  maxRuntimeMs: C.TIME.seconds(2),
112
104
  },
113
105
  "permissive": {
114
- bidiPolicy: "reject", // BIDI refused at every profile
115
- controlPolicy: "reject", // controls refused at every profile
116
- nullBytePolicy: "reject", // null refused at every profile
117
- zeroWidthPolicy: "reject", // zero-width refused at every profile
106
+ ...gateContract.CHAR_THREATS_REJECT_ALL,
118
107
  filterExprPolicy: "reject", // RCE class refused at every profile
119
108
  scriptExprPolicy: "reject", // RCE class refused at every profile
120
109
  dynamicHintPolicy: "reject", // RCE class refused at every profile
@@ -127,35 +116,6 @@ var PROFILES = Object.freeze({
127
116
  },
128
117
  });
129
118
 
130
- var DEFAULTS = Object.freeze(Object.assign({}, PROFILES["strict"], {
131
- mode: "enforce",
132
- }));
133
-
134
- var COMPLIANCE_POSTURES = Object.freeze({
135
- "hipaa": Object.assign({}, PROFILES["strict"], {
136
- forensicSnippetBytes: C.BYTES.bytes(256),
137
- }),
138
- "pci-dss": Object.assign({}, PROFILES["strict"], {
139
- forensicSnippetBytes: C.BYTES.bytes(256),
140
- }),
141
- "gdpr": Object.assign({}, PROFILES["balanced"], {
142
- forensicSnippetBytes: C.BYTES.bytes(128),
143
- }),
144
- "soc2": Object.assign({}, PROFILES["strict"], {
145
- forensicSnippetBytes: C.BYTES.bytes(512),
146
- }),
147
- });
148
-
149
- function _resolveOpts(opts) {
150
- return gateContract.resolveProfileAndPosture(opts, {
151
- profiles: PROFILES,
152
- compliancePostures: COMPLIANCE_POSTURES,
153
- defaults: DEFAULTS,
154
- errorClass: GuardJsonpathError,
155
- errCodePrefix: "jsonpath",
156
- });
157
- }
158
-
159
119
  function _hasDynamicHint(input) {
160
120
  for (var i = 0; i < DYNAMIC_HINTS.length; i += 1) {
161
121
  if (input.indexOf(DYNAMIC_HINTS[i]) !== -1) return DYNAMIC_HINTS[i];
@@ -164,26 +124,9 @@ function _hasDynamicHint(input) {
164
124
  }
165
125
 
166
126
  function _detectIssues(input, opts) {
167
- var issues = [];
168
- if (typeof input !== "string") {
169
- return [{ kind: "bad-input", severity: "high",
170
- ruleId: "jsonpath.bad-input",
171
- snippet: "jsonpath is not a string" }];
172
- }
173
- if (input.length === 0) {
174
- return [{ kind: "empty", severity: "high",
175
- ruleId: "jsonpath.empty",
176
- snippet: "jsonpath is empty" }];
177
- }
178
- if (Buffer.byteLength(input, "utf8") > opts.maxPatternBytes) {
179
- return [{ kind: "pattern-cap", severity: "high",
180
- ruleId: "jsonpath.pattern-cap",
181
- snippet: "jsonpath exceeds maxPatternBytes " +
182
- opts.maxPatternBytes }];
183
- }
184
-
185
- var charThreats = codepointClass.detectCharThreats(input, opts, "jsonpath");
186
- for (var ci = 0; ci < charThreats.length; ci += 1) issues.push(charThreats[ci]);
127
+ var pre = gateContract.detectStringInput(input, opts, { name: "jsonpath", cap: { bytes: opts.maxPatternBytes, kind: "pattern-cap", snippet: "jsonpath exceeds maxPatternBytes " + opts.maxPatternBytes } });
128
+ if (pre.done) return pre.issues;
129
+ var issues = pre.issues;
187
130
 
188
131
  if (opts.filterExprPolicy !== "allow" && FILTER_EXPR_RE.test(input)) { // allow:regex-no-length-cap — input bounded by maxPatternBytes
189
132
  issues.push({
@@ -280,13 +223,10 @@ function _detectIssues(input, opts) {
280
223
  * hostile.ok; // → false
281
224
  * hostile.issues.some(function (i) { return i.kind === "filter-expression"; }); // → true
282
225
  */
283
- function validate(input, opts) {
284
- opts = _resolveOpts(opts);
285
- numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
286
- ["maxBytes", "maxPatternBytes", "maxRecursiveDescents"],
287
- "guardJsonpath.validate", GuardJsonpathError, "jsonpath.bad-opt");
288
- return gateContract.aggregateIssues(_detectIssues(input, opts));
289
- }
226
+ // validate is assembled by gateContract.defineGuard from `detect`
227
+ // (_detectIssues) below — `validate(input, opts) = aggregateIssues(detect(
228
+ // input, resolveOpts(opts)))`, with the numeric opts validated via `intOpts`.
229
+ // The @primitive block above documents the resulting public ABI.
290
230
 
291
231
  /**
292
232
  * @primitive b.guardJsonpath.sanitize
@@ -327,13 +267,11 @@ function validate(input, opts) {
327
267
  * e.code; // → "jsonpath.filter-expression"
328
268
  * }
329
269
  */
330
- function sanitize(input, opts) {
331
- opts = _resolveOpts(opts);
332
- if (typeof input !== "string") {
333
- throw _err("jsonpath.bad-input", "sanitize requires string input");
334
- }
335
- var issues = _detectIssues(input, opts);
336
- gateContract.throwOnRefusalSeverity(issues, { errorClass: GuardJsonpathError, codePrefix: "jsonpath" });
270
+ // _sanitizeTransform the guard-specific normalize applied by defineGuard's
271
+ // generated sanitize AFTER resolve → detect → throw-on-refusal. JSONPath
272
+ // expressions cannot be safely repaired, so the transform is pure pass-through:
273
+ // input that survives the refusal gate is returned unchanged.
274
+ function _sanitizeTransform(input) {
337
275
  return input;
338
276
  }
339
277
 
@@ -351,13 +289,7 @@ function sanitize(input, opts) {
351
289
  // Their wiki sections render from the single-sourced @abiTemplate blocks
352
290
  // in gate-contract.js, instantiated per guard by the page generator.
353
291
 
354
- var INTEGRATION_FIXTURES = Object.freeze({
355
- kind: "identifier",
356
- benignBytes: Buffer.from("$.users[*].name", "utf8"),
357
- hostileBytes: Buffer.from("$..[?(@.x)]", "utf8"),
358
- benignIdentifier: "$.users[*].name",
359
- hostileIdentifier: "$..[?(@.x)]",
360
- });
292
+ var INTEGRATION_FIXTURES = gateContract.identifierFixtures("$.users[*].name", "$..[?(@.x)]");
361
293
 
362
294
  // Assembled from the gate-contract guard factory: error class, registry
363
295
  // exports (NAME / KIND / INTEGRATION_FIXTURES), buildProfile /
@@ -370,10 +302,10 @@ module.exports = gateContract.defineGuard({
370
302
  kind: "identifier",
371
303
  errorClass: GuardJsonpathError,
372
304
  profiles: PROFILES,
373
- defaults: DEFAULTS,
374
- postures: COMPLIANCE_POSTURES,
305
+ base: 256,
375
306
  integrationFixtures: INTEGRATION_FIXTURES,
376
- validate: validate,
377
- sanitize: sanitize,
307
+ detect: _detectIssues,
308
+ sanitizeTransform: _sanitizeTransform,
309
+ intOpts: ["maxBytes", "maxPatternBytes", "maxRecursiveDescents"],
378
310
  ctxFields: ["identifier", "jsonpath"],
379
311
  });