@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/network-dns.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var dns = require("node:dns");
|
|
4
|
+
var numericBounds = require("./numeric-bounds");
|
|
4
5
|
var net = require("node:net");
|
|
5
6
|
var https = require("node:https");
|
|
6
7
|
var nodeTls = require("node:tls");
|
|
@@ -347,10 +348,16 @@ function _decodeDnsAnswer(buf, qtype) {
|
|
|
347
348
|
for (var a = 0; a < ancount; a++) {
|
|
348
349
|
_skipDnsName(buf, state);
|
|
349
350
|
var off = state.off;
|
|
351
|
+
// Bound the RR fixed header (10 bytes: type+class+ttl+rdlen) and the rdata
|
|
352
|
+
// before indexing — ancount comes from the (untrusted upstream) header, so a
|
|
353
|
+
// truncated reply would otherwise over-read into a raw RangeError. Mirrors
|
|
354
|
+
// the hardened _decodeDnsAnswerRaw guards.
|
|
355
|
+
if (off + 10 > buf.length) throw new DnsError("dns/bad-reply", "answer record truncated");
|
|
350
356
|
var rtype = buf.readUInt16BE(off); off += 2;
|
|
351
357
|
off += 2;
|
|
352
358
|
off += 4;
|
|
353
359
|
var rdlen = buf.readUInt16BE(off); off += 2;
|
|
360
|
+
if (off + rdlen > buf.length) throw new DnsError("dns/bad-reply", "answer rdata truncated");
|
|
354
361
|
if (rtype === qtype && qtype === 1 && rdlen === 4) {
|
|
355
362
|
addrs.push(buf[off] + "." + buf[off + 1] + "." + buf[off + 2] + "." + buf[off + 3]);
|
|
356
363
|
} else if (rtype === qtype && qtype === DNS_QTYPE_AAAA && rdlen === IPV6_ADDR_BYTES) {
|
|
@@ -1851,7 +1858,7 @@ var DS_DIGEST_TYPES = Object.freeze({
|
|
|
1851
1858
|
});
|
|
1852
1859
|
|
|
1853
1860
|
function classifyDnskeyAlgorithm(algorithm) {
|
|
1854
|
-
if (
|
|
1861
|
+
if (!numericBounds.isFiniteInt(algorithm)) {
|
|
1855
1862
|
return null;
|
|
1856
1863
|
}
|
|
1857
1864
|
var row = DNSKEY_ALGORITHMS[algorithm];
|
|
@@ -1874,7 +1881,7 @@ function classifyDnskeyAlgorithm(algorithm) {
|
|
|
1874
1881
|
}
|
|
1875
1882
|
|
|
1876
1883
|
function classifyDsDigestType(digestType) {
|
|
1877
|
-
if (
|
|
1884
|
+
if (!numericBounds.isFiniteInt(digestType)) {
|
|
1878
1885
|
return null;
|
|
1879
1886
|
}
|
|
1880
1887
|
var row = DS_DIGEST_TYPES[digestType];
|
package/lib/network-dnssec.js
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
var nodeCrypto = require("node:crypto");
|
|
48
|
+
var numericBounds = require("./numeric-bounds");
|
|
48
49
|
var bCrypto = require("./crypto");
|
|
49
50
|
var safeBuffer = require("./safe-buffer");
|
|
50
51
|
var validateOpts = require("./validate-opts");
|
|
@@ -515,7 +516,7 @@ function nsec3Hash(name, opts) {
|
|
|
515
516
|
validateOpts(opts, ["salt", "iterations"], "dnssec.nsec3Hash");
|
|
516
517
|
var salt = _bytes(opts.salt, "salt");
|
|
517
518
|
var iters = opts.iterations;
|
|
518
|
-
if (
|
|
519
|
+
if (!numericBounds.isNonNegativeFiniteInt(iters)) {
|
|
519
520
|
throw new DnssecError("dnssec/bad-iterations", "dnssec.nsec3Hash: iterations must be a non-negative integer");
|
|
520
521
|
}
|
|
521
522
|
return _nsec3HashWire(_canonicalName(name), salt, iters);
|
|
@@ -176,6 +176,15 @@ async function mtaStsFetch(domain, opts) {
|
|
|
176
176
|
rejectUnauthorized: true,
|
|
177
177
|
});
|
|
178
178
|
} catch (_e) {
|
|
179
|
+
// RFC 8461 opportunistic fallback: a policy that can't be fetched — a
|
|
180
|
+
// network error, OR (with rejectUnauthorized:true + pinned servername
|
|
181
|
+
// above) a TLS-validation failure that signals a possible MITM of the
|
|
182
|
+
// policy endpoint — yields "no enforceable policy", so the sender
|
|
183
|
+
// proceeds without MTA-STS. This is the documented TOFU limitation for an
|
|
184
|
+
// UN-cached first fetch only: a valid cached policy is honored by the
|
|
185
|
+
// _getStsCache().wrap() wrapper and never reaches this catch, and the TLS
|
|
186
|
+
// failure itself is independently audited by httpClient's TLS layer — so
|
|
187
|
+
// this is a deliberate opportunistic fallback, not a silent swallow.
|
|
179
188
|
return null;
|
|
180
189
|
}
|
|
181
190
|
if (res.statusCode === 404) return null; // HTTP 404
|
|
@@ -403,23 +412,32 @@ function _selectorBytes(certDer, selector) {
|
|
|
403
412
|
return null;
|
|
404
413
|
}
|
|
405
414
|
|
|
415
|
+
// Constant-time byte-compare for TLSA association data. The TLSA record is
|
|
416
|
+
// public DNS so there is no secret to leak via timing, but the framework
|
|
417
|
+
// compares all crypto material (digests, certs) in constant time so the
|
|
418
|
+
// boundary stays uniform and can't regress into a leaky compare elsewhere.
|
|
419
|
+
function _constEqBytes(a, b) {
|
|
420
|
+
// bCrypto.timingSafeEqual is the length-tolerant constant-time wrapper.
|
|
421
|
+
return Buffer.isBuffer(a) && Buffer.isBuffer(b) && bCrypto.timingSafeEqual(a, b);
|
|
422
|
+
}
|
|
423
|
+
|
|
406
424
|
function _matchTlsaAgainstCert(rec, certDer) {
|
|
407
425
|
// Returns null on no-match, or { ok: true, mtypeLabel } on match.
|
|
408
426
|
var bytes = _selectorBytes(certDer, rec.selector);
|
|
409
427
|
if (!bytes) return null;
|
|
410
|
-
var dataHex =
|
|
428
|
+
var dataHex = (typeof rec.dataHex === "string" ? rec.dataHex : "").toLowerCase();
|
|
429
|
+
var want = Buffer.from(dataHex, "hex");
|
|
411
430
|
// RFC 6698 §2.1.3 matching types — Full byte match (0) or hashed
|
|
412
431
|
// comparison via SHA two-family (1 short / 2 long digest).
|
|
413
432
|
if (rec.mtype === 0) {
|
|
414
|
-
return bytes
|
|
415
|
-
? { ok: true, mtype: "Full" } : null;
|
|
433
|
+
return _constEqBytes(bytes, want) ? { ok: true, mtype: "Full" } : null;
|
|
416
434
|
}
|
|
417
435
|
if (rec.mtype === 1) {
|
|
418
|
-
return
|
|
436
|
+
return _constEqBytes(nodeCrypto.createHash("sha256").update(bytes).digest(), want)
|
|
419
437
|
? { ok: true, mtype: "SHA-256" } : null;
|
|
420
438
|
}
|
|
421
439
|
if (rec.mtype === 2) {
|
|
422
|
-
return
|
|
440
|
+
return _constEqBytes(nodeCrypto.createHash("sha512").update(bytes).digest(), want)
|
|
423
441
|
? { ok: true, mtype: "SHA-512" } : null;
|
|
424
442
|
}
|
|
425
443
|
return null;
|
package/lib/network-tls.js
CHANGED
|
@@ -1093,6 +1093,15 @@ function _verifyOcspSignature(parsed, issuerPem) {
|
|
|
1093
1093
|
// `ocsp.requireStapled` or any other source) plus the issuer cert PEM
|
|
1094
1094
|
// and returns a structured outcome:
|
|
1095
1095
|
// { ok, status, certStatus, thisUpdate, nextUpdate, signatureValid, errors }
|
|
1096
|
+
// Normalize a certificate serial for comparison: lowercase, drop separators
|
|
1097
|
+
// (X509Certificate.serialNumber is uppercase, no colons here but defensive) and
|
|
1098
|
+
// the optional DER leading sign-zero, so a serial from node:crypto and one
|
|
1099
|
+
// parsed from the OCSP certID DER compare equal. Both sides go through this.
|
|
1100
|
+
function _normOcspSerial(s) {
|
|
1101
|
+
var h = String(s || "").toLowerCase().replace(/[^0-9a-f]/g, "");
|
|
1102
|
+
return h.replace(/^0+/, "") || "0";
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1096
1105
|
function evaluateOcspResponse(ocspDer, opts) {
|
|
1097
1106
|
opts = opts || {};
|
|
1098
1107
|
var issuerPem = opts.issuerPem;
|
|
@@ -1120,12 +1129,21 @@ function evaluateOcspResponse(ocspDer, opts) {
|
|
|
1120
1129
|
return { ok: false, status: parsed.status, signatureValid: false,
|
|
1121
1130
|
errors: ["OCSP signature did not verify against the issuer key"] };
|
|
1122
1131
|
}
|
|
1123
|
-
//
|
|
1124
|
-
|
|
1132
|
+
// Bind the response to the serial of the certificate UNDER VALIDATION. The
|
|
1133
|
+
// old code defaulted an absent serialHex to responses[0]'s own serial and
|
|
1134
|
+
// then matched it — so a CA-signed "good" response covering a DIFFERENT cert
|
|
1135
|
+
// of the same issuer (replayed/mis-routed batch reply) was accepted. Require
|
|
1136
|
+
// a bound serial and fail closed without one; compare normalized (lowercase,
|
|
1137
|
+
// strip separators + DER leading sign-zero on both sides).
|
|
1138
|
+
if (!opts.serialHex) {
|
|
1139
|
+
return { ok: false, status: parsed.status, signatureValid: true,
|
|
1140
|
+
errors: ["OCSP evaluation requires opts.serialHex (the serial of the certificate being validated) to bind the response"] };
|
|
1141
|
+
}
|
|
1142
|
+
var wantSerial = _normOcspSerial(opts.serialHex);
|
|
1125
1143
|
var match = null;
|
|
1126
1144
|
for (var i = 0; i < parsed.basic.responses.length; i += 1) {
|
|
1127
1145
|
var r = parsed.basic.responses[i];
|
|
1128
|
-
if (
|
|
1146
|
+
if (_normOcspSerial(r.certIdSerialHex) === wantSerial) { match = r; break; }
|
|
1129
1147
|
}
|
|
1130
1148
|
if (!match) {
|
|
1131
1149
|
return { ok: false, status: parsed.status, signatureValid: true,
|
|
@@ -1418,7 +1436,9 @@ async function fetchOcspResponse(opts) {
|
|
|
1418
1436
|
}
|
|
1419
1437
|
var evald = evaluateOcspResponse(res.body, {
|
|
1420
1438
|
issuerPem: opts.issuerPem,
|
|
1421
|
-
|
|
1439
|
+
// Bind to the leaf being checked (its serial), not whatever the response
|
|
1440
|
+
// happens to carry — defaults to leafX.serialNumber when not overridden.
|
|
1441
|
+
serialHex: opts.serialHex || leafX.serialNumber,
|
|
1422
1442
|
expectedNonce: opts.nonce === false ? null : built.nonce,
|
|
1423
1443
|
});
|
|
1424
1444
|
if (!evald.ok) {
|
|
@@ -1458,7 +1478,9 @@ var ocsp = Object.freeze({
|
|
|
1458
1478
|
}
|
|
1459
1479
|
var evald = evaluateOcspResponse(rv.ocspBytes, {
|
|
1460
1480
|
issuerPem: opts.issuerPem,
|
|
1461
|
-
|
|
1481
|
+
// Bind to the connected peer's certificate serial (not the response's own
|
|
1482
|
+
// entry) — without it evaluateOcspResponse fails closed.
|
|
1483
|
+
serialHex: opts.serialHex || (rv.peerCert && rv.peerCert.serialNumber) || null,
|
|
1462
1484
|
});
|
|
1463
1485
|
if (!evald.ok) {
|
|
1464
1486
|
throw new TlsTrustError("tls/ocsp-not-good",
|
package/lib/network-tsig.js
CHANGED
|
@@ -61,8 +61,8 @@ var ERROR = { NOERROR: 0, BADSIG: 16, BADKEY: 17, BADTIME: 18, BADTRUNC: 22 };
|
|
|
61
61
|
|
|
62
62
|
function _normAlg(name, allowLegacy) {
|
|
63
63
|
var key = String(name || "hmac-sha256").toLowerCase().replace(/\.$/, "");
|
|
64
|
-
if (ALGORITHMS
|
|
65
|
-
if (LEGACY_ALGORITHMS
|
|
64
|
+
if (Object.prototype.hasOwnProperty.call(ALGORITHMS, key)) return { name: key, hash: ALGORITHMS[key] };
|
|
65
|
+
if (Object.prototype.hasOwnProperty.call(LEGACY_ALGORITHMS, key)) {
|
|
66
66
|
if (!allowLegacy) throw new TsigError("tsig/legacy-algorithm", "tsig: algorithm '" + key + "' is broken; pass allowLegacy:true to permit it for legacy interop");
|
|
67
67
|
return { name: key, hash: LEGACY_ALGORITHMS[key] };
|
|
68
68
|
}
|
package/lib/nis2-report.js
CHANGED
|
@@ -54,7 +54,7 @@ function create(opts) {
|
|
|
54
54
|
validateOpts.requireNonEmptyString(opts.entityId,
|
|
55
55
|
"nis2.report.create: opts.entityId is required (NIS2 registration ID)",
|
|
56
56
|
Nis2ReportError, "nis2-report/bad-entity-id");
|
|
57
|
-
if (!VALID_ENTITY_TYPES
|
|
57
|
+
if (!Object.prototype.hasOwnProperty.call(VALID_ENTITY_TYPES, opts.entityType)) {
|
|
58
58
|
throw new Nis2ReportError("nis2-report/bad-entity-type",
|
|
59
59
|
"nis2.report.create: opts.entityType must be 'essential' or 'important' (NIS2 Article 3 classification)");
|
|
60
60
|
}
|
package/lib/nist-crosswalk.js
CHANGED
|
@@ -199,7 +199,7 @@ var CATALOGS = {
|
|
|
199
199
|
* // → ["b.permissions", "b.middleware.requireAuth", ...]
|
|
200
200
|
*/
|
|
201
201
|
function controls(catalog) {
|
|
202
|
-
if (typeof catalog !== "string" || !CATALOGS
|
|
202
|
+
if (typeof catalog !== "string" || !Object.prototype.hasOwnProperty.call(CATALOGS, catalog)) {
|
|
203
203
|
throw new NistCrosswalkError("nist-crosswalk/unknown-catalog",
|
|
204
204
|
"controls: unknown catalog '" + catalog + "'. Known: " +
|
|
205
205
|
Object.keys(CATALOGS).join(", "));
|
package/lib/ntp-check.js
CHANGED
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
* Boot-time clock-drift verification against an external NTP / NTS-KE reference.
|
|
46
46
|
*/
|
|
47
47
|
var dgram = require("node:dgram");
|
|
48
|
+
var nodeCrypto = require("node:crypto");
|
|
49
|
+
var bCrypto = require("./crypto");
|
|
48
50
|
var C = require("./constants");
|
|
49
51
|
var lazyRequire = require("./lazy-require");
|
|
50
52
|
var safeAsync = require("./safe-async");
|
|
@@ -200,6 +202,14 @@ function querySingle(server, opts) {
|
|
|
200
202
|
// LI=0 (no warning), VN=4, Mode=3 (client). Other bytes zero.
|
|
201
203
|
var req = Buffer.alloc(NTP_PACKET_BYTES);
|
|
202
204
|
req[0] = 0x23;
|
|
205
|
+
// RFC 5905 §8 client-cookie: put a random 64-bit nonce in the request's
|
|
206
|
+
// Transmit Timestamp (bytes 40-47). A conformant server copies it verbatim
|
|
207
|
+
// into the reply's Originate Timestamp (bytes 24-31). Verifying that echo
|
|
208
|
+
// rejects an off-path spoofed reply — without it ANY 48-byte UDP datagram
|
|
209
|
+
// reaching our ephemeral port becomes the authoritative time, letting a
|
|
210
|
+
// spoofer force a fatal-drift refuse-to-boot under BLAMEJS_NTP_STRICT.
|
|
211
|
+
var originCookie = nodeCrypto.randomBytes(8);
|
|
212
|
+
originCookie.copy(req, 40);
|
|
203
213
|
var sendTimeMs = Date.now();
|
|
204
214
|
|
|
205
215
|
socket.on("error", function (e) {
|
|
@@ -213,6 +223,24 @@ function querySingle(server, opts) {
|
|
|
213
223
|
if (!Buffer.isBuffer(msg) || msg.length < NTP_PACKET_BYTES) {
|
|
214
224
|
return done({ code: "ntp/bad-reply", message: "reply too short (" + (msg && msg.length) + " bytes)" });
|
|
215
225
|
}
|
|
226
|
+
// Origin-cookie echo (RFC 5905 §8): the reply's Originate Timestamp
|
|
227
|
+
// (bytes 24-31) MUST equal the nonce we sent. An off-path spoofer can't
|
|
228
|
+
// know it, so this is the primary reply-authenticity check.
|
|
229
|
+
if (!bCrypto.timingSafeEqual(msg.subarray(24, 32), originCookie)) {
|
|
230
|
+
return done({ code: "ntp/origin-mismatch",
|
|
231
|
+
message: server + ": reply Originate Timestamp does not echo the request nonce (spoofed/stale reply)" });
|
|
232
|
+
}
|
|
233
|
+
// Reject a non-server mode, an unsynchronized/kiss-o'-death stratum
|
|
234
|
+
// (0 or >= 16), or LI=3 (alarm — clock not synchronized): such a peer
|
|
235
|
+
// cannot supply a trustworthy time and must not drive drift.
|
|
236
|
+
var mode = msg[0] & 0x07;
|
|
237
|
+
var li = (msg[0] >> 6) & 0x03;
|
|
238
|
+
var stratum = msg[1];
|
|
239
|
+
if (mode !== 4 || li === 3 || stratum === 0 || stratum >= 16) {
|
|
240
|
+
return done({ code: "ntp/unsynchronized",
|
|
241
|
+
message: server + ": reply mode=" + mode + " stratum=" + stratum + " LI=" + li +
|
|
242
|
+
" is not a synchronized server response" });
|
|
243
|
+
}
|
|
216
244
|
// Bytes 40-47 = Transmit Timestamp (NTP epoch seconds.fraction)
|
|
217
245
|
var ntpSeconds = msg.readUInt32BE(40); // NTP packet offset
|
|
218
246
|
var ntpFraction = msg.readUInt32BE(44); // NTP packet offset
|
package/lib/numeric-bounds.js
CHANGED
|
@@ -66,6 +66,14 @@ function isNonNegativeFiniteInt(value) {
|
|
|
66
66
|
Number.isInteger(value) && value >= 0;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// Any-sign finite integer (no sign bound) — for callers that only require
|
|
70
|
+
// integrality (e.g. a CloudEvents 32-bit signed-integer extension, a DNS
|
|
71
|
+
// algorithm/digest-type code) and reject only non-numbers / floats / Infinity /
|
|
72
|
+
// NaN. Distinct from isNonNegativeFiniteInt (which also forbids negatives).
|
|
73
|
+
function isFiniteInt(value) {
|
|
74
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
// requirePositiveFiniteIntIfPresent / requireNonNegativeFiniteIntIfPresent —
|
|
70
78
|
// optional-shape gates that throw via the caller's framework-error class
|
|
71
79
|
// when the value is present but invalid. Replaces the per-file
|
|
@@ -150,6 +158,7 @@ module.exports = {
|
|
|
150
158
|
shape: shape,
|
|
151
159
|
isPositiveFiniteInt: isPositiveFiniteInt,
|
|
152
160
|
isNonNegativeFiniteInt: isNonNegativeFiniteInt,
|
|
161
|
+
isFiniteInt: isFiniteInt,
|
|
153
162
|
requirePositiveFiniteInt: requirePositiveFiniteInt,
|
|
154
163
|
requirePositiveFiniteIntIfPresent: requirePositiveFiniteIntIfPresent,
|
|
155
164
|
requireNonNegativeFiniteIntIfPresent: requireNonNegativeFiniteIntIfPresent,
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
*/
|
|
38
38
|
var nodeCrypto = require("node:crypto");
|
|
39
39
|
var { URL } = require("node:url");
|
|
40
|
-
var { Readable } = require("node:stream");
|
|
41
40
|
var safeXml = require("../parsers/safe-xml");
|
|
42
41
|
var sharedRequest = require("./http-request");
|
|
43
42
|
var sigv4 = require("./sigv4");
|
|
@@ -299,7 +298,7 @@ function create(config) {
|
|
|
299
298
|
return getResponse(key, opts).then(function (r) { return r.body; });
|
|
300
299
|
}
|
|
301
300
|
|
|
302
|
-
function getStream(key, opts) { return
|
|
301
|
+
function getStream(key, opts) { return sharedRequest.promiseToStream(get(key, opts)); }
|
|
303
302
|
|
|
304
303
|
function getResponse(key, opts) {
|
|
305
304
|
opts = opts || {};
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
* Auth: same service-account JSON / RSA-SHA256-signed JWT exchanged
|
|
24
24
|
* for an OAuth2 access token as `lib/object-store/gcs.js`.
|
|
25
25
|
*/
|
|
26
|
-
var nodeFs = require("node:fs");
|
|
27
26
|
var gcs = require("./gcs");
|
|
28
27
|
var authHeader = require("../auth-header");
|
|
29
28
|
var httpClient = require("../http-client");
|
|
30
29
|
var safeJson = require("../safe-json");
|
|
31
30
|
var safeUrl = require("../safe-url");
|
|
32
31
|
var C = require("../constants");
|
|
32
|
+
var atomicFile = require("../atomic-file");
|
|
33
33
|
var requestHelpers = require("../request-helpers");
|
|
34
34
|
var { ObjectStoreError } = require("../framework-error");
|
|
35
35
|
|
|
@@ -139,7 +139,9 @@ function create(config) {
|
|
|
139
139
|
var serviceAccount = config.serviceAccount;
|
|
140
140
|
if (!serviceAccount && config.serviceAccountFile) {
|
|
141
141
|
try {
|
|
142
|
-
|
|
142
|
+
// Cap + fd-bound read of the GCS service-account JSON (private_key). NO
|
|
143
|
+
// refuseSymlink: commonly a k8s projected-secret mount (symlink).
|
|
144
|
+
serviceAccount = safeJson.parse(atomicFile.fdSafeReadSync(config.serviceAccountFile, { maxBytes: C.BYTES.kib(64), encoding: "utf8" }));
|
|
143
145
|
} catch (e) {
|
|
144
146
|
throw _err("BAD_OPT", "gcs bucketOps: failed to read serviceAccountFile '" +
|
|
145
147
|
config.serviceAccountFile + "': " + ((e && e.message) || String(e)), true);
|
package/lib/object-store/gcs.js
CHANGED
|
@@ -22,12 +22,11 @@
|
|
|
22
22
|
* https://cloud.google.com/storage/docs/json_api/v1
|
|
23
23
|
* https://developers.google.com/identity/protocols/oauth2/service-account
|
|
24
24
|
*/
|
|
25
|
-
var nodeFs = require("node:fs");
|
|
26
25
|
var nodeCrypto = require("node:crypto");
|
|
27
|
-
var { Readable } = require("node:stream");
|
|
28
26
|
var bCrypto = require("../crypto");
|
|
29
27
|
var safeJson = require("../safe-json");
|
|
30
28
|
var C = require("../constants");
|
|
29
|
+
var atomicFile = require("../atomic-file");
|
|
31
30
|
var requestHelpers = require("../request-helpers");
|
|
32
31
|
var { ObjectStoreError } = require("../framework-error");
|
|
33
32
|
var safeUrl = require("../safe-url");
|
|
@@ -115,7 +114,10 @@ function create(config) {
|
|
|
115
114
|
var serviceAccount = config.serviceAccount;
|
|
116
115
|
if (!serviceAccount && config.serviceAccountFile) {
|
|
117
116
|
try {
|
|
118
|
-
|
|
117
|
+
// Cap + fd-bound read of the GCS service-account JSON (contains the
|
|
118
|
+
// private_key). NO refuseSymlink: the SA file is commonly a k8s
|
|
119
|
+
// projected-secret mount (symlink). 64 KiB bounds the uncapped read.
|
|
120
|
+
serviceAccount = safeJson.parse(atomicFile.fdSafeReadSync(config.serviceAccountFile, { maxBytes: C.BYTES.kib(64), encoding: "utf8" }), { schema: SERVICE_ACCOUNT_SCHEMA });
|
|
119
121
|
} catch (e) {
|
|
120
122
|
throw new Error("gcs: failed to read serviceAccountFile '" + config.serviceAccountFile + "': " + e.message);
|
|
121
123
|
}
|
|
@@ -224,7 +226,7 @@ function create(config) {
|
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
function getStream(key, opts) {
|
|
227
|
-
return
|
|
229
|
+
return sharedRequest.promiseToStream(get(key, opts));
|
|
228
230
|
}
|
|
229
231
|
|
|
230
232
|
async function getResponse(key, opts) {
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
* Errors are surfaced as object-store errors with statusCode set so the
|
|
17
17
|
* retry layer can classify retryable vs permanent.
|
|
18
18
|
*/
|
|
19
|
-
var { Readable } = require("node:stream");
|
|
20
19
|
var { ObjectStoreError } = require("../framework-error");
|
|
21
20
|
var safeUrl = require("../safe-url");
|
|
22
21
|
var sharedRequest = require("./http-request");
|
|
@@ -96,7 +95,7 @@ function create(config) {
|
|
|
96
95
|
// https that doesn't buffer; return a Readable wrapping the buffered
|
|
97
96
|
// body. Backends that support chunked transfer (e.g. SigV4) implement
|
|
98
97
|
// a real streaming getStream in their own adapter.
|
|
99
|
-
return
|
|
98
|
+
return sharedRequest.promiseToStream(get(key));
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
function head(key) {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* different class pass `errorClass: SomeError` in opts.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
var { Readable } = require("node:stream");
|
|
19
20
|
var httpClient = require("../http-client");
|
|
20
21
|
var C = require("../constants");
|
|
21
22
|
var numericBounds = require("../numeric-bounds");
|
|
@@ -109,7 +110,22 @@ function resolvePresignUploadMinBytes(opts) {
|
|
|
109
110
|
// parameter; the rest is identical.
|
|
110
111
|
function applyConditionalGetHeaders(target, opts, rangeHeaderName) {
|
|
111
112
|
if (opts.range) {
|
|
112
|
-
|
|
113
|
+
// The documented + shipped contract is an array [start, end] (see
|
|
114
|
+
// b.archive.adapters.objectStore and b.backup). Reading .start/.end off an
|
|
115
|
+
// array yields undefined → "bytes=undefined-undefined", which every store
|
|
116
|
+
// IGNORES — silently returning the FULL object instead of the byte range
|
|
117
|
+
// (over-fetch + wrong data for a partial read). Accept the array form (and
|
|
118
|
+
// {start,end} for compatibility) and validate, so a malformed range fails
|
|
119
|
+
// loudly rather than emitting a garbage header.
|
|
120
|
+
var r = opts.range;
|
|
121
|
+
var start = Array.isArray(r) ? r[0] : r.start;
|
|
122
|
+
var end = Array.isArray(r) ? r[1] : r.end;
|
|
123
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start) {
|
|
124
|
+
throw _err("INVALID_RANGE",
|
|
125
|
+
"range must be [start, end] (or { start, end }) with 0 <= start <= end, got " +
|
|
126
|
+
JSON.stringify(r), true);
|
|
127
|
+
}
|
|
128
|
+
target[rangeHeaderName] = "bytes=" + start + "-" + end;
|
|
113
129
|
}
|
|
114
130
|
if (opts.ifNoneMatch) target["If-None-Match"] = opts.ifNoneMatch;
|
|
115
131
|
if (opts.ifMatch) target["If-Match"] = opts.ifMatch;
|
|
@@ -153,8 +169,21 @@ function notModifiedGetResult() {
|
|
|
153
169
|
};
|
|
154
170
|
}
|
|
155
171
|
|
|
172
|
+
// Wrap a Promise<Buffer|string> as a Readable WITHOUT awaiting it first, so a
|
|
173
|
+
// backend's getStream() stays synchronous (the dispatcher hands the returned
|
|
174
|
+
// Readable straight to the consumer). A bare `Readable.from(promise)` throws
|
|
175
|
+
// ERR_INVALID_ARG_TYPE — Readable.from needs an (async-)iterable, not a Promise
|
|
176
|
+
// — which broke getStream on every remote backend. The async generator defers
|
|
177
|
+
// the await to the first read; a rejection surfaces as the stream's 'error'
|
|
178
|
+
// event, matching the dispatcher's "the Readable surfaces its own errors"
|
|
179
|
+
// contract.
|
|
180
|
+
function promiseToStream(promise) {
|
|
181
|
+
return Readable.from((async function* () { yield await promise; })());
|
|
182
|
+
}
|
|
183
|
+
|
|
156
184
|
module.exports = request;
|
|
157
185
|
module.exports.applyConditionalGetHeaders = applyConditionalGetHeaders;
|
|
186
|
+
module.exports.promiseToStream = promiseToStream;
|
|
158
187
|
module.exports.mapGetResponse = mapGetResponse;
|
|
159
188
|
module.exports.mapHeadResponse = mapHeadResponse;
|
|
160
189
|
module.exports.notModifiedGetResult = notModifiedGetResult;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
var nodeFs = require("node:fs");
|
|
15
15
|
var nodePath = require("node:path");
|
|
16
16
|
var atomicFile = require("../atomic-file");
|
|
17
|
+
var C = require("../constants");
|
|
17
18
|
var cluster = require("../cluster");
|
|
18
19
|
var { ObjectStoreError } = require("../framework-error");
|
|
19
20
|
|
|
@@ -54,16 +55,14 @@ function create(config) {
|
|
|
54
55
|
return Promise.resolve({ size: body.length });
|
|
55
56
|
}
|
|
56
57
|
if (body && typeof body.pipe === "function") {
|
|
57
|
-
// Streaming put —
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
body.on("error", reject);
|
|
66
|
-
});
|
|
58
|
+
// Streaming put — stage into a no-follow exclusive temp + atomic rename
|
|
59
|
+
// (a bare createWriteStream(full) follows a symlink planted at `full` and
|
|
60
|
+
// leaves a half-written object there if the source aborts). maxBytes is
|
|
61
|
+
// raised to the object-store ceiling; default 64 MiB is too small here.
|
|
62
|
+
return atomicFile.writeStream(full, body, {
|
|
63
|
+
fileMode: 0o600,
|
|
64
|
+
maxBytes: C.BYTES.gib(64),
|
|
65
|
+
}).then(function (r) { return { size: r.bytesWritten }; });
|
|
67
66
|
}
|
|
68
67
|
if (typeof body === "string") {
|
|
69
68
|
var buf = Buffer.from(body, "utf8");
|
|
@@ -75,18 +74,39 @@ function create(config) {
|
|
|
75
74
|
|
|
76
75
|
function get(key) {
|
|
77
76
|
var full = _resolveSafe(rootDir, key);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
// One capped fd-bound read (no existsSync check-then-read TOCTOU): get()
|
|
78
|
+
// buffers the whole object, so an uncapped read of a multi-GiB object is an
|
|
79
|
+
// OOM lever — cap it (large reads should use getStream). refuseSymlink stays
|
|
80
|
+
// OFF: operators may legitimately symlink into the store, and _resolveSafe
|
|
81
|
+
// already confines the key to rootDir.
|
|
82
|
+
try {
|
|
83
|
+
return Promise.resolve(atomicFile.fdSafeReadSync(full, {
|
|
84
|
+
maxBytes: C.BYTES.mib(64),
|
|
85
|
+
errorFor: function (kind, detail) {
|
|
86
|
+
if (kind === "enoent") return _err("NOT_FOUND", "key not found: " + key, true);
|
|
87
|
+
if (kind === "too-large") {
|
|
88
|
+
return _err("OBJECT_TOO_LARGE", "object " + key + " exceeds the buffered-get read cap (" +
|
|
89
|
+
detail.size + " > " + detail.max + " bytes) — use getStream()", true);
|
|
90
|
+
}
|
|
91
|
+
return _err("READ_FAILED", "failed to read " + key, true);
|
|
92
|
+
},
|
|
93
|
+
}));
|
|
94
|
+
} catch (e) { return Promise.reject(e); }
|
|
82
95
|
}
|
|
83
96
|
|
|
84
97
|
function getStream(key) {
|
|
85
98
|
var full = _resolveSafe(rootDir, key);
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
// Open once and stream from that fd, mapping ENOENT to NOT_FOUND, so the
|
|
100
|
+
// existsSync→createReadStream check-then-read window is collapsed. Plain
|
|
101
|
+
// O_RDONLY (follows symlinks): operators may symlink into the store, and the
|
|
102
|
+
// key is already confined by _resolveSafe.
|
|
103
|
+
var fd;
|
|
104
|
+
try { fd = nodeFs.openSync(full, "r"); }
|
|
105
|
+
catch (e) {
|
|
106
|
+
if (e && e.code === "ENOENT") throw _err("NOT_FOUND", "key not found: " + key, true);
|
|
107
|
+
throw e;
|
|
88
108
|
}
|
|
89
|
-
return nodeFs.createReadStream(full);
|
|
109
|
+
return nodeFs.createReadStream(full, { fd: fd });
|
|
90
110
|
}
|
|
91
111
|
|
|
92
112
|
function head(key) {
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
*/
|
|
26
26
|
var nodeCrypto = require("node:crypto");
|
|
27
27
|
var { URL } = require("node:url");
|
|
28
|
-
var { Readable } = require("node:stream");
|
|
29
28
|
var safeXml = require("../parsers/safe-xml");
|
|
30
29
|
var sharedRequest = require("./http-request");
|
|
31
30
|
var C = require("../constants");
|
|
@@ -633,7 +632,7 @@ function create(config) {
|
|
|
633
632
|
}
|
|
634
633
|
|
|
635
634
|
function getStream(key, opts) {
|
|
636
|
-
return
|
|
635
|
+
return sharedRequest.promiseToStream(get(key, opts));
|
|
637
636
|
}
|
|
638
637
|
|
|
639
638
|
// getResponse(key, opts?) — full-fidelity GET. Returns
|
|
@@ -133,12 +133,27 @@ function _valueToOtlp(v) {
|
|
|
133
133
|
return { stringValue: String(v) };
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// Run a single wire STRING (span name / status message) through the telemetry
|
|
137
|
+
// redactor. These reach the collector exactly like attribute values, so a
|
|
138
|
+
// connection string / token / PII in an error message (the tracer's canonical
|
|
139
|
+
// `setStatus("error", e.message)`) must be scrubbed too (CWE-532). Routes
|
|
140
|
+
// through redactAttrs (the same chokepoint attributes use); fails toward an
|
|
141
|
+
// empty string on a redactor throw, matching redactAttrs' drop-on-error.
|
|
142
|
+
function _redactWireString(key, value) {
|
|
143
|
+
if (typeof value !== "string" || value.length === 0) return value || "";
|
|
144
|
+
try {
|
|
145
|
+
var holder = {};
|
|
146
|
+
holder[key] = value;
|
|
147
|
+
return observability().redactAttrs(holder)[key];
|
|
148
|
+
} catch (_e) { return ""; }
|
|
149
|
+
}
|
|
150
|
+
|
|
136
151
|
function _spanToOtlp(span) {
|
|
137
152
|
return {
|
|
138
153
|
traceId: span.traceId,
|
|
139
154
|
spanId: span.spanId,
|
|
140
155
|
parentSpanId: span.parentSpanId || "",
|
|
141
|
-
name: span.name,
|
|
156
|
+
name: _redactWireString("otel.span.name", span.name),
|
|
142
157
|
kind: KIND_TO_OTLP[span.kind] || KIND_TO_OTLP.internal,
|
|
143
158
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
144
159
|
endTimeUnixNano: span.endTimeUnixNano || span.startTimeUnixNano,
|
|
@@ -155,7 +170,7 @@ function _spanToOtlp(span) {
|
|
|
155
170
|
droppedEventsCount: span.droppedEventsCount || 0,
|
|
156
171
|
status: {
|
|
157
172
|
code: STATUS_CODE_TO_OTLP[span.status && span.status.code] || 0,
|
|
158
|
-
message: (span.status && span.status.message) || "",
|
|
173
|
+
message: _redactWireString("exception.message", (span.status && span.status.message) || ""),
|
|
159
174
|
},
|
|
160
175
|
};
|
|
161
176
|
}
|
|
@@ -336,7 +351,7 @@ function _attrsToProto(attrs) {
|
|
|
336
351
|
function _spanToProto(span) {
|
|
337
352
|
// Status code: 0=Unset, 1=Ok, 2=Error. Status field 1 is reserved.
|
|
338
353
|
var statusBody = Buffer.concat([
|
|
339
|
-
pb.string(2, (span.status && span.status.message) || ""),
|
|
354
|
+
pb.string(2, _redactWireString("exception.message", (span.status && span.status.message) || "")),
|
|
340
355
|
pb.uint32(3, STATUS_CODE_TO_OTLP[span.status && span.status.code] || 0),
|
|
341
356
|
]);
|
|
342
357
|
var eventsRepeated = pb.repeatedMessage(11, span.events || [], function (e) {
|
|
@@ -352,7 +367,7 @@ function _spanToProto(span) {
|
|
|
352
367
|
pb.bytes(2, _hexToBytes(span.spanId)),
|
|
353
368
|
pb.string(3, ""), // trace_state (not yet propagated by the framework)
|
|
354
369
|
pb.bytes(4, _hexToBytes(span.parentSpanId || "")),
|
|
355
|
-
pb.string(5, span.name || ""),
|
|
370
|
+
pb.string(5, _redactWireString("otel.span.name", span.name || "")),
|
|
356
371
|
pb.uint32(6, KIND_TEXT_TO_ENUM[span.kind] != null ? KIND_TEXT_TO_ENUM[span.kind] : KIND_TEXT_TO_ENUM.internal),
|
|
357
372
|
pb.fixed64(7, span.startTimeUnixNano || 0),
|
|
358
373
|
pb.fixed64(8, span.endTimeUnixNano || span.startTimeUnixNano || 0), // proto field number 8, not bytes
|
|
@@ -648,6 +663,7 @@ module.exports = {
|
|
|
648
663
|
OtlpExporterError: OtlpExporterError,
|
|
649
664
|
// Exported for tests
|
|
650
665
|
_spanToOtlp: _spanToOtlp,
|
|
666
|
+
_spanToProto: _spanToProto,
|
|
651
667
|
_bundleSpans: _bundleSpans,
|
|
652
668
|
_attrToOtlp: _attrToOtlp,
|
|
653
669
|
_BASE64URL_RE_REF: safeBuffer.BASE64URL_RE, // not used; reserved for OTLP/protobuf shape upgrade
|
package/lib/outbox.js
CHANGED
|
@@ -375,11 +375,18 @@ function create(opts) {
|
|
|
375
375
|
{ name: "last_error", type: "TEXT" },
|
|
376
376
|
{ name: "status", type: "VARCHAR(16)", notNull: true, default: "pending" },
|
|
377
377
|
], { dialect: dialect }), dialect);
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
378
|
+
// Index for the publisher's claim path (scans status='pending' ORDER BY
|
|
379
|
+
// next_attempt_at). sqlite/postgres support a partial index (WHERE on
|
|
380
|
+
// CREATE INDEX) on next_attempt_at; MySQL does NOT — a WHERE there is a
|
|
381
|
+
// syntax error that made declareSchema() throw on MySQL — so fall back to a
|
|
382
|
+
// composite (status, next_attempt_at) index, which serves the same
|
|
383
|
+
// equality+range scan. The 'pending' literal is a builder-emitted static
|
|
384
|
+
// predicate, opted in via allowLiterals.
|
|
385
|
+
var idxCols = dialect === "mysql" ? ["status", "next_attempt_at"] : ["next_attempt_at"];
|
|
386
|
+
var idxOpts = { dialect: dialect };
|
|
387
|
+
if (dialect !== "mysql") idxOpts.where = "status = 'pending'";
|
|
381
388
|
var idx = sql.toExternalSql(sql.createIndex(opts.table + "_pending_idx", opts.table,
|
|
382
|
-
|
|
389
|
+
idxCols, idxOpts), dialect);
|
|
383
390
|
await target.query(ddl.sql, ddl.params);
|
|
384
391
|
await target.query(idx.sql, idx.params);
|
|
385
392
|
// Back-compat: an outbox table created before the claimed_at column
|
package/lib/parsers/safe-xml.js
CHANGED
|
@@ -174,7 +174,7 @@ function parse(input, opts) {
|
|
|
174
174
|
}
|
|
175
175
|
out += String.fromCodePoint(code);
|
|
176
176
|
i = end + 1;
|
|
177
|
-
} else if (BUILT_IN_ENTITIES
|
|
177
|
+
} else if (Object.prototype.hasOwnProperty.call(BUILT_IN_ENTITIES, name)) {
|
|
178
178
|
out += BUILT_IN_ENTITIES[name];
|
|
179
179
|
i = end + 1;
|
|
180
180
|
} else {
|