@blamejs/core 0.15.11 → 0.15.13
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 +7 -23
- package/lib/acme.js +13 -16
- package/lib/agent-event-bus.js +5 -4
- package/lib/agent-idempotency.js +2 -5
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-capability.js +1 -6
- package/lib/ai-dp.js +1 -5
- package/lib/ai-input.js +2 -2
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-read.js +5 -17
- package/lib/archive-tar-read.js +5 -16
- package/lib/archive.js +2 -10
- package/lib/arg-parser.js +7 -6
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +108 -31
- package/lib/audit-chain.js +133 -53
- package/lib/audit-daily-review.js +24 -14
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +257 -0
- package/lib/audit.js +84 -0
- package/lib/auth/access-lock.js +5 -27
- package/lib/auth/dpop.js +23 -35
- package/lib/auth/fido-mds3.js +9 -15
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +13 -15
- package/lib/auth/lockout.js +6 -25
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +3 -3
- package/lib/auth/password.js +7 -1
- package/lib/auth/saml.js +37 -27
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +1 -1
- package/lib/auth/status-list.js +7 -7
- package/lib/auth/step-up.js +6 -12
- package/lib/auth-bot-challenge.js +6 -37
- package/lib/backup/bundle.js +11 -18
- package/lib/backup/index.js +14 -47
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/cache.js +7 -18
- package/lib/cbor.js +2 -1
- package/lib/cdn-cache-control.js +8 -9
- package/lib/cert.js +3 -10
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +5 -1
- package/lib/client-hints.js +10 -10
- package/lib/cloud-events.js +40 -31
- package/lib/cluster-storage.js +2 -38
- package/lib/cluster.js +4 -2
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +67 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +1 -11
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +2 -14
- package/lib/compliance.js +2 -9
- package/lib/config-drift.js +2 -12
- package/lib/content-digest.js +10 -8
- package/lib/cookies.js +5 -11
- package/lib/cose.js +19 -11
- package/lib/cra-report.js +1 -11
- package/lib/crypto-field.js +5 -10
- package/lib/crypto.js +120 -3
- package/lib/csp.js +235 -3
- package/lib/daemon.js +42 -41
- package/lib/data-act.js +19 -9
- package/lib/db-query.js +6 -40
- package/lib/db.js +34 -12
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dsr.js +8 -12
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +14 -26
- package/lib/fda-21cfr11.js +12 -8
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +869 -38
- package/lib/gdpr-ropa.js +4 -13
- package/lib/graphql-federation.js +1 -1
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +1 -0
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +16 -13
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +1 -1
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +18 -62
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +65 -117
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +8 -9
- package/lib/guard-json.js +87 -103
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +2 -1
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +5 -4
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +12 -13
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +8 -11
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +95 -140
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/http-client-cache.js +8 -13
- package/lib/http-client-cookie-jar.js +2 -4
- package/lib/http-client.js +8 -21
- package/lib/http-message-signature.js +26 -8
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +3 -21
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +6 -15
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +28 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-syslog.js +6 -0
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +24 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +3 -2
- package/lib/mail-auth.js +40 -66
- package/lib/mail-bimi.js +19 -39
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +6 -9
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +15 -26
- package/lib/mail-helo.js +2 -3
- package/lib/mail-journal.js +2 -1
- package/lib/mail-mdn.js +4 -3
- package/lib/mail-rbl.js +5 -8
- package/lib/mail-scan.js +2 -2
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +35 -51
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +89 -0
- package/lib/mail-spam-score.js +7 -8
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +11 -11
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +54 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +1 -1
- package/lib/mdoc.js +11 -12
- package/lib/metrics.js +32 -30
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +11 -22
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +79 -54
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +2 -10
- package/lib/middleware/csrf-protect.js +13 -2
- package/lib/middleware/daily-byte-quota.js +6 -26
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +7 -0
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/rate-limit.js +20 -28
- package/lib/middleware/scim-server.js +3 -2
- package/lib/middleware/security-headers.js +9 -1
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/tus-upload.js +12 -27
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +116 -36
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +97 -6
- package/lib/network-dns.js +22 -26
- package/lib/network-dnssec.js +16 -16
- package/lib/network-heartbeat.js +6 -5
- package/lib/network-proxy.js +3 -7
- package/lib/network-smtp-policy.js +29 -41
- package/lib/network-tls.js +74 -55
- package/lib/network.js +2 -6
- package/lib/nis2-report.js +1 -11
- package/lib/nonce-store.js +81 -3
- package/lib/notify.js +7 -12
- package/lib/numeric-bounds.js +22 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +5 -45
- package/lib/object-store/gcs.js +8 -71
- package/lib/object-store/http-put.js +1 -5
- package/lib/object-store/http-request.js +128 -0
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +9 -77
- package/lib/observability-otlp-exporter.js +9 -25
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +43 -37
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +2 -2
- package/lib/parsers/safe-yaml.js +7 -6
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/redact.js +2 -1
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +133 -6
- package/lib/restore.js +5 -22
- package/lib/retention.js +3 -5
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +12 -26
- package/lib/safe-json.js +7 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +12 -26
- package/lib/sandbox-worker.js +9 -2
- package/lib/sandbox.js +3 -2
- package/lib/scheduler.js +5 -12
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +9 -21
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +194 -6
- package/lib/sql.js +225 -78
- package/lib/sse.js +6 -10
- package/lib/static.js +136 -98
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +313 -17
- package/lib/tenant-quota.js +2 -20
- package/lib/tsa.js +11 -15
- package/lib/validate-opts.js +154 -8
- package/lib/vault/seal-pem-file.js +30 -48
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +2 -7
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +43 -18
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +7 -3
- package/lib/worm.js +2 -3
- package/lib/ws-client.js +8 -10
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/jobs.js
CHANGED
|
@@ -77,6 +77,7 @@ var { boot } = require("./log");
|
|
|
77
77
|
var queue = require("./queue");
|
|
78
78
|
var validateOpts = require("./validate-opts");
|
|
79
79
|
var { JobsError } = require("./framework-error");
|
|
80
|
+
var boundedMap = require("./bounded-map");
|
|
80
81
|
|
|
81
82
|
var log = boot("jobs");
|
|
82
83
|
|
|
@@ -102,10 +103,10 @@ function create(opts) {
|
|
|
102
103
|
if (typeof handler !== "function") {
|
|
103
104
|
throw _err("INVALID_HANDLER", "jobs.define: handler must be a function", true);
|
|
104
105
|
}
|
|
105
|
-
|
|
106
|
+
boundedMap.requireAbsent(registry, name, function () {
|
|
106
107
|
throw _err("DUPLICATE_NAME",
|
|
107
108
|
"jobs.define: '" + name + "' is already defined", true);
|
|
108
|
-
}
|
|
109
|
+
});
|
|
109
110
|
if (started) {
|
|
110
111
|
// Defining after start would mean the new handler doesn't run
|
|
111
112
|
// until the next start cycle. Reject loudly so operators don't
|
package/lib/keychain.js
CHANGED
|
@@ -242,24 +242,12 @@ async function _writeFile(fallbackFile, doc, passphrase) {
|
|
|
242
242
|
// Drain a stream into a Buffer, honoring an upper byte cap so a
|
|
243
243
|
// runaway tool can't OOM the framework.
|
|
244
244
|
function _drain(stream, capBytes) {
|
|
245
|
-
return
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
sizeMessage: "native tool output exceeded " + capBytes + " bytes",
|
|
252
|
-
});
|
|
253
|
-
stream.on("data", function (chunk) {
|
|
254
|
-
try {
|
|
255
|
-
collector.push(chunk);
|
|
256
|
-
} catch (e) {
|
|
257
|
-
try { stream.destroy(); } catch (_e) { /* destroy best-effort */ }
|
|
258
|
-
reject(e);
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
stream.on("end", function () { resolve(collector.result()); });
|
|
262
|
-
stream.on("error", function (e) { reject(e); });
|
|
245
|
+
if (!stream) return Promise.resolve(Buffer.alloc(0));
|
|
246
|
+
return safeBuffer.collectStream(stream, {
|
|
247
|
+
maxBytes: capBytes,
|
|
248
|
+
errorClass: KeychainError,
|
|
249
|
+
sizeCode: "keychain/native-output-too-large",
|
|
250
|
+
sizeMessage: "native tool output exceeded " + capBytes + " bytes",
|
|
263
251
|
});
|
|
264
252
|
}
|
|
265
253
|
|
package/lib/legal-hold.js
CHANGED
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
* on policy denials (already-held / not-held / invalid-citation).
|
|
61
61
|
*/
|
|
62
62
|
var bCrypto = require("./crypto");
|
|
63
|
-
var lazyRequire = require("./lazy-require");
|
|
64
63
|
var safeJson = require("./safe-json");
|
|
65
64
|
var validateOpts = require("./validate-opts");
|
|
66
65
|
var sql = require("./sql");
|
|
@@ -77,7 +76,7 @@ var { defineClass } = require("./framework-error");
|
|
|
77
76
|
var HOLD_TABLE = "_blamejs_legal_hold"; // allow:hand-rolled-sql — canonical local table-name; passed to b.sql with quoteName
|
|
78
77
|
var AUDIT_TABLE = "audit_log";
|
|
79
78
|
|
|
80
|
-
var
|
|
79
|
+
var auditEmit = require("./audit-emit");
|
|
81
80
|
|
|
82
81
|
var LegalHoldError = defineClass("LegalHoldError", { alwaysPermanent: true });
|
|
83
82
|
var _err = LegalHoldError.factory;
|
|
@@ -151,19 +150,11 @@ function create(opts) {
|
|
|
151
150
|
validateOpts.optionalObjectWithMethod(opts.signWith, "sign",
|
|
152
151
|
"create: opts.signWith", LegalHoldError, "BAD_OPT", "b.auditSign-shaped object");
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
action: action,
|
|
160
|
-
outcome: outcome,
|
|
161
|
-
resource: { kind: "legal-hold", id: info && info.subjectId },
|
|
162
|
-
reason: (info && info.reason) || null,
|
|
163
|
-
metadata: info || {},
|
|
164
|
-
});
|
|
165
|
-
} catch (_e) { /* best-effort */ }
|
|
166
|
-
}
|
|
153
|
+
var _emit = auditEmit.gatedReasonEmitter({
|
|
154
|
+
audit: auditOn,
|
|
155
|
+
sink: auditInstance,
|
|
156
|
+
extra: function (info) { return { resource: { kind: "legal-hold", id: info && info.subjectId } }; },
|
|
157
|
+
});
|
|
167
158
|
|
|
168
159
|
function _ensureSchema() {
|
|
169
160
|
// Idempotent migration. The framework SQLite path installs the
|
|
@@ -198,20 +198,9 @@ function create(config) {
|
|
|
198
198
|
"ResourceAlreadyExistsException treated as success).");
|
|
199
199
|
}
|
|
200
200
|
var cfg = Object.assign({}, DEFAULTS, config);
|
|
201
|
-
var onDrop = typeof cfg.onDrop === "function" ? cfg.onDrop : null;
|
|
202
|
-
var _emitDrop = safeAsync.makeDropCallback(onDrop);
|
|
203
|
-
var buffer = [];
|
|
204
|
-
var dropCount = 0;
|
|
205
|
-
var inFlight = false;
|
|
206
|
-
var closed = false;
|
|
207
201
|
var sequenceToken = null;
|
|
208
|
-
// Captures the in-flight drain (the IIFE inside _flush) so close() can await
|
|
209
|
-
// the actual run before flipping `closed` — otherwise the _flush while-loop's
|
|
210
|
-
// `&& !closed` bails and buffered records are stranded at shutdown.
|
|
211
|
-
var inFlightPromise = null;
|
|
212
|
-
var flushScheduler = safeAsync.makeScheduledFlush(cfg.maxBatchAgeMs, function () { return _flush(); });
|
|
213
202
|
|
|
214
|
-
function _takeBatch() {
|
|
203
|
+
function _takeBatch(buffer) {
|
|
215
204
|
var batch = [];
|
|
216
205
|
var totalBytes = 0;
|
|
217
206
|
while (buffer.length > 0) {
|
|
@@ -239,45 +228,25 @@ function create(config) {
|
|
|
239
228
|
return autoCreatePromise;
|
|
240
229
|
}
|
|
241
230
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (batch.length === 0) break;
|
|
262
|
-
try {
|
|
263
|
-
await retryHelper.withRetry(function () {
|
|
264
|
-
return _send(batch);
|
|
265
|
-
}, Object.assign({
|
|
266
|
-
isPermanent: _isPermanentAwsError,
|
|
267
|
-
}, cfg.retry || {}));
|
|
268
|
-
} catch (e) {
|
|
269
|
-
dropCount += batch.length;
|
|
270
|
-
_emitDrop("retry-exhausted", batch, e);
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
} finally {
|
|
275
|
-
inFlight = false;
|
|
276
|
-
inFlightPromise = null;
|
|
277
|
-
if (buffer.length > 0) flushScheduler.schedule();
|
|
278
|
-
}
|
|
279
|
-
})();
|
|
280
|
-
return inFlightPromise;
|
|
231
|
+
// CloudWatch rejects any single event above its 256 KiB hard cap — drop it
|
|
232
|
+
// (with a truncated preview) before it can poison a batch; otherwise build
|
|
233
|
+
// the { timestamp, message } event the API expects.
|
|
234
|
+
function _prepareRecord(record) {
|
|
235
|
+
var message = typeof record.message === "string" ? record.message : JSON.stringify(record);
|
|
236
|
+
var size = _eventByteSize(message);
|
|
237
|
+
if (size > CW_MAX_EVENT_BYTES) {
|
|
238
|
+
return {
|
|
239
|
+
rejected: true,
|
|
240
|
+
reason: "event too large",
|
|
241
|
+
dropKind: "event-too-large",
|
|
242
|
+
drop: [{
|
|
243
|
+
timestamp: record.ts || Date.now(),
|
|
244
|
+
message: message.slice(0, DROP_PREVIEW_BYTES) + "...[truncated for drop event]",
|
|
245
|
+
}],
|
|
246
|
+
error: new Error("event exceeds 256 KiB CloudWatch hard cap (was " + size + " bytes)"),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return { entry: { timestamp: record.ts || Date.now(), message: message } };
|
|
281
250
|
}
|
|
282
251
|
|
|
283
252
|
async function _send(batch) {
|
|
@@ -306,70 +275,33 @@ function create(config) {
|
|
|
306
275
|
return res;
|
|
307
276
|
}
|
|
308
277
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
_emitDrop("overflow", [dropped], null);
|
|
330
|
-
}
|
|
331
|
-
buffer.push({
|
|
332
|
-
timestamp: record.ts || Date.now(),
|
|
333
|
-
message: message,
|
|
334
|
-
});
|
|
335
|
-
if (buffer.length >= cfg.batchSize) {
|
|
336
|
-
_flush().catch(function () {});
|
|
337
|
-
} else {
|
|
338
|
-
flushScheduler.schedule();
|
|
339
|
-
}
|
|
340
|
-
return Promise.resolve({ accepted: true, queued: buffer.length });
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async function close() {
|
|
344
|
-
// Drain BEFORE flipping closed=true — _flush()'s `while (... && !closed)`
|
|
345
|
-
// loop bails on !closed, so flipping the flag first strands the records the
|
|
346
|
-
// operator queued just before shutdown (the b.logStream drain contract).
|
|
347
|
-
// Stop the timer, await any in-flight drain, drain the remainder, THEN
|
|
348
|
-
// refuse new enqueues. Mirrors the webhook sink.
|
|
349
|
-
flushScheduler.cancel();
|
|
350
|
-
if (inFlightPromise) {
|
|
351
|
-
try { await inFlightPromise; } catch (_e) { /* surfaced via onDrop */ }
|
|
352
|
-
}
|
|
353
|
-
await _flush();
|
|
354
|
-
closed = true;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function stats() {
|
|
358
|
-
return {
|
|
359
|
-
queued: buffer.length,
|
|
360
|
-
dropped: dropCount,
|
|
361
|
-
inFlight: inFlight,
|
|
362
|
-
sequenceToken: sequenceToken,
|
|
363
|
-
endpoint: _resolveEndpoint(cfg),
|
|
364
|
-
};
|
|
365
|
-
}
|
|
278
|
+
// beforeDrain runs the autoCreate handshake once; its failure is permanent
|
|
279
|
+
// (every batch would hit the same error) so the whole buffer is dropped under
|
|
280
|
+
// "autocreate-failed". takeBatch enforces CloudWatch's per-batch byte + count
|
|
281
|
+
// caps; sendBatch wraps the signed PutLogEvents in retry, treating throttling
|
|
282
|
+
// / sequence-token errors per _isPermanentAwsError.
|
|
283
|
+
var sink = safeAsync.makeBatchingSink({
|
|
284
|
+
batchSize: cfg.batchSize,
|
|
285
|
+
bufferLimit: cfg.bufferLimit,
|
|
286
|
+
maxBatchAgeMs: cfg.maxBatchAgeMs,
|
|
287
|
+
onDrop: cfg.onDrop,
|
|
288
|
+
prepareRecord: _prepareRecord,
|
|
289
|
+
takeBatch: _takeBatch,
|
|
290
|
+
beforeDrain: _ensureAutoCreated,
|
|
291
|
+
beforeDrainDropKind: "autocreate-failed",
|
|
292
|
+
sendBatch: function (batch) {
|
|
293
|
+
return retryHelper.withRetry(function () {
|
|
294
|
+
return _send(batch);
|
|
295
|
+
}, Object.assign({ isPermanent: _isPermanentAwsError }, cfg.retry || {}));
|
|
296
|
+
},
|
|
297
|
+
});
|
|
366
298
|
|
|
367
299
|
return {
|
|
368
300
|
protocol: "cloudwatch",
|
|
369
|
-
emit: emit,
|
|
370
|
-
close: close,
|
|
371
|
-
stats: stats,
|
|
372
|
-
flush:
|
|
301
|
+
emit: sink.emit,
|
|
302
|
+
close: sink.close,
|
|
303
|
+
stats: function () { return sink.stats({ sequenceToken: sequenceToken, endpoint: _resolveEndpoint(cfg) }); },
|
|
304
|
+
flush: sink.flush,
|
|
373
305
|
};
|
|
374
306
|
}
|
|
375
307
|
|
|
@@ -38,6 +38,9 @@ var lazyRequire = require("./lazy-require");
|
|
|
38
38
|
// scrub attribute values through the telemetry redactor before they cross the
|
|
39
39
|
// OTLP egress boundary (CWE-532).
|
|
40
40
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
41
|
+
// Lazy — network-tls is widely required; audit an insecure (cert-validation-
|
|
42
|
+
// disabled) outbound TLS session at honor time, same surface as connectWithEch.
|
|
43
|
+
var networkTls = lazyRequire(function () { return require("./network-tls"); });
|
|
41
44
|
|
|
42
45
|
var _err = LogStreamError.factory;
|
|
43
46
|
var _log = boot("log-stream-otlp-grpc");
|
|
@@ -215,7 +218,17 @@ function _makeClient(cfg) {
|
|
|
215
218
|
var sessionOpts = {};
|
|
216
219
|
if (cfg.ca) sessionOpts.ca = cfg.ca;
|
|
217
220
|
if (cfg.servername) sessionOpts.servername = cfg.servername;
|
|
218
|
-
if (cfg.allowInsecure
|
|
221
|
+
if (cfg.allowInsecure && url.protocol === "https:") {
|
|
222
|
+
// allowInsecure only has meaning on a TLS session. For an h2c endpoint
|
|
223
|
+
// (http://, cleartext HTTP/2) there is no certificate to validate and
|
|
224
|
+
// nothing to skip, so neither rejectUnauthorized nor the insecure-TLS
|
|
225
|
+
// audit applies — emitting it there would be a false security event.
|
|
226
|
+
// Operator-governed (not a hardcoded literal): cfg.allowInsecure is true in
|
|
227
|
+
// this branch, so this resolves to false — but derived from the operator's
|
|
228
|
+
// own flag, audited, and never a framework default.
|
|
229
|
+
sessionOpts.rejectUnauthorized = !cfg.allowInsecure;
|
|
230
|
+
networkTls().auditInsecureTls({ host: authority, source: "log-stream.otlp-grpc" });
|
|
231
|
+
}
|
|
219
232
|
var session = http2.connect(authority, sessionOpts);
|
|
220
233
|
session.on("error", function () { /* surfaced through request err */ });
|
|
221
234
|
if (typeof session.unref === "function") session.unref();
|
|
@@ -347,24 +360,24 @@ function create(config) {
|
|
|
347
360
|
}
|
|
348
361
|
}
|
|
349
362
|
|
|
363
|
+
// Fire-and-forget enqueue: full batch drains immediately (not awaited —
|
|
364
|
+
// emit is hot-path), partial batch coalesces via the scheduler. Errors
|
|
365
|
+
// surface through onDrop. No dropCount here — gRPC drop accounting is the
|
|
366
|
+
// export stream's concern, not the buffer's.
|
|
367
|
+
var _enqueue = safeAsync.makeBufferedEnqueue(buffer, {
|
|
368
|
+
batchSize: cfg.batchSize,
|
|
369
|
+
bufferLimit: cfg.bufferLimit,
|
|
370
|
+
flush: _flush,
|
|
371
|
+
schedule: flushScheduler.schedule,
|
|
372
|
+
onOverflow: function (dropped) { _emitDrop("overflow", [dropped], null); },
|
|
373
|
+
});
|
|
374
|
+
|
|
350
375
|
function emit(record) {
|
|
351
376
|
if (closed) {
|
|
352
377
|
_emitDrop("sink-closed", [record], null);
|
|
353
378
|
return Promise.resolve({ accepted: false, reason: "closed" });
|
|
354
379
|
}
|
|
355
|
-
|
|
356
|
-
var dropped = buffer.shift();
|
|
357
|
-
_emitDrop("overflow", [dropped], null);
|
|
358
|
-
}
|
|
359
|
-
buffer.push(record);
|
|
360
|
-
if (buffer.length >= cfg.batchSize) {
|
|
361
|
-
// Drain immediately; don't await — emit is fire-and-forget on the
|
|
362
|
-
// hot path. Errors surface via onDrop.
|
|
363
|
-
_flush().catch(function () {});
|
|
364
|
-
} else if (!closed) {
|
|
365
|
-
flushScheduler.schedule();
|
|
366
|
-
}
|
|
367
|
-
return Promise.resolve({ accepted: true, queued: buffer.length });
|
|
380
|
+
return _enqueue(record);
|
|
368
381
|
}
|
|
369
382
|
|
|
370
383
|
async function close() {
|
|
@@ -409,6 +422,7 @@ module.exports = {
|
|
|
409
422
|
create: create,
|
|
410
423
|
// Exposed for layer-0 tests that verify the wire encoding without
|
|
411
424
|
// standing up an HTTP/2 server.
|
|
425
|
+
_makeClient: _makeClient,
|
|
412
426
|
_encodeAnyValue: _encodeAnyValue,
|
|
413
427
|
_encodeKeyValue: _encodeKeyValue,
|
|
414
428
|
_encodeLogRecord: _encodeLogRecord,
|
package/lib/log-stream-otlp.js
CHANGED
|
@@ -212,89 +212,25 @@ function create(config) {
|
|
|
212
212
|
"Content-Type": "application/json",
|
|
213
213
|
"Accept": "application/json",
|
|
214
214
|
}, _authHeaders(cfg));
|
|
215
|
-
var
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
async function _flush() {
|
|
228
|
-
if (inFlight) return inFlightPromise;
|
|
229
|
-
if (buffer.length === 0) return;
|
|
230
|
-
inFlight = true;
|
|
231
|
-
inFlightPromise = (async function () {
|
|
232
|
-
try {
|
|
233
|
-
while (buffer.length > 0 && !closed) {
|
|
234
|
-
var batch = buffer.splice(0, cfg.batchSize);
|
|
235
|
-
var body = _serializeBatch(batch, cfg, scopeVersion);
|
|
236
|
-
try {
|
|
237
|
-
await retryHelper.withRetry(function () {
|
|
238
|
-
return _post(resolvedUrl, body, headers, cfg.timeoutMs, cfg.allowedProtocols, cfg.allowInternal);
|
|
239
|
-
}, cfg.retry);
|
|
240
|
-
} catch (e) {
|
|
241
|
-
dropCount += batch.length;
|
|
242
|
-
_emitDrop("retry-exhausted", batch, e);
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
} finally {
|
|
247
|
-
inFlight = false;
|
|
248
|
-
inFlightPromise = null;
|
|
249
|
-
if (buffer.length > 0) flushScheduler.schedule();
|
|
250
|
-
}
|
|
251
|
-
})();
|
|
252
|
-
return inFlightPromise;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function emit(record) {
|
|
256
|
-
if (closed) return Promise.resolve({ accepted: false, reason: "sink closed" });
|
|
257
|
-
if (buffer.length >= cfg.bufferLimit) {
|
|
258
|
-
var dropped = buffer.shift();
|
|
259
|
-
dropCount += 1;
|
|
260
|
-
_emitDrop("overflow", [dropped], null);
|
|
261
|
-
}
|
|
262
|
-
buffer.push(record);
|
|
263
|
-
if (buffer.length >= cfg.batchSize) {
|
|
264
|
-
_flush().catch(function () {});
|
|
265
|
-
} else {
|
|
266
|
-
flushScheduler.schedule();
|
|
267
|
-
}
|
|
268
|
-
return Promise.resolve({ accepted: true, queued: buffer.length });
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async function close() {
|
|
272
|
-
// Drain BEFORE flipping closed=true (see _flush's `&& !closed` guard) so
|
|
273
|
-
// records queued just before shutdown reach the wire. Mirrors the webhook
|
|
274
|
-
// + cloudwatch sinks.
|
|
275
|
-
flushScheduler.cancel();
|
|
276
|
-
if (inFlightPromise) {
|
|
277
|
-
try { await inFlightPromise; } catch (_e) { /* surfaced via onDrop */ }
|
|
278
|
-
}
|
|
279
|
-
await _flush();
|
|
280
|
-
closed = true;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function stats() {
|
|
284
|
-
return {
|
|
285
|
-
queued: buffer.length,
|
|
286
|
-
dropped: dropCount,
|
|
287
|
-
inFlight: inFlight,
|
|
288
|
-
url: resolvedUrl,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
215
|
+
var sink = safeAsync.makeBatchingSink({
|
|
216
|
+
batchSize: cfg.batchSize,
|
|
217
|
+
bufferLimit: cfg.bufferLimit,
|
|
218
|
+
maxBatchAgeMs: cfg.maxBatchAgeMs,
|
|
219
|
+
onDrop: cfg.onDrop,
|
|
220
|
+
sendBatch: function (batch) {
|
|
221
|
+
var body = _serializeBatch(batch, cfg, scopeVersion);
|
|
222
|
+
return retryHelper.withRetry(function () {
|
|
223
|
+
return _post(resolvedUrl, body, headers, cfg.timeoutMs, cfg.allowedProtocols, cfg.allowInternal);
|
|
224
|
+
}, cfg.retry);
|
|
225
|
+
},
|
|
226
|
+
});
|
|
291
227
|
|
|
292
228
|
return {
|
|
293
229
|
protocol: "otlp",
|
|
294
|
-
emit: emit,
|
|
295
|
-
close: close,
|
|
296
|
-
stats: stats,
|
|
297
|
-
flush:
|
|
230
|
+
emit: sink.emit,
|
|
231
|
+
close: sink.close,
|
|
232
|
+
stats: function () { return sink.stats({ url: resolvedUrl }); },
|
|
233
|
+
flush: sink.flush,
|
|
298
234
|
};
|
|
299
235
|
}
|
|
300
236
|
|
package/lib/log-stream-syslog.js
CHANGED
|
@@ -37,6 +37,9 @@ var safeAsync = require("./safe-async");
|
|
|
37
37
|
var safeBuffer = require("./safe-buffer");
|
|
38
38
|
var safeUrl = require("./safe-url");
|
|
39
39
|
var { LogStreamError } = require("./framework-error");
|
|
40
|
+
var lazyRequire = require("./lazy-require");
|
|
41
|
+
// Lazy — audit a cert-validation-disabled syslog/TLS session at honor time.
|
|
42
|
+
var networkTls = lazyRequire(function () { return require("./network-tls"); });
|
|
40
43
|
|
|
41
44
|
var _err = LogStreamError.factory;
|
|
42
45
|
var log = boot("log-stream-syslog");
|
|
@@ -223,6 +226,9 @@ function create(config) {
|
|
|
223
226
|
});
|
|
224
227
|
if (cfg.ca) tlsOpts.ca = cfg.ca;
|
|
225
228
|
if (cfg.servername) tlsOpts.servername = cfg.servername;
|
|
229
|
+
if (cfg.rejectUnauthorized === false) {
|
|
230
|
+
networkTls().auditInsecureTls({ host: cfg.host, port: cfg.port, source: "log-stream.syslog" });
|
|
231
|
+
}
|
|
226
232
|
sock = nodeTls.connect(tlsOpts, onConnect);
|
|
227
233
|
} else {
|
|
228
234
|
sock = net.connect(connectOpts, onConnect);
|
|
@@ -98,101 +98,29 @@ function create(config) {
|
|
|
98
98
|
errorClass: LogStreamError,
|
|
99
99
|
});
|
|
100
100
|
var headers = Object.assign({ "Content-Type": cfg.contentType }, _authHeaders(cfg));
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Without this, a record arriving mid-flush gets buffered, the
|
|
118
|
-
// emit-time _flush() early-returns on `if (inFlight) return`, and
|
|
119
|
-
// the buffered record is stranded if shutdown fires before
|
|
120
|
-
// flushScheduler.schedule() drains it.
|
|
121
|
-
var inFlightPromise = null;
|
|
122
|
-
async function _flush() {
|
|
123
|
-
if (inFlight) return inFlightPromise;
|
|
124
|
-
if (buffer.length === 0) return;
|
|
125
|
-
inFlight = true;
|
|
126
|
-
inFlightPromise = (async function () {
|
|
127
|
-
try {
|
|
128
|
-
while (buffer.length > 0 && !closed) {
|
|
129
|
-
var batch = buffer.splice(0, cfg.batchSize);
|
|
130
|
-
var body = _serializeBatch(batch, cfg.bodyShape);
|
|
131
|
-
try {
|
|
132
|
-
await retryHelper.withRetry(function () {
|
|
133
|
-
return _post(cfg.url, body, headers, cfg.timeoutMs, cfg.allowedProtocols, cfg.allowInternal);
|
|
134
|
-
}, cfg.retry);
|
|
135
|
-
} catch (e) {
|
|
136
|
-
// Batch permanently rejected — surface via dropCount AND the
|
|
137
|
-
// operator-supplied onDrop callback. The dispatcher path
|
|
138
|
-
// wraps its own audit hook around emit(); operators using
|
|
139
|
-
// this sink directly rely on dropCount + onDrop.
|
|
140
|
-
dropCount += batch.length;
|
|
141
|
-
_emitDrop("retry-exhausted", batch, e);
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
} finally {
|
|
146
|
-
inFlight = false;
|
|
147
|
-
inFlightPromise = null;
|
|
148
|
-
if (buffer.length > 0) flushScheduler.schedule();
|
|
149
|
-
}
|
|
150
|
-
})();
|
|
151
|
-
return inFlightPromise;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function emit(record) {
|
|
155
|
-
if (closed) return Promise.resolve({ accepted: false, reason: "sink closed" });
|
|
156
|
-
if (buffer.length >= cfg.bufferLimit) {
|
|
157
|
-
var dropped = buffer.shift(); // drop oldest
|
|
158
|
-
dropCount += 1;
|
|
159
|
-
_emitDrop("overflow", [dropped], null);
|
|
160
|
-
}
|
|
161
|
-
buffer.push(record);
|
|
162
|
-
if (buffer.length >= cfg.batchSize) {
|
|
163
|
-
// Don't await — non-blocking flush. Caller's emit returns immediately.
|
|
164
|
-
_flush().catch(function () {});
|
|
165
|
-
} else {
|
|
166
|
-
flushScheduler.schedule();
|
|
167
|
-
}
|
|
168
|
-
return Promise.resolve({ accepted: true, queued: buffer.length });
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async function close() {
|
|
172
|
-
// Drain BEFORE flipping closed=true. _flush()'s while loop bails on
|
|
173
|
-
// !closed, so flipping the flag first leaves any buffered records
|
|
174
|
-
// stranded — the very records the operator queued just before
|
|
175
|
-
// calling shutdown(). Order: stop the timer, await any in-flight
|
|
176
|
-
// flush so its records POST before we touch the buffer, drain
|
|
177
|
-
// anything still queued, THEN refuse new enqueues.
|
|
178
|
-
flushScheduler.cancel();
|
|
179
|
-
if (inFlightPromise) {
|
|
180
|
-
try { await inFlightPromise; } catch (_e) { /* surfaced via onDrop */ }
|
|
181
|
-
}
|
|
182
|
-
await _flush();
|
|
183
|
-
closed = true;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function stats() {
|
|
187
|
-
return { queued: buffer.length, dropped: dropCount, inFlight: inFlight };
|
|
188
|
-
}
|
|
101
|
+
// A batch permanently rejected by retry surfaces via dropCount AND the
|
|
102
|
+
// operator-supplied onDrop — the dispatcher wraps its own audit around
|
|
103
|
+
// emit(), but a sink wired directly (or by buffer overflow) would otherwise
|
|
104
|
+
// lose drops silently. onDrop is best-effort; a throw inside it is swallowed.
|
|
105
|
+
var sink = safeAsync.makeBatchingSink({
|
|
106
|
+
batchSize: cfg.batchSize,
|
|
107
|
+
bufferLimit: cfg.bufferLimit,
|
|
108
|
+
maxBatchAgeMs: cfg.maxBatchAgeMs,
|
|
109
|
+
onDrop: cfg.onDrop,
|
|
110
|
+
sendBatch: function (batch) {
|
|
111
|
+
var body = _serializeBatch(batch, cfg.bodyShape);
|
|
112
|
+
return retryHelper.withRetry(function () {
|
|
113
|
+
return _post(cfg.url, body, headers, cfg.timeoutMs, cfg.allowedProtocols, cfg.allowInternal);
|
|
114
|
+
}, cfg.retry);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
189
117
|
|
|
190
118
|
return {
|
|
191
119
|
protocol: "webhook",
|
|
192
|
-
emit: emit,
|
|
193
|
-
close: close,
|
|
194
|
-
stats: stats,
|
|
195
|
-
flush:
|
|
120
|
+
emit: sink.emit,
|
|
121
|
+
close: sink.close,
|
|
122
|
+
stats: sink.stats,
|
|
123
|
+
flush: sink.flush,
|
|
196
124
|
};
|
|
197
125
|
}
|
|
198
126
|
|
package/lib/log.js
CHANGED
|
@@ -464,7 +464,11 @@ function boot(name) {
|
|
|
464
464
|
var stream = (LEVELS[levelName] >= LEVELS.warn) ? process.stderr : process.stdout;
|
|
465
465
|
var isTty = !!(stream && stream.isTTY);
|
|
466
466
|
if (isTty) {
|
|
467
|
-
|
|
467
|
+
// Raw human-readable line — escape BOTH the C0/newline (line-forging)
|
|
468
|
+
// and bidi (re-ordering) control classes the create() path neutralizes,
|
|
469
|
+
// so a hostile boot message can't inject lines or re-order the visible
|
|
470
|
+
// line on a TTY / syslog reader (CWE-117 / Trojan-Source CVE-2021-42574).
|
|
471
|
+
sink(_escapeBidiControls(_escapeC0Controls(prefix + String(msg))));
|
|
468
472
|
return;
|
|
469
473
|
}
|
|
470
474
|
var entry = {
|
|
@@ -474,7 +478,9 @@ function boot(name) {
|
|
|
474
478
|
component: name,
|
|
475
479
|
boot: true,
|
|
476
480
|
};
|
|
477
|
-
|
|
481
|
+
// JSON.stringify already escapes C0/newlines; bidi/format controls survive
|
|
482
|
+
// raw into a piped aggregator, so apply the same bidi escape create() uses.
|
|
483
|
+
sink(_escapeBidiControls(JSON.stringify(entry)));
|
|
478
484
|
}
|
|
479
485
|
|
|
480
486
|
function debug(msg, fields) {
|
|
@@ -560,6 +566,22 @@ function _escapeBidiControls(s) {
|
|
|
560
566
|
});
|
|
561
567
|
}
|
|
562
568
|
|
|
569
|
+
// C0 control chars (incl. CR / LF / TAB) + DEL — escaped to `\uXXXX` so a
|
|
570
|
+
// hostile message can't forge extra log lines on a raw (non-JSON) TTY sink
|
|
571
|
+
// (log-injection, CWE-117). The create() path gets this for free from
|
|
572
|
+
// JSON.stringify; the boot() TTY branch writes raw text and needs it
|
|
573
|
+
// explicitly. Pairs with _escapeBidiControls (which only covers the bidi set).
|
|
574
|
+
var _C0_CONTROL_RE = /[\u0000-\u001f\u007f]/g; // eslint-disable-line no-control-regex -- the C0/DEL set is what we escape
|
|
575
|
+
|
|
576
|
+
function _escapeC0Controls(s) {
|
|
577
|
+
if (typeof s !== "string" || s.length === 0) return s;
|
|
578
|
+
return s.replace(_C0_CONTROL_RE, function (ch) {
|
|
579
|
+
var code = ch.charCodeAt(0).toString(16);
|
|
580
|
+
while (code.length < 4) code = "0" + code;
|
|
581
|
+
return "\\u" + code;
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
563
585
|
// runs before safeEnv on the boot path; safeEnv requires log, so log
|
|
564
586
|
// can't go through safeEnv to read its own level.
|
|
565
587
|
function _bootMinLevel() {
|