@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
@@ -81,7 +81,7 @@ var codepointClass = require("./codepoint-class");
81
81
  var lazyRequire = require("./lazy-require");
82
82
  var gateContract = require("./gate-contract");
83
83
  var C = require("./constants");
84
- var numericBounds = require("./numeric-bounds");
84
+ var pick = require("./pick");
85
85
  var { GuardGraphqlError } = require("./framework-error");
86
86
 
87
87
  var observability = lazyRequire(function () { return require("./observability"); });
@@ -100,10 +100,7 @@ var PROTO_POISON_QUERY_RE = /[\s,({:]\$?(?:__proto__|constructor|prototype)\b/;
100
100
 
101
101
  var PROFILES = Object.freeze({
102
102
  "strict": {
103
- bidiPolicy: "reject",
104
- controlPolicy: "reject",
105
- nullBytePolicy: "reject",
106
- zeroWidthPolicy: "reject",
103
+ ...gateContract.CHAR_THREATS_REJECT_ALL,
107
104
  introspectionPolicy: "reject",
108
105
  persistedQueryPolicy: "audit",
109
106
  operationNamePolicy: "audit",
@@ -120,10 +117,7 @@ var PROFILES = Object.freeze({
120
117
  maxRuntimeMs: C.TIME.seconds(2),
121
118
  },
122
119
  "balanced": {
123
- bidiPolicy: "reject",
124
- controlPolicy: "reject",
125
- nullBytePolicy: "reject",
126
- zeroWidthPolicy: "reject",
120
+ ...gateContract.CHAR_THREATS_REJECT_ALL,
127
121
  introspectionPolicy: "audit",
128
122
  persistedQueryPolicy: "audit",
129
123
  operationNamePolicy: "audit",
@@ -140,10 +134,7 @@ var PROFILES = Object.freeze({
140
134
  maxRuntimeMs: C.TIME.seconds(2),
141
135
  },
142
136
  "permissive": {
143
- bidiPolicy: "reject", // BIDI refused at every profile
144
- controlPolicy: "reject", // controls refused at every profile
145
- nullBytePolicy: "reject", // null refused at every profile
146
- zeroWidthPolicy: "reject", // zero-width refused at every profile
137
+ ...gateContract.CHAR_THREATS_REJECT_ALL,
147
138
  introspectionPolicy: "allow",
148
139
  persistedQueryPolicy: "allow",
149
140
  operationNamePolicy: "allow",
@@ -161,35 +152,6 @@ var PROFILES = Object.freeze({
161
152
  },
162
153
  });
163
154
 
164
- var DEFAULTS = Object.freeze(Object.assign({}, PROFILES["strict"], {
165
- mode: "enforce",
166
- }));
167
-
168
- var COMPLIANCE_POSTURES = Object.freeze({
169
- "hipaa": Object.assign({}, PROFILES["strict"], {
170
- forensicSnippetBytes: C.BYTES.bytes(512),
171
- }),
172
- "pci-dss": Object.assign({}, PROFILES["strict"], {
173
- forensicSnippetBytes: C.BYTES.bytes(512),
174
- }),
175
- "gdpr": Object.assign({}, PROFILES["balanced"], {
176
- forensicSnippetBytes: C.BYTES.bytes(256),
177
- }),
178
- "soc2": Object.assign({}, PROFILES["strict"], {
179
- forensicSnippetBytes: C.BYTES.bytes(1024),
180
- }),
181
- });
182
-
183
- function _resolveOpts(opts) {
184
- return gateContract.resolveProfileAndPosture(opts, {
185
- profiles: PROFILES,
186
- compliancePostures: COMPLIANCE_POSTURES,
187
- defaults: DEFAULTS,
188
- errorClass: GuardGraphqlError,
189
- errCodePrefix: "graphql",
190
- });
191
- }
192
-
193
155
  // _measureQueryShape — walks the query string and computes
194
156
  // brace-depth + per-selection-set alias counts using simple paren
195
157
  // counting. Not a full GraphQL parser (operator runs the schema-
@@ -333,9 +295,7 @@ function _detectIssues(req, opts) {
333
295
  var pVar = req.variables;
334
296
  var pHas = Object.prototype.hasOwnProperty;
335
297
  var pName = (pVar && typeof pVar === "object" && !Array.isArray(pVar) &&
336
- (pHas.call(pVar, "__proto__") ? "__proto__" :
337
- pHas.call(pVar, "constructor") ? "constructor" :
338
- pHas.call(pVar, "prototype") ? "prototype" : null));
298
+ pick.POISONED_KEYS.find(function (pk) { return pHas.call(pVar, pk); })) || null;
339
299
  if (pName) {
340
300
  issues.push({
341
301
  kind: "variable-prototype-poison", severity: "critical",
@@ -500,14 +460,11 @@ function _detectIssues(req, opts) {
500
460
  * var ok = b.guardGraphql.validate(benign, { profile: "strict" });
501
461
  * ok.ok; // → true
502
462
  */
503
- function validate(input, opts) {
504
- opts = _resolveOpts(opts);
505
- numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
506
- ["maxBytes", "maxQueryBytes", "maxVariableBytes",
507
- "maxDepth", "maxAliasesPerSelection", "maxBatchSize"],
508
- "guardGraphql.validate", GuardGraphqlError, "graphql.bad-opt");
509
- return gateContract.aggregateIssues(_detectIssues(input, opts));
510
- }
463
+ // validate is assembled by gateContract.defineGuard from `detect`
464
+ // (_detectIssues) below — `validate(input, opts) = aggregateIssues(detect(
465
+ // input, resolveOpts(opts)))`, with the byte / depth / alias / batch caps
466
+ // declared via `intOpts`. The @primitive block above documents the
467
+ // resulting public ABI.
511
468
 
512
469
  /**
513
470
  * @primitive b.guardGraphql.sanitize
@@ -539,10 +496,11 @@ function validate(input, opts) {
539
496
  * e.code; // → "graphql.introspection"
540
497
  * }
541
498
  */
542
- function sanitize(input, opts) {
543
- opts = _resolveOpts(opts);
544
- var issues = _detectIssues(input, opts);
545
- gateContract.throwOnRefusalSeverity(issues, { errorClass: GuardGraphqlError, codePrefix: "graphql" });
499
+ // _sanitizeTransform the guard-specific normalize applied by defineGuard's
500
+ // generated sanitize AFTER resolve → detect → throw-on-refusal. GraphQL
501
+ // request bundles can't be partially repaired; once detection passes with no
502
+ // critical/high issue, the input is returned unchanged.
503
+ function _sanitizeTransform(input) {
546
504
  return input;
547
505
  }
548
506
 
@@ -583,25 +541,19 @@ function sanitize(input, opts) {
583
541
  * rv.issues[0].ruleId; // → "graphql.alias-bomb"
584
542
  */
585
543
  function gate(opts) {
586
- opts = _resolveOpts(opts);
544
+ opts = _guard.resolveOpts(opts);
587
545
  return gateContract.buildGuardGate(
588
546
  opts.name || "guardGraphql:" + (opts.profile || "default"),
589
547
  opts,
590
548
  async function (ctx) {
591
549
  var req = ctx && (ctx.graphqlRequest || ctx.gql);
592
550
  if (!req) return { ok: true, action: "serve" };
593
- var rv = validate(req, opts);
594
- if (rv.issues.length === 0) return { ok: true, action: "serve" };
595
- var hasCritical = rv.issues.some(function (i) {
596
- return i.severity === "critical";
597
- });
598
- var hasHigh = rv.issues.some(function (i) {
599
- return i.severity === "high";
600
- });
601
- if (!hasCritical && !hasHigh) {
602
- return { ok: true, action: "audit-only", issues: rv.issues };
603
- }
604
- return { ok: false, action: "refuse", issues: rv.issues };
551
+ // validate is assembled by defineGuard (from `detect` below) and lives
552
+ // on the frozen module.exports; the gate is only invoked at runtime,
553
+ // after the export is assigned. Behaviour is identical to the prior
554
+ // local validate (resolve → intOpts → aggregateIssues(detect)).
555
+ var rv = module.exports.validate(req, opts);
556
+ return gateContract.severityDisposition(rv.issues);
605
557
  });
606
558
  }
607
559
 
@@ -636,15 +588,16 @@ var INTEGRATION_FIXTURES = Object.freeze({
636
588
  // surface (validate / sanitize / bespoke gate) passed through verbatim.
637
589
  // The custom KIND ("graphql-request") is accepted because the bespoke
638
590
  // gate reads its own ctx fields (ctx.graphqlRequest / ctx.gql).
639
- module.exports = gateContract.defineGuard({
591
+ var _guard = module.exports = gateContract.defineGuard({
640
592
  name: "graphql",
641
593
  kind: "graphql-request",
642
594
  errorClass: GuardGraphqlError,
643
595
  profiles: PROFILES,
644
- defaults: DEFAULTS,
645
- postures: COMPLIANCE_POSTURES,
596
+ base: 512,
646
597
  integrationFixtures: INTEGRATION_FIXTURES,
647
- validate: validate,
648
- sanitize: sanitize,
598
+ detect: _detectIssues,
599
+ sanitizeTransform: _sanitizeTransform,
600
+ intOpts: ["maxBytes", "maxQueryBytes", "maxVariableBytes",
601
+ "maxDepth", "maxAliasesPerSelection", "maxBatchSize"],
649
602
  gate: gate,
650
603
  });
@@ -82,6 +82,19 @@ var _TAG_RE = tagwalk.TAG_RE;
82
82
  var _parseAttrs = tagwalk.parseAttrs;
83
83
  var _lineColAt = tagwalk.lineColAt;
84
84
 
85
+ // Strip every HTML tag for plain-text measurement. Re-runs the tag regex
86
+ // until the string is stable so nested-bracket residue (e.g. `<scr<x>ipt>`)
87
+ // cannot survive a single pass. Output is used only for text-length / content
88
+ // checks, never re-emitted as HTML.
89
+ function _stripTags(s) {
90
+ var prev;
91
+ do {
92
+ prev = s;
93
+ s = s.replace(/<[^>]+>/g, "");
94
+ } while (s !== prev);
95
+ return s;
96
+ }
97
+
85
98
  function _innerText(html, tagOpenEnd, tagName) {
86
99
  var lower = tagName.toLowerCase();
87
100
  var closeRe = new RegExp("</\\s*" + lower + "\\s*>", "i"); // allow:dynamic-regex — `lower` is a tag name from the framework's static SC registry, not operator input; `\\s*` and tag name are RegExp-safe (no special chars)
@@ -89,7 +102,7 @@ function _innerText(html, tagOpenEnd, tagName) {
89
102
  var m = closeRe.exec(html);
90
103
  if (!m) return "";
91
104
  var raw = html.slice(tagOpenEnd, m.index);
92
- return raw.replace(/<[^>]+>/g, "").replace(/&[a-z]+;|&#\d+;/gi, "").trim();
105
+ return _stripTags(raw).replace(/&[a-z]+;|&#\d+;/gi, "").trim();
93
106
  }
94
107
 
95
108
  // ---- Per-element checks ----
@@ -270,7 +283,7 @@ function _checkPageTitle(html, report, opts) {
270
283
  });
271
284
  return;
272
285
  }
273
- var title = m[1].replace(/<[^>]+>/g, "").trim();
286
+ var title = _stripTags(m[1]).trim();
274
287
  if (title.length === 0) {
275
288
  var pos2 = _lineColAt(html, m.index);
276
289
  _addFinding(report, {
package/lib/guard-html.js CHANGED
@@ -93,6 +93,8 @@
93
93
  */
94
94
 
95
95
  var codepointClass = require("./codepoint-class");
96
+ var markupTokenizer = require("./markup-tokenizer");
97
+ var markupEscape = require("./markup-escape").markupEscape;
96
98
  var guardHtmlWcag = require("./guard-html-wcag");
97
99
  var lazyRequire = require("./lazy-require");
98
100
  var gateContract = require("./gate-contract");
@@ -105,7 +107,6 @@ var observability = lazyRequire(function () { return require("./observability");
105
107
  void observability;
106
108
 
107
109
  var _err = GuardHtmlError.factory;
108
- var HEX_RADIX = 16; // base-16 radix, not byte size
109
110
 
110
111
  // ---- Codepoint catalog (shared via lib/codepoint-class) ----
111
112
 
@@ -179,16 +180,14 @@ var URL_ATTRS = Object.freeze([
179
180
  ]);
180
181
 
181
182
  // Always-allowed schemes (every profile).
182
- var SAFE_SCHEMES = Object.freeze(["http", "https", "mailto", "tel"]);
183
+ var SAFE_SCHEMES = gateContract.SAFE_URL_SCHEMES;
183
184
 
184
185
  // Schemes denied by default. `data` is dangerous globally except in
185
186
  // image context (data:image/*) which is the only data: payload that
186
187
  // can't directly script the page; the per-attribute check below honours
187
188
  // that exception when allowImageData=true and the host tag is <img>.
188
- var DANGEROUS_SCHEMES = Object.freeze([
189
- "javascript", "vbscript", "livescript", "mocha", "ecmascript",
190
- "file", "mhtml", "jar", "intent", "view-source", "feed", "data",
191
- ]);
189
+ // Markup-attribute scheme denylist — the shared XSS / dangerous-resource set.
190
+ var DANGEROUS_SCHEMES = gateContract.DANGEROUS_URL_SCHEMES;
192
191
 
193
192
  // CSS dangerous tokens — case-insensitive match against attribute
194
193
  // value content.
@@ -291,54 +290,12 @@ var PROFILES = Object.freeze({
291
290
  },
292
291
  });
293
292
 
294
- var DEFAULTS = Object.freeze(Object.assign({}, PROFILES["strict"], {
295
- mode: "enforce",
293
+ var DEFAULTS = gateContract.strictDefaults(PROFILES, {
296
294
  maxRuntimeMs: C.TIME.seconds(30),
297
- }));
298
-
299
- var COMPLIANCE_POSTURES = Object.freeze({
300
- "hipaa": {
301
- allowedTags: STRICT_ALLOWED_TAGS,
302
- bidiPolicy: "reject",
303
- controlPolicy: "reject",
304
- nullBytePolicy: "reject",
305
- cssPolicy: "reject",
306
- domClobberPolicy: "reject",
307
- mxssHintPolicy: "reject",
308
- forensicSnippetBytes: C.BYTES.bytes(256),
309
- },
310
- "pci-dss": {
311
- allowedTags: STRICT_ALLOWED_TAGS,
312
- bidiPolicy: "reject",
313
- controlPolicy: "reject",
314
- nullBytePolicy: "reject",
315
- cssPolicy: "reject",
316
- domClobberPolicy: "reject",
317
- mxssHintPolicy: "reject",
318
- urlSchemes: SAFE_SCHEMES,
319
- forensicSnippetBytes: C.BYTES.bytes(256),
320
- },
321
- "gdpr": {
322
- allowedTags: BALANCED_ALLOWED_TAGS,
323
- bidiPolicy: "strip",
324
- controlPolicy: "strip",
325
- cssPolicy: "strip",
326
- domClobberPolicy: "strip",
327
- mxssHintPolicy: "audit",
328
- forensicSnippetBytes: C.BYTES.bytes(128),
329
- },
330
- "soc2": {
331
- allowedTags: STRICT_ALLOWED_TAGS,
332
- bidiPolicy: "reject",
333
- controlPolicy: "reject",
334
- nullBytePolicy: "reject",
335
- cssPolicy: "reject",
336
- domClobberPolicy: "reject",
337
- mxssHintPolicy: "reject",
338
- forensicSnippetBytes: C.BYTES.bytes(512),
339
- },
340
295
  });
341
296
 
297
+ var COMPLIANCE_POSTURES = gateContract.compliancePostures(PROFILES, { base: 256 });
298
+
342
299
  // ---- Internal helpers ----
343
300
 
344
301
  function _resolveOpts(opts) {
@@ -372,12 +329,7 @@ function _resolveOpts(opts) {
372
329
  */
373
330
  function escapeText(value) {
374
331
  var s = value == null ? "" : String(value);
375
- return s
376
- .replace(/&/g, "&amp;")
377
- .replace(/</g, "&lt;")
378
- .replace(/>/g, "&gt;")
379
- .replace(/"/g, "&quot;")
380
- .replace(/'/g, "&#39;");
332
+ return markupEscape(s, { apos: "&#39;" });
381
333
  }
382
334
 
383
335
  /**
@@ -401,12 +353,9 @@ function escapeText(value) {
401
353
  */
402
354
  function escapeAttr(value) {
403
355
  var s = value == null ? "" : String(value);
404
- return s
405
- .replace(/&/g, "&amp;")
406
- .replace(/</g, "&lt;")
407
- .replace(/>/g, "&gt;")
408
- .replace(/"/g, "&quot;")
409
- .replace(/'/g, "&#39;")
356
+ // Base markup chars + apostrophe via markupEscape; the backtick + "="
357
+ // escapes are the IE attribute-injection hardening unique to this escaper.
358
+ return markupEscape(s, { apos: "&#39;" })
410
359
  .replace(/`/g, "&#96;")
411
360
  .replace(/=/g, "&#61;");
412
361
  }
@@ -440,14 +389,11 @@ var NAMED_ENTITY_ASCII = {
440
389
  // scheme. Returns "" if no scheme.
441
390
  function _extractScheme(rawUrl) {
442
391
  var s = String(rawUrl || "").trim();
443
- // Decode HTML numeric entities just enough to expose hidden schemes
444
- // like &#x6A;avascript:... or &#106;avascript:...
445
- s = s.replace(/&#x([0-9a-f]+);/gi, function (_m, h) {
446
- return String.fromCharCode(parseInt(h, HEX_RADIX));
447
- });
448
- s = s.replace(/&#(\d+);/g, function (_m, d) {
449
- return String.fromCharCode(parseInt(d, 10));
450
- });
392
+ // Decode HTML numeric entities (hex &#x..; and decimal &#..;, semicolon
393
+ // OPTIONAL) just enough to expose hidden schemes like &#x6A;avascript: or
394
+ // the browser-decoded no-semicolon form &#106avascript:. Shared decoder so
395
+ // guard-html / guard-svg / guard-markdown can't drift (see codepoint-class).
396
+ s = codepointClass.decodeNumericEntities(s);
451
397
  // Decode HTML5 named entities that browsers honor inside URL
452
398
  // contexts. Without this, payloads like `java&Tab;script:alert(1)`
453
399
  // bypass the scheme allowlist (the literal `&Tab;` between `java`
@@ -511,9 +457,10 @@ function _isCssDangerous(value) {
511
457
 
512
458
  function _tokenize(input, maxBytes) {
513
459
  var s = String(input || "");
514
- if (s.length > maxBytes) {
460
+ var nb = Buffer.byteLength(s, "utf8");
461
+ if (nb > maxBytes) {
515
462
  throw _err("html.too-large",
516
- "input " + s.length + " bytes exceeds maxBytes " + maxBytes);
463
+ "input " + nb + " bytes exceeds maxBytes " + maxBytes);
517
464
  }
518
465
  var tokens = [];
519
466
  var len = s.length;
@@ -531,9 +478,11 @@ function _tokenize(input, maxBytes) {
531
478
 
532
479
  // Comment / CDATA / doctype
533
480
  if (s.startsWith("<!--", lt)) {
534
- var endC = s.indexOf("-->", lt + 4);
481
+ // WHATWG comment end (covers "--!>" + abrupt "<!-->" / "<!--->"), not
482
+ // only "-->", so a smuggled element after an early terminator is split
483
+ // out as a live token instead of hidden inside the comment (mXSS).
484
+ var endC = markupTokenizer.htmlCommentEnd(s, lt);
535
485
  if (endC === -1) endC = len;
536
- else endC += 3;
537
486
  tokens.push({ type: "comment", raw: s.slice(lt, endC), start: lt, end: endC });
538
487
  pos = endC; continue;
539
488
  }
@@ -567,28 +516,15 @@ function _tokenize(input, maxBytes) {
567
516
 
568
517
  // Start tag — find the matching `>`, but skip over `>` inside
569
518
  // quoted attribute values.
570
- var p = lt + 1;
571
- var inQuote = "";
572
- while (p < len) {
573
- var ch = s.charAt(p);
574
- if (inQuote) {
575
- if (ch === inQuote) inQuote = "";
576
- } else {
577
- if (ch === '"' || ch === "'") inQuote = ch;
578
- else if (ch === ">") break;
579
- }
580
- p += 1;
581
- }
519
+ var p = markupTokenizer.scanToTagEnd(s, lt + 1, len);
582
520
  var endT = p < len ? p + 1 : len;
583
521
  var raw = s.slice(lt, endT);
584
522
  var inner = raw.slice(1, raw.charAt(raw.length - 1) === ">" ? raw.length - 1 : raw.length);
585
523
  if (inner.endsWith("/")) inner = inner.slice(0, inner.length - 1);
586
524
 
587
- var nameMatch = inner.match(/^([A-Za-z][A-Za-z0-9:-]*)/);
588
- var tagName = nameMatch ? nameMatch[1].toLowerCase() : "";
589
- var attrSrc = nameMatch ? inner.slice(nameMatch[0].length) : "";
590
-
591
- var attrs = _parseAttrs(attrSrc);
525
+ var htmlParts = markupTokenizer.splitTagNameAttrs(inner, /^([A-Za-z][A-Za-z0-9:-]*)/);
526
+ var tagName = htmlParts.tagName;
527
+ var attrs = _parseAttrs(htmlParts.attrSrc);
592
528
  tokens.push({
593
529
  type: "tag", name: tagName, attrs: attrs,
594
530
  raw: raw, start: lt, end: endT,
@@ -643,7 +579,7 @@ function _parseAttrs(src) {
643
579
  function _detectIssues(input, opts) {
644
580
  var s = String(input || "");
645
581
  // 1. Whole-input bidi / null-byte / control char threats.
646
- var issues = codepointClass.detectCharThreats(s, opts, "html");
582
+ var issues = codepointClass.detectCharThreats(s, opts, "html", "warn");
647
583
 
648
584
  var tokens;
649
585
  try { tokens = _tokenize(s, opts.maxBytes); }
@@ -712,7 +648,7 @@ function _detectIssues(input, opts) {
712
648
  for (var ai = 0; ai < attrs.length; ai += 1) {
713
649
  var a = attrs[ai];
714
650
  var an = a.name.toLowerCase();
715
- if (a.value && a.value.length > opts.maxAttrValueBytes) {
651
+ if (a.value && Buffer.byteLength(a.value, "utf8") > opts.maxAttrValueBytes) {
716
652
  issues.push({
717
653
  kind: "attr-value-too-large", severity: "high",
718
654
  ruleId: "html.attr-size",
@@ -814,9 +750,10 @@ function _detectIssues(input, opts) {
814
750
 
815
751
  function _sanitize(input, opts) {
816
752
  var s = String(input || "");
817
- if (s.length > opts.maxBytes) {
753
+ var nb = Buffer.byteLength(s, "utf8");
754
+ if (nb > opts.maxBytes) {
818
755
  throw _err("html.too-large",
819
- "input " + s.length + " bytes exceeds maxBytes " + opts.maxBytes);
756
+ "input " + nb + " bytes exceeds maxBytes " + opts.maxBytes);
820
757
  }
821
758
  codepointClass.assertNoCharThreats(s, opts, _err, "html");
822
759
  s = codepointClass.applyCharStripPolicies(s, opts);
@@ -884,7 +821,7 @@ function _sanitize(input, opts) {
884
821
  if (EVENT_HANDLER_RE.test(an)) continue;
885
822
  if (DANGEROUS_ATTRS.indexOf(an) !== -1) continue;
886
823
  if (Object.keys(allowedAttrs).length > 0 && !allowedAttrs[an]) continue;
887
- if (a.value && a.value.length > opts.maxAttrValueBytes) continue;
824
+ if (a.value && Buffer.byteLength(a.value, "utf8") > opts.maxAttrValueBytes) continue;
888
825
  if (_isUrlAttr(an)) {
889
826
  var scheme = _extractScheme(a.value);
890
827
  if (scheme && DANGEROUS_SCHEMES.indexOf(scheme) !== -1) {
@@ -1061,39 +998,47 @@ function sanitize(input, opts) {
1061
998
  * rv.ok; // → false
1062
999
  * rv.action; // → "refuse"
1063
1000
  */
1001
+ // Disposition of each html finding = what the operator's policy for that class
1002
+ // selected. The configurable classes (CSS injection / DOM-clobbering / mXSS
1003
+ // hint) and the bidi / null / control char threats follow their policies
1004
+ // (sanitize under `strip`, refuse under `reject`, audit under `audit`). The
1005
+ // always-dangerous denylist (dangerous tag / event handler / dangerous attr /
1006
+ // dangerous URL scheme) refuses — stripping a script-equivalent is not a
1007
+ // provable repair (an mXSS / parser-differential vector can survive); a
1008
+ // non-allowlisted but otherwise-benign tag / scheme sanitizes (the allowlist
1009
+ // sanitizer drops it); structural caps and a tokenizer failure refuse.
1010
+ // Exhaustive over every kind the detection pass emits.
1011
+ function _gateDispositionFor(issue, opts) {
1012
+ var shared = gateContract.charThreatDisposition(issue, opts);
1013
+ if (shared) return shared;
1014
+ switch (issue.kind) {
1015
+ case "css-injection": return gateContract.policyDisposition(opts.cssPolicy);
1016
+ case "dom-clobber": return gateContract.policyDisposition(opts.domClobberPolicy);
1017
+ case "mxss-hint": return gateContract.policyDisposition(opts.mxssHintPolicy);
1018
+ case "non-allowlisted-tag":
1019
+ case "non-allowlisted-url-scheme": return "sanitize";
1020
+ case "dangerous-tag":
1021
+ case "event-handler":
1022
+ case "dangerous-attr":
1023
+ case "dangerous-url-scheme": return "refuse";
1024
+ case "tokenize-failed":
1025
+ case "ie-conditional-comment":
1026
+ case "depth-cap":
1027
+ case "attr-count-cap":
1028
+ case "attr-value-too-large": return "refuse";
1029
+ default: return null;
1030
+ }
1031
+ }
1032
+
1064
1033
  function gate(opts) {
1065
1034
  opts = _resolveOpts(opts);
1066
- return gateContract.buildGuardGate(
1067
- opts.name || "guardHtml:" + (opts.profile || "default"),
1068
- opts,
1069
- async function (ctx) {
1070
- var text = gateContract.extractBytesAsText(ctx);
1071
- if (!text) return { ok: true, action: "serve" };
1072
- var rv = validate(text, opts);
1073
- if (rv.issues.length === 0) return { ok: true, action: "serve" };
1074
- var hasCritical = rv.issues.some(function (i) {
1075
- return i.severity === "critical" || i.severity === "high";
1076
- });
1077
- if (!hasCritical) return { ok: true, action: "audit-only", issues: rv.issues };
1078
-
1079
- // Sanitize attempt — only when no policy says reject.
1080
- if (opts.bidiPolicy !== "reject" &&
1081
- opts.controlPolicy !== "reject" &&
1082
- opts.nullBytePolicy !== "reject" &&
1083
- opts.cssPolicy !== "reject" &&
1084
- opts.domClobberPolicy !== "reject" &&
1085
- opts.mxssHintPolicy !== "reject") {
1086
- try {
1087
- var clean = sanitize(text, opts);
1088
- return {
1089
- ok: true, action: "sanitize",
1090
- sanitized: Buffer.from(clean, "utf8"),
1091
- issues: rv.issues,
1092
- };
1093
- } catch (_e) { /* fall through */ }
1094
- }
1095
- return { ok: false, action: "refuse", issues: rv.issues };
1096
- });
1035
+ return gateContract.buildContentGate({
1036
+ name: opts.name || "guardHtml:" + (opts.profile || "default"),
1037
+ opts: opts,
1038
+ validate: validate,
1039
+ dispositionFor: _gateDispositionFor,
1040
+ produceSanitized: function (text, o) { return sanitize(text, o); },
1041
+ });
1097
1042
  }
1098
1043
 
1099
1044
  // buildProfile / compliancePosture / loadRulePack are assembled by
@@ -1135,6 +1080,7 @@ module.exports = gateContract.defineGuard({
1135
1080
  sanitize: sanitize,
1136
1081
  gate: gate,
1137
1082
  extra: {
1083
+ _gateDispositionForTest: _gateDispositionFor,
1138
1084
  escapeText: escapeText,
1139
1085
  escapeAttr: escapeAttr,
1140
1086
  DANGEROUS_TAGS: DANGEROUS_TAGS,
@@ -31,6 +31,7 @@
31
31
 
32
32
  var { defineClass } = require("./framework-error");
33
33
  var gateContract = require("./gate-contract");
34
+ var codepointClass = require("./codepoint-class");
34
35
 
35
36
  var GuardIdempotencyKeyError = defineClass("GuardIdempotencyKeyError", { alwaysPermanent: true });
36
37
 
@@ -90,7 +91,7 @@ function validate(value, opts) {
90
91
  // C0 / DEL / slash refusal.
91
92
  for (var i = 0; i < value.length; i += 1) {
92
93
  var c = value.charCodeAt(i);
93
- if (c < 0x20 || c === 0x7F) { // C0 + DEL refusal
94
+ if (codepointClass.isForbiddenControlChar(c, { forbidTab: true })) { // C0 + DEL refusal
94
95
  throw new GuardIdempotencyKeyError("idempotency-key/control-char",
95
96
  "guardIdempotencyKey.validate: control char 0x" + c.toString(16) + " at offset " + i);
96
97
  }