@blamejs/core 0.15.13 → 0.15.15

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 (208) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +38 -6
  4. package/lib/agent-event-bus.js +13 -0
  5. package/lib/agent-idempotency.js +5 -1
  6. package/lib/agent-snapshot.js +32 -2
  7. package/lib/ai-aedt-bias-audit.js +2 -1
  8. package/lib/ai-content-detect.js +1 -3
  9. package/lib/ai-frontier-protocol.js +1 -1
  10. package/lib/ai-model-manifest.js +1 -1
  11. package/lib/ai-output.js +16 -7
  12. package/lib/api-snapshot.js +4 -1
  13. package/lib/app-shutdown.js +7 -1
  14. package/lib/archive-gz.js +9 -0
  15. package/lib/archive-read.js +9 -7
  16. package/lib/archive-tar-read.js +51 -8
  17. package/lib/archive.js +4 -2
  18. package/lib/asn1-der.js +70 -22
  19. package/lib/atomic-file.js +204 -2
  20. package/lib/audit-chain.js +54 -5
  21. package/lib/audit-daily-review.js +12 -2
  22. package/lib/audit-sign.js +2 -1
  23. package/lib/audit-tools.js +108 -23
  24. package/lib/audit.js +7 -2
  25. package/lib/auth/access-lock.js +2 -2
  26. package/lib/auth/bot-challenge.js +1 -1
  27. package/lib/auth/ciba.js +71 -8
  28. package/lib/auth/dpop.js +15 -1
  29. package/lib/auth/fido-mds3.js +30 -13
  30. package/lib/auth/jwt.js +21 -5
  31. package/lib/auth/lockout.js +18 -2
  32. package/lib/auth/oauth.js +8 -2
  33. package/lib/auth/passkey.js +1 -1
  34. package/lib/auth/password.js +1 -1
  35. package/lib/auth/saml.js +42 -12
  36. package/lib/auth/sd-jwt-vc.js +24 -4
  37. package/lib/auth/status-list.js +14 -2
  38. package/lib/auth/step-up.js +9 -1
  39. package/lib/auth-bot-challenge.js +21 -2
  40. package/lib/backup/bundle.js +7 -2
  41. package/lib/backup/crypto.js +23 -8
  42. package/lib/backup/index.js +41 -20
  43. package/lib/backup/manifest.js +7 -1
  44. package/lib/break-glass.js +41 -22
  45. package/lib/cbor.js +34 -11
  46. package/lib/cdn-cache-control.js +7 -3
  47. package/lib/cert.js +5 -3
  48. package/lib/cli.js +5 -1
  49. package/lib/cloud-events.js +3 -2
  50. package/lib/cluster-storage.js +7 -3
  51. package/lib/codepoint-class.js +17 -0
  52. package/lib/compliance-eaa.js +1 -1
  53. package/lib/compliance-sanctions.js +9 -7
  54. package/lib/compliance.js +1 -1
  55. package/lib/config-drift.js +22 -8
  56. package/lib/content-credentials.js +11 -8
  57. package/lib/content-digest.js +11 -4
  58. package/lib/cookies.js +10 -2
  59. package/lib/cose.js +20 -0
  60. package/lib/crdt.js +2 -1
  61. package/lib/crypto-field.js +48 -24
  62. package/lib/crypto.js +18 -4
  63. package/lib/csp.js +13 -0
  64. package/lib/daemon.js +4 -1
  65. package/lib/data-act.js +27 -4
  66. package/lib/db-file-lifecycle.js +14 -6
  67. package/lib/db-query.js +102 -11
  68. package/lib/db.js +32 -15
  69. package/lib/dora.js +43 -13
  70. package/lib/dr-runbook.js +1 -1
  71. package/lib/dsa.js +2 -1
  72. package/lib/dsr.js +22 -8
  73. package/lib/early-hints.js +19 -0
  74. package/lib/eat.js +5 -1
  75. package/lib/external-db.js +60 -4
  76. package/lib/fda-21cfr11.js +30 -5
  77. package/lib/flag-providers.js +6 -2
  78. package/lib/forms.js +1 -1
  79. package/lib/gate-contract.js +46 -5
  80. package/lib/gdpr-ropa.js +18 -9
  81. package/lib/graphql-federation.js +17 -4
  82. package/lib/guard-all.js +2 -2
  83. package/lib/guard-dsn.js +1 -1
  84. package/lib/guard-envelope.js +1 -1
  85. package/lib/guard-html.js +9 -11
  86. package/lib/guard-imap-command.js +1 -1
  87. package/lib/guard-jmap.js +1 -1
  88. package/lib/guard-json.js +14 -6
  89. package/lib/guard-mail-move.js +1 -1
  90. package/lib/guard-managesieve-command.js +1 -1
  91. package/lib/guard-pop3-command.js +1 -1
  92. package/lib/guard-smtp-command.js +1 -1
  93. package/lib/guard-svg.js +8 -9
  94. package/lib/html-balance.js +7 -3
  95. package/lib/http-client-cookie-jar.js +33 -12
  96. package/lib/http-client.js +225 -53
  97. package/lib/iab-tcf.js +3 -2
  98. package/lib/importmap-integrity.js +41 -1
  99. package/lib/incident-report.js +9 -6
  100. package/lib/json-patch.js +1 -1
  101. package/lib/json-path.js +24 -3
  102. package/lib/jtd.js +2 -2
  103. package/lib/legal-hold.js +24 -8
  104. package/lib/log.js +2 -2
  105. package/lib/mail-agent.js +2 -2
  106. package/lib/mail-arf.js +1 -1
  107. package/lib/mail-auth.js +27 -4
  108. package/lib/mail-bimi.js +16 -16
  109. package/lib/mail-bounce.js +3 -3
  110. package/lib/mail-crypto-smime.js +71 -6
  111. package/lib/mail-deploy.js +9 -5
  112. package/lib/mail-dkim.js +20 -7
  113. package/lib/mail-greylist.js +2 -4
  114. package/lib/mail-helo.js +2 -4
  115. package/lib/mail-journal.js +11 -8
  116. package/lib/mail-mdn.js +8 -4
  117. package/lib/mail-rbl.js +2 -4
  118. package/lib/mail-scan.js +3 -5
  119. package/lib/mail-server-jmap.js +4 -1
  120. package/lib/mail-server-registry.js +1 -1
  121. package/lib/mail-server-tls.js +9 -2
  122. package/lib/mail-spam-score.js +2 -4
  123. package/lib/mail-store-fts.js +1 -1
  124. package/lib/mail.js +22 -2
  125. package/lib/markup-tokenizer.js +24 -0
  126. package/lib/mcp.js +6 -4
  127. package/lib/mdoc.js +26 -3
  128. package/lib/metrics.js +14 -3
  129. package/lib/middleware/api-encrypt.js +2 -2
  130. package/lib/middleware/body-parser.js +10 -4
  131. package/lib/middleware/bot-guard.js +26 -18
  132. package/lib/middleware/clear-site-data.js +5 -1
  133. package/lib/middleware/compose-pipeline.js +39 -5
  134. package/lib/middleware/compression.js +9 -0
  135. package/lib/middleware/cors.js +32 -23
  136. package/lib/middleware/csrf-protect.js +60 -21
  137. package/lib/middleware/daily-byte-quota.js +6 -4
  138. package/lib/middleware/fetch-metadata.js +28 -4
  139. package/lib/middleware/network-allowlist.js +61 -30
  140. package/lib/middleware/rate-limit.js +25 -16
  141. package/lib/middleware/scim-server.js +2 -1
  142. package/lib/middleware/security-headers.js +24 -6
  143. package/lib/middleware/speculation-rules.js +6 -3
  144. package/lib/middleware/tus-upload.js +2 -2
  145. package/lib/money.js +1 -1
  146. package/lib/mtls-ca.js +10 -6
  147. package/lib/network-dns-resolver.js +1 -1
  148. package/lib/network-dns.js +9 -2
  149. package/lib/network-dnssec.js +2 -1
  150. package/lib/network-smtp-policy.js +23 -5
  151. package/lib/network-tls.js +27 -5
  152. package/lib/network-tsig.js +2 -2
  153. package/lib/nis2-report.js +1 -1
  154. package/lib/nist-crosswalk.js +1 -1
  155. package/lib/ntp-check.js +28 -0
  156. package/lib/numeric-bounds.js +9 -0
  157. package/lib/object-store/azure-blob.js +1 -2
  158. package/lib/object-store/gcs-bucket-ops.js +4 -2
  159. package/lib/object-store/gcs.js +6 -4
  160. package/lib/object-store/http-put.js +1 -2
  161. package/lib/object-store/http-request.js +30 -1
  162. package/lib/object-store/local.js +37 -17
  163. package/lib/object-store/sigv4.js +1 -2
  164. package/lib/observability-otlp-exporter.js +20 -4
  165. package/lib/outbox.js +11 -4
  166. package/lib/parsers/safe-xml.js +1 -1
  167. package/lib/parsers/safe-yaml.js +21 -3
  168. package/lib/pipl-cn.js +11 -8
  169. package/lib/queue-local.js +10 -3
  170. package/lib/redact.js +7 -3
  171. package/lib/request-helpers.js +347 -36
  172. package/lib/resource-access-lock.js +3 -3
  173. package/lib/restore-bundle.js +46 -18
  174. package/lib/restore-rollback.js +10 -4
  175. package/lib/restore.js +19 -0
  176. package/lib/retention.js +20 -4
  177. package/lib/router.js +17 -4
  178. package/lib/safe-ical.js +2 -2
  179. package/lib/safe-icap.js +1 -1
  180. package/lib/safe-json.js +70 -0
  181. package/lib/safe-sieve.js +1 -1
  182. package/lib/safe-vcard.js +1 -1
  183. package/lib/sandbox-worker.js +6 -0
  184. package/lib/sandbox.js +1 -1
  185. package/lib/scheduler.js +17 -1
  186. package/lib/self-update-standalone-verifier.js +16 -0
  187. package/lib/session.js +62 -120
  188. package/lib/sql.js +25 -3
  189. package/lib/static.js +65 -13
  190. package/lib/template.js +7 -5
  191. package/lib/tenant-quota.js +52 -19
  192. package/lib/tsa.js +5 -2
  193. package/lib/vault/index.js +5 -0
  194. package/lib/vault/passphrase-ops.js +22 -26
  195. package/lib/vault/passphrase-source.js +8 -3
  196. package/lib/vault/rotate.js +13 -18
  197. package/lib/vault/seal-pem-file.js +4 -1
  198. package/lib/vc.js +1 -1
  199. package/lib/vendor/MANIFEST.json +10 -10
  200. package/lib/vendor/public-suffix-list.dat +23 -10
  201. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  202. package/lib/webhook.js +16 -1
  203. package/lib/websocket.js +1 -1
  204. package/lib/worm.js +1 -1
  205. package/lib/ws-client.js +83 -46
  206. package/lib/x509-chain.js +91 -0
  207. package/package.json +1 -1
  208. package/sbom.cdx.json +6 -6
