@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.
- package/CHANGELOG.md +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +38 -6
- package/lib/agent-event-bus.js +13 -0
- package/lib/agent-idempotency.js +5 -1
- package/lib/agent-snapshot.js +32 -2
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +9 -7
- package/lib/archive-tar-read.js +51 -8
- package/lib/archive.js +4 -2
- package/lib/asn1-der.js +70 -22
- package/lib/atomic-file.js +204 -2
- package/lib/audit-chain.js +54 -5
- package/lib/audit-daily-review.js +12 -2
- package/lib/audit-sign.js +2 -1
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +7 -2
- package/lib/auth/access-lock.js +2 -2
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +71 -8
- package/lib/auth/dpop.js +15 -1
- package/lib/auth/fido-mds3.js +30 -13
- package/lib/auth/jwt.js +21 -5
- package/lib/auth/lockout.js +18 -2
- package/lib/auth/oauth.js +8 -2
- package/lib/auth/passkey.js +1 -1
- package/lib/auth/password.js +1 -1
- package/lib/auth/saml.js +42 -12
- package/lib/auth/sd-jwt-vc.js +24 -4
- package/lib/auth/status-list.js +14 -2
- package/lib/auth/step-up.js +9 -1
- package/lib/auth-bot-challenge.js +21 -2
- package/lib/backup/bundle.js +7 -2
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +41 -20
- package/lib/backup/manifest.js +7 -1
- package/lib/break-glass.js +41 -22
- package/lib/cbor.js +34 -11
- package/lib/cdn-cache-control.js +7 -3
- package/lib/cert.js +5 -3
- package/lib/cli.js +5 -1
- package/lib/cloud-events.js +3 -2
- package/lib/cluster-storage.js +7 -3
- package/lib/codepoint-class.js +17 -0
- package/lib/compliance-eaa.js +1 -1
- package/lib/compliance-sanctions.js +9 -7
- package/lib/compliance.js +1 -1
- package/lib/config-drift.js +22 -8
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +11 -4
- package/lib/cookies.js +10 -2
- package/lib/cose.js +20 -0
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +48 -24
- package/lib/crypto.js +18 -4
- package/lib/csp.js +13 -0
- package/lib/daemon.js +4 -1
- package/lib/data-act.js +27 -4
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +102 -11
- package/lib/db.js +32 -15
- package/lib/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +22 -8
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db.js +60 -4
- package/lib/fda-21cfr11.js +30 -5
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- package/lib/gate-contract.js +46 -5
- package/lib/gdpr-ropa.js +18 -9
- package/lib/graphql-federation.js +17 -4
- package/lib/guard-all.js +2 -2
- package/lib/guard-dsn.js +1 -1
- package/lib/guard-envelope.js +1 -1
- package/lib/guard-html.js +9 -11
- package/lib/guard-imap-command.js +1 -1
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +14 -6
- package/lib/guard-mail-move.js +1 -1
- package/lib/guard-managesieve-command.js +1 -1
- package/lib/guard-pop3-command.js +1 -1
- package/lib/guard-smtp-command.js +1 -1
- package/lib/guard-svg.js +8 -9
- package/lib/html-balance.js +7 -3
- package/lib/http-client-cookie-jar.js +33 -12
- package/lib/http-client.js +225 -53
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/incident-report.js +9 -6
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/legal-hold.js +24 -8
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arf.js +1 -1
- package/lib/mail-auth.js +27 -4
- package/lib/mail-bimi.js +16 -16
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-deploy.js +9 -5
- package/lib/mail-dkim.js +20 -7
- package/lib/mail-greylist.js +2 -4
- package/lib/mail-helo.js +2 -4
- package/lib/mail-journal.js +11 -8
- package/lib/mail-mdn.js +8 -4
- package/lib/mail-rbl.js +2 -4
- package/lib/mail-scan.js +3 -5
- package/lib/mail-server-jmap.js +4 -1
- package/lib/mail-server-registry.js +1 -1
- package/lib/mail-server-tls.js +9 -2
- package/lib/mail-spam-score.js +2 -4
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail.js +22 -2
- package/lib/markup-tokenizer.js +24 -0
- package/lib/mcp.js +6 -4
- package/lib/mdoc.js +26 -3
- package/lib/metrics.js +14 -3
- package/lib/middleware/api-encrypt.js +2 -2
- package/lib/middleware/body-parser.js +10 -4
- package/lib/middleware/bot-guard.js +26 -18
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compose-pipeline.js +39 -5
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +60 -21
- package/lib/middleware/daily-byte-quota.js +6 -4
- package/lib/middleware/fetch-metadata.js +28 -4
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +25 -16
- package/lib/middleware/scim-server.js +2 -1
- package/lib/middleware/security-headers.js +24 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +2 -2
- package/lib/money.js +1 -1
- package/lib/mtls-ca.js +10 -6
- package/lib/network-dns-resolver.js +1 -1
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +2 -1
- package/lib/network-smtp-policy.js +23 -5
- package/lib/network-tls.js +27 -5
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +1 -1
- package/lib/nist-crosswalk.js +1 -1
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +9 -0
- package/lib/object-store/azure-blob.js +1 -2
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +6 -4
- package/lib/object-store/http-put.js +1 -2
- package/lib/object-store/http-request.js +30 -1
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4.js +1 -2
- package/lib/observability-otlp-exporter.js +20 -4
- package/lib/outbox.js +11 -4
- package/lib/parsers/safe-xml.js +1 -1
- package/lib/parsers/safe-yaml.js +21 -3
- package/lib/pipl-cn.js +11 -8
- package/lib/queue-local.js +10 -3
- package/lib/redact.js +7 -3
- package/lib/request-helpers.js +347 -36
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +19 -0
- package/lib/retention.js +20 -4
- package/lib/router.js +17 -4
- package/lib/safe-ical.js +2 -2
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +70 -0
- package/lib/safe-sieve.js +1 -1
- package/lib/safe-vcard.js +1 -1
- package/lib/sandbox-worker.js +6 -0
- package/lib/sandbox.js +1 -1
- package/lib/scheduler.js +17 -1
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/session.js +62 -120
- package/lib/sql.js +25 -3
- package/lib/static.js +65 -13
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +52 -19
- package/lib/tsa.js +5 -2
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +4 -1
- package/lib/vc.js +1 -1
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/webhook.js +16 -1
- package/lib/websocket.js +1 -1
- package/lib/worm.js +1 -1
- package/lib/ws-client.js +83 -46
- package/lib/x509-chain.js +91 -0
- package/package.json +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
}
|
package/lib/cdn-cache-control.js
CHANGED
|
@@ -376,9 +376,13 @@ function parse(headerValue) {
|
|
|
376
376
|
}
|
|
377
377
|
continue;
|
|
378
378
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
?
|
|
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
|
-
|
|
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
|
package/lib/cloud-events.js
CHANGED
|
@@ -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
|
|
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 (!
|
|
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
|
}
|
package/lib/cluster-storage.js
CHANGED
|
@@ -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
|
-
//
|
|
331
|
-
//
|
|
332
|
-
|
|
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
|
}
|
package/lib/codepoint-class.js
CHANGED
|
@@ -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 `javascript:` (no semicolon)
|
|
362
|
+
// the same as `javascript:`, 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,
|
package/lib/compliance-eaa.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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]
|
|
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
|
package/lib/config-drift.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
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
|
-
|
|
1351
|
-
|
|
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
|
|
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("/"));
|
package/lib/content-digest.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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) {
|