package/lib/cbor.js CHANGED
@@ -55,10 +55,17 @@
55
55
 
56
56
  var C = require("./constants");
57
57
  var safeBuffer = require("./safe-buffer");
58
+ var boundedMap = require("./bounded-map");
58
59
  var { defineClass } = require("./framework-error");
59
60
 
60
61
  var CborError = defineClass("CborError", { alwaysPermanent: true });
61
62
 
63
+ // Hoisted so the map-decode loop's per-key uniqueness guard doesn't allocate a
64
+ // closure per key (the decode hot path).
65
+ function _throwDuplicateKey() {
66
+ throw new CborError("cbor/duplicate-key", "cbor.decode: duplicate map key (RFC 8949 §5.6)");
67
+ }
68
+
62
69
  var DEFAULT_MAX_DEPTH = 64; // nesting depth, not a size
63
70
  var ABSOLUTE_MAX_DEPTH = 256; // nesting depth ceiling, not a size
64
71
  var DEFAULT_MAX_BYTES = C.BYTES.mib(16);
@@ -127,6 +134,7 @@ function _encodeFloat(value) {
127
134
  // exponent must fit the half range and the low 13 mantissa bits must
128
135
  // be zero (half has a 10-bit mantissa vs float32's 23).
129
136
  function _doubleToHalfBits(value) {
137
+ if (value === 0) return Object.is(value, -0) ? 0x8000 : 0x0000; // ±zero → half zero (sign-preserving); -0 must not fall to the subnormal path
130
138
  var fbuf = Buffer.alloc(4);
131
139
  fbuf.writeFloatBE(value, 0);
132
140
  if (fbuf.readFloatBE(0) !== value) return -1; // not exact in float32 → not in float16
@@ -177,8 +185,10 @@ function _encodeValue(value, opts) {
177
185
  // Exact integers within the safe range encode as CBOR integers;
178
186
  // an integer-VALUED number beyond 2^53 (e.g. 1e300) has lost
179
187
  // integer precision and is a float — encode it as a float (use a
180
- // bigint for exact 64-bit CBOR integers).
181
- if (Number.isInteger(value) && Math.abs(value) <= Number.MAX_SAFE_INTEGER) {
188
+ // bigint for exact 64-bit CBOR integers). NEGATIVE ZERO is excluded:
189
+ // CBOR integers have no -0, so encoding -0 as the uint 0 would silently
190
+ // drop the sign; it falls through to the float branch (float16 -0.0).
191
+ if (Number.isInteger(value) && !Object.is(value, -0) && Math.abs(value) <= Number.MAX_SAFE_INTEGER) {
182
192
  return value >= 0 ? _head(0, value) : _head(1, -1 - value);
183
193
  }
184
194
  if (!isFinite(value) && !opts.allowNonFinite) {
@@ -337,7 +347,11 @@ function _need(state, n) {
337
347
 
338
348
  function _readArgument(state, ai) {
339
349
  // ai is the low-5-bits additional info. Returns the argument as a
340
- // Number (or BigInt for 8-byte values beyond Number range).
350
+ // Number (or BigInt for 8-byte values beyond Number range). Non-minimal
351
+ // (non-canonical) heads are tolerated here by design — strict canonical
352
+ // enforcement is the opt-in `requireDeterministic` mode (round-trip compare);
353
+ // duplicate keys encoded non-minimally are caught value-based in the map
354
+ // decoder regardless of mode (RFC 8949 §5.6).
341
355
  if (ai < CBOR_AI_1BYTE) return ai;
342
356
  if (ai === CBOR_AI_1BYTE) { _need(state, 1); var v1 = state.buf[state.pos]; state.pos += 1; return v1; }
343
357
  if (ai === 25) { _need(state, 2); var v2 = state.buf.readUInt16BE(state.pos); state.pos += 2; return v2; }
@@ -410,17 +424,26 @@ function _decodeItem(state, depth) {
410
424
  case 5: { // map
411
425
  var mlen = _lenOf(_readArgument(state, ai));
412
426
  var m = new Map();
413
- var seen = [];
427
+ // O(1) duplicate-key detection (RFC 8949 §5.6) keyed on the CANONICAL
428
+ // re-encoding of each decoded key — not its raw input bytes. A per-key
429
+ // Buffer.compare scan over a `seen` array is O(n²), and cbor.decode runs on
430
+ // raw attacker bytes BEFORE any signature check on every COSE / CWT / EAT /
431
+ // mdoc verify path, so a large distinct-key map is an unauthenticated
432
+ // algorithmic-complexity DoS (CWE-407). Keying on the canonical encoding
433
+ // (rather than the input bytes) also closes the bypass where the SAME key
434
+ // value is sent twice with different (e.g. non-minimal) encodings to dodge
435
+ // a byte-wise dup check and silently last-value-win — both copies
436
+ // re-encode identically here and the duplicate is caught regardless of the
437
+ // requireDeterministic mode.
438
+ var seen = new Set();
414
439
  for (var j = 0; j < mlen; j++) {
415
440
  var keyStart = state.pos;
416
441
  var key = _decodeItem(state, depth + 1);
417
- var keyBytes = state.buf.slice(keyStart, state.pos);
418
- for (var s = 0; s < seen.length; s++) {
419
- if (Buffer.compare(seen[s], keyBytes) === 0) {
420
- throw new CborError("cbor/duplicate-key", "cbor.decode: duplicate map key (RFC 8949 §5.6)");
421
- }
422
- }
423
- seen.push(keyBytes);
442
+ var keyId;
443
+ try { keyId = encode(key).toString("latin1"); }
444
+ catch (_e) { keyId = "raw:" + state.buf.toString("latin1", keyStart, state.pos); }
445
+ boundedMap.requireAbsentMember(seen, keyId, _throwDuplicateKey);
446
+ seen.add(keyId);
424
447
  var val = _decodeItem(state, depth + 1);
425
448
  m.set(key, val);
426
449
  }
@@ -376,9 +376,13 @@ function parse(headerValue) {
376
376
  }
377
377
  continue;
378
378
  }
379
- var n = Number(val);
380
- if (isFinite(n) && n >= 0) {
381
- out[_camel(key)] = Math.floor(n);
379
+ // RFC 9111 delta-seconds is 1*DIGIT — Number() would also accept hex
380
+ // ("0x10"), exponential ("1e3"), and surrounding whitespace, which are
381
+ // not valid cache-directive values. Round-trip parseInt to require pure
382
+ // decimal digits.
383
+ var n = parseInt(val, 10);
384
+ if (Number.isFinite(n) && n >= 0 && String(n) === val) {
385
+ out[_camel(key)] = n;
382
386
  }
383
387
  continue;
384
388
  }
package/lib/cert.js CHANGED
@@ -125,7 +125,9 @@ function _createSealedDiskStorage(opts) {
125
125
  async readSealed(relPath) {
126
126
  var p = nodePath.join(rootDir, relPath + ".sealed");
127
127
  if (!nodeFs.existsSync(p)) return null;
128
- var sealed = nodeFs.readFileSync(p);
128
+ // Cap + fd-bound (sealed cert/key envelope is well under 256 KiB). NO
129
+ // refuseSymlink: the sealed store may be operator-mounted.
130
+ var sealed = atomicFile.fdSafeReadSync(p, { maxBytes: C.BYTES.kib(256) });
129
131
  var plain = vaultStore.unseal(sealed);
130
132
  return Buffer.isBuffer(plain) ? plain : Buffer.from(plain);
131
133
  },
@@ -139,7 +141,7 @@ function _createSealedDiskStorage(opts) {
139
141
  async readMeta(certName) {
140
142
  var p = nodePath.join(_certDir(certName), "meta.json");
141
143
  if (!nodeFs.existsSync(p)) return null;
142
- try { return safeJson.parse(nodeFs.readFileSync(p, "utf8"), { maxBytes: C.BYTES.kib(16) }); }
144
+ try { return safeJson.parse(atomicFile.fdSafeReadSync(p, { maxBytes: C.BYTES.kib(16), encoding: "utf8" }), { maxBytes: C.BYTES.kib(16) }); }
143
145
  catch (e) {
144
146
  // meta.json is a derived index (expiry + fingerprint), not a
145
147
  // source of truth — the sealed cert is. A corrupt meta must not
@@ -429,7 +431,7 @@ function create(opts) {
429
431
  function _loadOrGenerateAccountKey() {
430
432
  // Read sealed account JWK; generate + persist if absent.
431
433
  var sealedBuf = nodeFs.existsSync(nodePath.join(storage.rootDir, "account/jwk.json.sealed"))
432
- ? nodeFs.readFileSync(nodePath.join(storage.rootDir, "account/jwk.json.sealed"))
434
+ ? atomicFile.fdSafeReadSync(nodePath.join(storage.rootDir, "account/jwk.json.sealed"), { maxBytes: C.BYTES.kib(64) })
433
435
  : null;
434
436
  if (sealedBuf) {
435
437
  var jwk;
package/lib/cli.js CHANGED
@@ -39,6 +39,7 @@ var os = require("node:os");
39
39
  var nodePath = require("node:path");
40
40
  var apiSnapshot = require("./api-snapshot");
41
41
  var argParser = require("./arg-parser");
42
+ var atomicFile = require("./atomic-file");
42
43
  var auditChain = require("./audit-chain");
43
44
  var auditTools = require("./audit-tools");
44
45
  var backup = require("./backup");
@@ -1484,7 +1485,10 @@ async function _runMtls(args, ctx) {
1484
1485
  validityDays: daysP,
1485
1486
  });
1486
1487
  if (outPath) {
1487
- nodeFs.writeFileSync(outPath, p12.p12, { mode: 0o600 });
1488
+ // Atomic, symlink-refusing write a bare writeFileSync follows a
1489
+ // symlink an attacker pre-planted at the operator-supplied --out
1490
+ // path (CWE-59) and could expose the client key bundle through it.
1491
+ atomicFile.writeSync(outPath, p12.p12, { fileMode: 0o600 });
1488
1492
  report.write("p12 written: " + outPath);
1489
1493
  } else {
1490
1494
  // No --out: stream the bytes to stdout for piping. Operators
@@ -35,6 +35,7 @@
35
35
  */
36
36
 
37
37
  var nodeCrypto = require("node:crypto");
38
+ var numericBounds = require("./numeric-bounds");
38
39
  var validateOpts = require("./validate-opts");
39
40
  var rfc3339 = require("./rfc3339");
40
41
  var safeJson = require("./safe-json");
@@ -265,7 +266,7 @@ function parse(envelope) {
265
266
  for (var j = 0; j < keys.length; j += 1) {
266
267
  var key = keys[j];
267
268
  if (REQUIRED_ATTRS.indexOf(key) !== -1) continue;
268
- if (KNOWN_OPTIONAL_ATTRS[key]) continue;
269
+ if (Object.prototype.hasOwnProperty.call(KNOWN_OPTIONAL_ATTRS, key)) continue;
269
270
  extensions[key] = envelope[key];
270
271
  }
271
272
 
@@ -309,7 +310,7 @@ function _extIssue(name, v) {
309
310
  if (v === null) return null;
310
311
  if (typeof v === "string" || typeof v === "boolean") return null;
311
312
  if (typeof v === "number") {
312
- if (!isFinite(v) || Math.floor(v) !== v) return "extension '" + name + "' must be an integer (CloudEvents has no float type)";
313
+ if (!numericBounds.isFiniteInt(v)) return "extension '" + name + "' must be an integer (CloudEvents has no float type)";
313
314
  if (v < INT_MIN || v > INT_MAX) return "extension '" + name + "' integer out of 32-bit range";
314
315
  return null;
315
316
  }
@@ -327,9 +327,13 @@ var _activeTx = null;
327
327
  // synchronous this runs atomically to completion with no interleaving.
328
328
  function _localExec(sql, params) {
329
329
  var stmt = _localDb().prepare(sql);
330
- // Heuristic: if the statement returns rows (SELECT or has RETURNING),
331
- // use .all(); otherwise .run() and report changes as rowCount.
332
- if (/^\s*SELECT\b/i.test(sql) || /\bRETURNING\b/i.test(sql)) {
330
+ // Choose .all() vs .run() by whether the statement returns rows. A leading-
331
+ // keyword check (`^\s*SELECT`) mis-routes a WITH (CTE) read "WITH c AS (...)
332
+ // SELECT ..." does not start with SELECT — to .run(), which on node:sqlite
333
+ // silently returns only a changes count and DROPS the rows. The shared
334
+ // CTE/EXPLAIN-aware classifier resolves the effective verb (and a top-level
335
+ // RETURNING) so every row-returning statement reaches the caller via .all().
336
+ if (externalDb._statementReturnsRows(sql)) {
333
337
  var rows = stmt.all.apply(stmt, params || []);
334
338
  return { rows: rows, rowCount: rows.length };
335
339
  }
@@ -356,9 +356,26 @@ function firstControlCharOffset(s, opts) {
356
356
  return -1;
357
357
  }
358
358
 
359
+ // Decode HTML numeric character references (hex &#x..; and decimal &#..;) just
360
+ // enough to expose a scheme hidden behind entity-encoding. The trailing
361
+ // semicolon is OPTIONAL — a browser decodes `&#106avascript:` (no semicolon)
362
+ // the same as `&#106;avascript:`, so a semicolon-required decoder lets the
363
+ // no-semicolon form bypass a scheme allowlist. Shared so guard-html / guard-svg
364
+ // / guard-markdown cannot drift on this (the bug class that shipped one buggy
365
+ // + one correct copy).
366
+ var NUMERIC_ENTITY_RE_G = /&#(?:x([0-9a-f]+)|(\d+));?/gi;
367
+ function decodeNumericEntities(s) {
368
+ return String(s == null ? "" : s).replace(NUMERIC_ENTITY_RE_G, function (m, hex, dec) {
369
+ var cp = hex !== undefined ? parseInt(hex, 16) : parseInt(dec, 10);
370
+ if (!isFinite(cp) || cp < 0 || cp > 0x10FFFF) return m;
371
+ try { return String.fromCodePoint(cp); } catch (_e) { return m; }
372
+ });
373
+ }
374
+
359
375
  module.exports = {
360
376
  isForbiddenControlChar: isForbiddenControlChar,
361
377
  firstControlCharOffset: firstControlCharOffset,
378
+ decodeNumericEntities: decodeNumericEntities,
362
379
  isAsciiAlnum: isAsciiAlnum,
363
380
  isUnreserved: isUnreserved,
364
381
  hex4: hex4,
@@ -82,7 +82,7 @@ function create(opts) {
82
82
  throw new ComplianceEaaError("compliance-eaa/bad-decl",
83
83
  "compliance.eaa.declareCriterion: decl must be an object with { conformance, note? }");
84
84
  }
85
- if (!VALID_CONFORMANCE[decl.conformance]) {
85
+ if (!Object.prototype.hasOwnProperty.call(VALID_CONFORMANCE, decl.conformance)) {
86
86
  throw new ComplianceEaaError("compliance-eaa/bad-conformance",
87
87
  "compliance.eaa.declareCriterion: conformance must be one of " + Object.keys(VALID_CONFORMANCE).join(", "));
88
88
  }
@@ -368,13 +368,14 @@ function create(opts) {
368
368
 
369
369
  for (var c = 0; c < index.length; c++) {
370
370
  var candidate = index[c];
371
- // Type filter: when input.type is set, skip candidates of
372
- // the wrong type unless candidate is an entity (entities can
373
- // be matched regardless to catch operator-side type errors).
374
- if (input.type && candidate.type !== input.type &&
375
- candidate.type !== "entity") {
376
- continue;
377
- }
371
+ // A sanctions screen MUST over-match on the legally-operative NAME. The
372
+ // operator-asserted input.type is an unverified counterparty
373
+ // self-classification, NOT data-driven non-match confidence using it to
374
+ // EXCLUDE a name hit inverts the screen to under-match (a false negative
375
+ // that processes a payment which should have been blocked). So never skip
376
+ // a candidate on type; the name match runs against every record and the
377
+ // type mismatch is surfaced as a non-dispositive `typeMatch` signal on
378
+ // each hit for operator triage.
378
379
 
379
380
  var bestForCandidate = { score: 0, name: "" };
380
381
  for (var qi = 0; qi < queryNames.length; qi++) {
@@ -404,6 +405,7 @@ function create(opts) {
404
405
  listed: candidate.listedAt,
405
406
  programs: candidate.programs,
406
407
  type: candidate.type,
408
+ typeMatch: input.type ? candidate.type === input.type : null,
407
409
  country: candidate.country,
408
410
  });
409
411
  }
package/lib/compliance.js CHANGED
@@ -953,7 +953,7 @@ var REGIME_MAP = Object.freeze({
953
953
  * b.compliance.describe("not-a-real-posture"); // → null
954
954
  */
955
955
  function describe(posture) {
956
- return REGIME_MAP[posture] || null;
956
+ return Object.prototype.hasOwnProperty.call(REGIME_MAP, posture) ? REGIME_MAP[posture] : null;
957
957
  }
958
958
 
959
959
  // POSTURE_DEFAULTS — per-posture configuration knobs that primitives
@@ -35,7 +35,6 @@
35
35
  * @card
36
36
  * Monitor + alert when runtime config diverges from a declared baseline.
37
37
  */
38
- var nodeFs = require("node:fs");
39
38
  var nodePath = require("node:path");
40
39
  var auditSign = require("./audit-sign");
41
40
  var canonicalJson = require("./canonical-json");
@@ -45,6 +44,7 @@ var safeJson = require("./safe-json");
45
44
  var validateOpts = require("./validate-opts");
46
45
  var { defineClass } = require("./framework-error");
47
46
  var atomicFile = require("./atomic-file");
47
+ var C = require("./constants");
48
48
 
49
49
  var audit = lazyRequire(function () { return require("./audit"); });
50
50
  var auditEmit = require("./audit-emit");
@@ -163,9 +163,13 @@ function create(opts) {
163
163
  var _emit = auditEmit.gatedReasonEmitter({ audit: auditOn, sink: auditInstance });
164
164
 
165
165
  function _readSidecar() {
166
- if (!nodeFs.existsSync(sidecarPath)) return null;
166
+ // Capped fd-bound read (no existsSync check-then-read window): the signed
167
+ // config-baseline sidecar is parsed + verified, so a tampered multi-GB file
168
+ // would OOM the reader before signature verify. refuseSymlink stays OFF: the
169
+ // data dir may be a symlink-mounted volume (k8s PVC). Any read failure
170
+ // (missing / too-large) → null, the existing "no baseline yet" behavior.
167
171
  var raw;
168
- try { raw = nodeFs.readFileSync(sidecarPath, "utf8"); }
172
+ try { raw = atomicFile.fdSafeReadSync(sidecarPath, { maxBytes: C.BYTES.mib(1), encoding: "utf8" }); }
169
173
  catch (_e) { return null; }
170
174
  var parsed;
171
175
  try { parsed = safeJson.parse(raw); }
@@ -190,9 +194,12 @@ function create(opts) {
190
194
  publicKeyPem: auditSign.getPublicKey(),
191
195
  snapshot: snapshot,
192
196
  };
193
- var tmp = sidecarPath + ".tmp";
194
- nodeFs.writeFileSync(tmp, JSON.stringify(payload, null, 2));
195
- atomicFile.renameWithRetry(tmp, sidecarPath);
197
+ // Atomic, symlink-refusing write. The previous hand-rolled form staged
198
+ // into a PREDICTABLE temp name (`sidecarPath + ".tmp"`) via a plain
199
+ // writeFileSync, so an attacker could pre-plant a symlink at that exact
200
+ // path and have the signed sidecar written through it (CWE-59 / CWE-377).
201
+ // writeSync uses a CSPRNG temp name opened O_EXCL | O_NOFOLLOW.
202
+ atomicFile.writeSync(sidecarPath, JSON.stringify(payload, null, 2), { fileMode: 0o600 });
196
203
  }
197
204
 
198
205
  function _verifySidecar(parsed) {
@@ -367,7 +374,10 @@ function verifyVendorIntegrity(opts) {
367
374
  var libVendorDir = opts.libVendorDir || nodePath.join(__dirname, "vendor");
368
375
  var manifestPath = opts.manifestPath || nodePath.join(libVendorDir, "MANIFEST.json");
369
376
  var raw;
370
- try { raw = nodeFs.readFileSync(manifestPath, "utf8"); }
377
+ // Capped fd-bound read of the vendor MANIFEST.json (operator-bundled, install-
378
+ // time). refuseSymlink OFF — the vendored tree ships read-only and an install
379
+ // may symlink lib/. The cap precedes the alloc.
380
+ try { raw = atomicFile.fdSafeReadSync(manifestPath, { maxBytes: C.BYTES.mib(4), encoding: "utf8" }); }
371
381
  catch (_e) {
372
382
  throw _err("VENDOR_MANIFEST_MISSING",
373
383
  "vendor MANIFEST.json missing at " + manifestPath, true);
@@ -397,7 +407,11 @@ function verifyVendorIntegrity(opts) {
397
407
  var abs = nodePath.isAbsolute(rel) ? rel : nodePath.join(libVendorDir, relInVendor);
398
408
  var actual;
399
409
  try {
400
- var bytes = nodeFs.readFileSync(abs);
410
+ // Capped fd-bound read (raw bytes hashing, no encoding). Sanity ceiling
411
+ // so a corrupted/huge vendored file is a read-failed mismatch (caught
412
+ // below) rather than an OOM in the boot integrity loop. NO refuseSymlink
413
+ // (vendored tree ships read-only; installs may symlink lib/).
414
+ var bytes = atomicFile.fdSafeReadSync(abs, { maxBytes: C.BYTES.mib(64) });
401
415
  actual = "sha256:" + require("node:crypto")
402
416
  .createHash("sha256").update(bytes).digest("hex");
403
417
  } catch (_e) {
@@ -31,6 +31,7 @@
31
31
  var nodeCrypto = require("node:crypto");
32
32
  var C = require("./constants");
33
33
  var bCrypto = require("./crypto");
34
+ var x509Chain = require("./x509-chain");
34
35
  var canonicalJson = require("./canonical-json");
35
36
  var safeJson = require("./safe-json");
36
37
  var validateOpts = require("./validate-opts");
@@ -1103,7 +1104,7 @@ function attachIdentityAssertion(opts) {
1103
1104
  validateOpts.requireObject(opts, "contentCredentials.attachIdentityAssertion", ContentCredentialsError);
1104
1105
  validateOpts(opts, ["binding", "subject", "referencedAssertions", "privateKeyPem", "audit"],
1105
1106
  "contentCredentials.attachIdentityAssertion");
1106
- if (typeof opts.binding !== "string" || !IDENTITY_BINDINGS[opts.binding]) {
1107
+ if (typeof opts.binding !== "string" || !Object.prototype.hasOwnProperty.call(IDENTITY_BINDINGS, opts.binding)) {
1107
1108
  throw ContentCredentialsError.factory("content-credentials/bad-identity-binding",
1108
1109
  "attachIdentityAssertion: binding must be one of " + Object.keys(IDENTITY_BINDINGS).join(" / "));
1109
1110
  }
@@ -1217,7 +1218,7 @@ function verifyIdentityAssertion(assertion, publicKeyPem, opts) {
1217
1218
  return _fail("public-key-required");
1218
1219
  }
1219
1220
  var sp = assertion.signer_payload;
1220
- if (!sp || typeof sp !== "object" || typeof sp.binding !== "string" || !IDENTITY_BINDINGS[sp.binding] ||
1221
+ if (!sp || typeof sp !== "object" || typeof sp.binding !== "string" || !Object.prototype.hasOwnProperty.call(IDENTITY_BINDINGS, sp.binding) ||
1221
1222
  !Array.isArray(sp.referenced_assertions)) {
1222
1223
  return _fail("signer-payload-shape");
1223
1224
  }
@@ -1334,9 +1335,10 @@ function _verifyIdentityX509Chain(certChainPem, trustAnchorsPem) {
1334
1335
  // only direct-root / self-signed leaves.
1335
1336
  for (var li = 0; li < certs.length - 1; li += 1) {
1336
1337
  var child = certs[li], parent = certs[li + 1];
1337
- var linked = false;
1338
- try { linked = child.checkIssued(parent) && child.verify(parent.publicKey); }
1339
- catch (_eLink) { linked = false; }
1338
+ // issuerValidlyIssued enforces basicConstraints cA:TRUE on the parent in
1339
+ // addition to the issuance + signature linkage — a non-CA cert cannot be
1340
+ // an intermediate issuer (basicConstraints bypass, CVE-2002-0862 class).
1341
+ var linked = x509Chain.issuerValidlyIssued(parent, child);
1340
1342
  if (!linked) { return { ok: false, reason: "broken-chain" }; }
1341
1343
  }
1342
1344
  // The top of the presented chain must chain to (or BE) a trust anchor.
@@ -1347,8 +1349,9 @@ function _verifyIdentityX509Chain(certChainPem, trustAnchorsPem) {
1347
1349
  if (top.fingerprint256 === anchor.fingerprint256) {
1348
1350
  chained = true; // top of the chain IS the anchor (root-in-chain or self-signed leaf == anchor)
1349
1351
  } else {
1350
- try { chained = top.checkIssued(anchor) && top.verify(anchor.publicKey); }
1351
- catch (_eChk) { chained = false; }
1352
+ // issuerValidlyIssued enforces basicConstraints cA:TRUE on the anchor
1353
+ // in addition to issuance + signature (basicConstraints bypass class).
1354
+ chained = x509Chain.issuerValidlyIssued(anchor, top);
1352
1355
  }
1353
1356
  if (chained) {
1354
1357
  if (now < anchor.validFromDate.getTime() || now > anchor.validToDate.getTime()) {
@@ -1427,7 +1430,7 @@ function cacImplicitLabel(opts) {
1427
1430
  throw new ContentCredentialsError("cac-implicit-label/bad-content-id",
1428
1431
  "cacImplicitLabel: contentId must match [A-Za-z0-9._:/-]");
1429
1432
  }
1430
- if (typeof opts.contentKind !== "string" || !CAC_KIND_ENUM[opts.contentKind]) {
1433
+ if (typeof opts.contentKind !== "string" || !Object.prototype.hasOwnProperty.call(CAC_KIND_ENUM, opts.contentKind)) {
1431
1434
  throw new ContentCredentialsError("cac-implicit-label/bad-content-kind",
1432
1435
  "cacImplicitLabel: contentKind must be one of " +
1433
1436
  Object.keys(CAC_KIND_ENUM).join("/"));
@@ -99,9 +99,12 @@ function create(body, opts) {
99
99
  if (!Array.isArray(algos) || algos.length === 0) throw new ContentDigestError("content-digest/bad-arg", "contentDigest.create: opts.algorithms must be a non-empty array");
100
100
  var members = algos.map(function (a) {
101
101
  var name = String(a).toLowerCase();
102
- var nodeAlg = ACTIVE[name];
102
+ // hasOwnProperty: the algorithm name is operator/caller input; a bracket
103
+ // lookup lets "constructor"/"toString" inherit a truthy value off the
104
+ // prototype and pass the support check (proto shadowing).
105
+ var nodeAlg = Object.prototype.hasOwnProperty.call(ACTIVE, name) ? ACTIVE[name] : undefined;
103
106
  if (!nodeAlg) {
104
- if (DEPRECATED[name]) throw new ContentDigestError("content-digest/insecure-algorithm", "contentDigest.create: '" + name + "' is a deprecated/insecure digest algorithm (RFC 9530 §6); use sha-256 or sha-512");
107
+ if (Object.prototype.hasOwnProperty.call(DEPRECATED, name)) throw new ContentDigestError("content-digest/insecure-algorithm", "contentDigest.create: '" + name + "' is a deprecated/insecure digest algorithm (RFC 9530 §6); use sha-256 or sha-512");
105
108
  throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.create: unsupported digest algorithm '" + name + "'");
106
109
  }
107
110
  var digest = nodeCrypto.createHash(nodeAlg).update(bytes).digest("base64");
@@ -154,8 +157,12 @@ function verify(fieldValue, body, opts) {
154
157
  var kvp = structuredFields.parseKeyValuePiece(m);
155
158
  var name = kvp.key;
156
159
  var raw = kvp.value.trim();
160
+ // hasOwnProperty: `name` is the algorithm token from the untrusted inbound
161
+ // Content-Digest header; a bracket lookup lets "constructor"/"__proto__"
162
+ // inherit a truthy value off the prototype and reach createHash (proto
163
+ // shadowing).
164
+ if (!Object.prototype.hasOwnProperty.call(ACTIVE, name)) continue; // ignore legacy / unknown entries
157
165
  var nodeAlg = ACTIVE[name];
158
- if (!nodeAlg) continue; // ignore legacy / unknown entries
159
166
  if (raw.length < 2 || raw.charAt(0) !== ":" || raw.charAt(raw.length - 1) !== ":") {
160
167
  throw new ContentDigestError("content-digest/bad-field", "contentDigest.verify: '" + name + "' value is not an RFC 8941 byte sequence (:base64:)");
161
168
  }
@@ -172,7 +179,7 @@ function verify(fieldValue, body, opts) {
172
179
  if (!Array.isArray(opts.required)) throw new ContentDigestError("content-digest/bad-arg", "contentDigest.verify: opts.required must be an array");
173
180
  for (var r = 0; r < opts.required.length; r++) {
174
181
  var req = String(opts.required[r]).toLowerCase();
175
- if (!ACTIVE[req]) throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.verify: required algorithm '" + req + "' is not a modern digest");
182
+ if (!Object.prototype.hasOwnProperty.call(ACTIVE, req)) throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.verify: required algorithm '" + req + "' is not a modern digest");
176
183
  if (!seen[req]) throw new ContentDigestError("content-digest/missing-algorithm", "contentDigest.verify: required digest '" + req + "' is not present");
177
184
  }
178
185
  }
package/lib/cookies.js CHANGED
@@ -96,7 +96,11 @@ function _validateValue(value) {
96
96
  // Length cap before the regex test (defense in depth — the regex
97
97
  // here is a simple character class, but the same discipline that
98
98
  // bounds longer regexes elsewhere applies).
99
- if (value.length > MAX_VALUE_LENGTH || FORBIDDEN_VALUE_RE.test(value)) {
99
+ // Byte length, not value.length (UTF-16 code units): the cap bounds the
100
+ // on-the-wire Set-Cookie size, and a multibyte value is 2-4x larger in bytes
101
+ // than chars, so a char-count check under-enforces and a 4096-char value can
102
+ // emit a ~12 KiB+ header.
103
+ if (Buffer.byteLength(value, "utf8") > MAX_VALUE_LENGTH || FORBIDDEN_VALUE_RE.test(value)) {
100
104
  throw new CookieError("cookies/invalid-value",
101
105
  "cookie value is too long or contains forbidden control character (CRLF/NUL/;/,)");
102
106
  }
@@ -108,7 +112,11 @@ function _validateValue(value) {
108
112
  // header — never trust unscrubbed values reach the wire.
109
113
  function _scrubAttr(s) {
110
114
  if (typeof s !== "string") return s;
111
- return s.replace(/[\r\n\0]/g, ""); // allow:duplicate-regex CR/LF/NUL header-injection rejection appears in cookies / mail / security-headers; each is the boundary primitive for its domain
115
+ // `;` joins Set-Cookie attributes, so an unscrubbed `;` in Path/SameSite is
116
+ // attribute injection (e.g. Path=/x; HttpOnly). Domain is regex-validated
117
+ // upstream; Path/SameSite only flow through here, so strip `;` alongside the
118
+ // CR/LF/NUL header-injection set.
119
+ return s.replace(/[\r\n\0;]/g, ""); // allow:duplicate-regex — CR/LF/NUL(+;) header-injection rejection appears in cookies / mail / security-headers; each is the boundary primitive for its domain
112
120
 
113
121
  }
114
122
 
package/lib/cose.js CHANGED
@@ -110,6 +110,24 @@ function _algParamsFor(algId) {
110
110
  }
111
111
  }
112
112
 
113
+ // RFC 9053 §2 binds each ECDSA alg to ONE curve. node:crypto will happily
114
+ // verify a sha512 (ES512-shape) signature against a P-256 key as
115
+ // self-consistent, so without this check a COSE_Sign1 declaring ES512 but
116
+ // carrying a P-256 key passes when ES512 is allowlisted — an alg/curve
117
+ // confusion (the COSE sibling of jwt-external._assertAlgKtyMatch). EdDSA /
118
+ // ML-DSA carry their parameters in the KeyObject and need no binding.
119
+ var _ES_ALG_CURVE = { ES256: "prime256v1", ES384: "secp384r1", ES512: "secp521r1" };
120
+ function _assertEcAlgCurve(algName, key) {
121
+ var want = _ES_ALG_CURVE[algName];
122
+ if (want === undefined) return; // not an ECDSA alg — nothing to bind
123
+ var got = (key && key.asymmetricKeyDetails && key.asymmetricKeyDetails.namedCurve) || null;
124
+ if (key.asymmetricKeyType !== "ec" || got !== want) {
125
+ throw new CoseError("cose/alg-curve-mismatch",
126
+ "cose: alg '" + algName + "' requires an EC key on " + want +
127
+ ", got " + (key.asymmetricKeyType === "ec" ? got : key.asymmetricKeyType));
128
+ }
129
+ }
130
+
113
131
  function _bstr(x) {
114
132
  return safeBuffer.toBuffer(x, {
115
133
  errorFactory: function (code, msg) { return new CoseError(code, msg); },
@@ -169,6 +187,7 @@ async function sign(payload, opts) {
169
187
  var algId = ALG_NAME_TO_ID[opts.alg];
170
188
  var params = _algParamsFor(algId);
171
189
  var key = _toKeyObject(opts.privateKey, "private");
190
+ _assertEcAlgCurve(opts.alg, key); // RFC 9053 alg↔curve binding — refuse e.g. ES512 over a P-256 key
172
191
 
173
192
  var protMap = new Map();
174
193
  protMap.set(HDR_ALG, algId);
@@ -352,6 +371,7 @@ async function verify(coseSign1, opts) {
352
371
  var key = opts.publicKey
353
372
  ? _toKeyObject(opts.publicKey, "public")
354
373
  : _toKeyObject(opts.keyResolver(protMap, unprotected), "public");
374
+ _assertEcAlgCurve(algName, key); // RFC 9053 alg↔curve binding
355
375
 
356
376
  var externalAad = opts.externalAad == null ? Buffer.alloc(0) : _bstr(opts.externalAad);
357
377
  var toBeSigned = _toBeSigned(protectedBstr, externalAad, payload);
package/lib/crdt.js CHANGED
@@ -41,6 +41,7 @@
41
41
 
42
42
  var bCrypto = require("./crypto");
43
43
  var safeJson = require("./safe-json");
44
+ var numericBounds = require("./numeric-bounds");
44
45
  var { defineClass } = require("./framework-error");
45
46
 
46
47
  var CrdtError = defineClass("CrdtError", { alwaysPermanent: true });
@@ -52,7 +53,7 @@ function _replicaId(opts) {
52
53
  return id;
53
54
  }
54
55
  function _posInt(n, label) {
55
- if (typeof n !== "number" || !isFinite(n) || n < 0 || Math.floor(n) !== n) throw new CrdtError("crdt/bad-value", "crdt: " + label + " must be a non-negative integer");
56
+ if (!numericBounds.isNonNegativeFiniteInt(n)) throw new CrdtError("crdt/bad-value", "crdt: " + label + " must be a non-negative integer");
56
57
  return n;
57
58
  }
58
59
  function _maxMerge(a, b) {