@blamejs/core 0.6.69 → 0.7.0
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 +6 -0
- package/README.md +11 -1
- package/index.js +2 -5
- package/lib/api-key.js +24 -55
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +20 -16
- package/lib/app.js +6 -5
- package/lib/archive.js +58 -50
- package/lib/atomic-file.js +11 -5
- package/lib/audit-chain.js +12 -4
- package/lib/audit-sign.js +38 -13
- package/lib/audit-tools.js +4 -4
- package/lib/audit.js +8 -4
- package/lib/auth/jwt.js +3 -2
- package/lib/auth/lockout.js +3 -13
- package/lib/auth/oauth.js +27 -17
- package/lib/auth/passkey.js +1 -5
- package/lib/auth/password.js +23 -13
- package/lib/backup/bundle.js +2 -4
- package/lib/backup/crypto.js +8 -6
- package/lib/backup/index.js +18 -14
- package/lib/backup/manifest.js +6 -2
- package/lib/break-glass.js +12 -7
- package/lib/bundler.js +32 -21
- package/lib/cache-redis.js +8 -3
- package/lib/cache.js +39 -59
- package/lib/canonical-json.js +17 -2
- package/lib/chain-writer.js +2 -2
- package/lib/cli-helpers.js +9 -4
- package/lib/cli.js +27 -5
- package/lib/cluster-provider-db.js +2 -1
- package/lib/cluster-storage.js +71 -11
- package/lib/cluster.js +30 -25
- package/lib/config-drift.js +3 -4
- package/lib/constants.js +8 -3
- package/lib/cookies.js +16 -4
- package/lib/credential-hash.js +4 -7
- package/lib/crypto.js +9 -9
- package/lib/csv.js +11 -8
- package/lib/db-declare-row-policy.js +4 -9
- package/lib/db-declare-view.js +3 -6
- package/lib/db-query.js +9 -3
- package/lib/db-schema.js +75 -10
- package/lib/db.js +116 -70
- package/lib/deprecate.js +3 -8
- package/lib/dev.js +30 -19
- package/lib/dual-control.js +2 -4
- package/lib/error-page.js +32 -39
- package/lib/external-db-migrate.js +27 -20
- package/lib/external-db.js +11 -3
- package/lib/file-type.js +16 -8
- package/lib/forms.js +48 -12
- package/lib/framework-error.js +1 -2
- package/lib/{internal-sha1-hibp.js → framework-sha1-hibp.js} +1 -1
- package/lib/handlers.js +1 -1
- package/lib/http-client-cookie-jar.js +25 -18
- package/lib/http-client.js +46 -23
- package/lib/i18n.js +6 -16
- package/lib/jobs.js +5 -1
- package/lib/log-stream-cloudwatch.js +31 -30
- package/lib/log-stream-local.js +12 -3
- package/lib/log-stream-otlp-grpc.js +29 -23
- package/lib/log-stream-otlp.js +13 -19
- package/lib/log-stream-syslog.js +27 -22
- package/lib/log-stream-webhook.js +8 -17
- package/lib/log-stream.js +13 -2
- package/lib/log.js +83 -5
- package/lib/mail-bounce.js +22 -15
- package/lib/mail-dkim.js +3 -6
- package/lib/mail.js +62 -34
- package/lib/metrics.js +61 -25
- package/lib/middleware/api-encrypt.js +15 -13
- package/lib/middleware/body-parser.js +46 -39
- package/lib/middleware/bot-guard.js +25 -4
- package/lib/middleware/compression.js +26 -11
- package/lib/middleware/cors.js +6 -5
- package/lib/middleware/csp-nonce.js +17 -4
- package/lib/middleware/csrf-protect.js +1 -1
- package/lib/middleware/db-role-for.js +7 -30
- package/lib/middleware/health.js +42 -8
- package/lib/middleware/index.js +55 -36
- package/lib/middleware/rate-limit.js +11 -8
- package/lib/middleware/request-id.js +10 -2
- package/lib/middleware/request-log.js +3 -2
- package/lib/middleware/require-auth.js +5 -2
- package/lib/middleware/sse.js +5 -4
- package/lib/migration-files.js +37 -0
- package/lib/migrations.js +42 -17
- package/lib/mtls-ca.js +38 -17
- package/lib/mtls-engine-default.js +15 -7
- package/lib/network-dns.js +49 -20
- package/lib/network-heartbeat.js +7 -7
- package/lib/network-nts.js +70 -48
- package/lib/network-proxy.js +34 -15
- package/lib/network-tls.js +7 -5
- package/lib/network.js +3 -3
- package/lib/notify.js +14 -36
- package/lib/ntp-check.js +7 -6
- package/lib/numeric-bounds.js +35 -3
- package/lib/object-store/azure-blob-bucket-ops.js +73 -15
- package/lib/object-store/azure-blob.js +31 -24
- package/lib/object-store/gcs-bucket-ops.js +41 -17
- package/lib/object-store/gcs.js +41 -34
- package/lib/object-store/http-put.js +2 -11
- package/lib/object-store/http-request.js +38 -0
- package/lib/object-store/index.js +1 -1
- package/lib/object-store/sigv4-bucket-ops.js +33 -37
- package/lib/object-store/sigv4.js +50 -30
- package/lib/observability.js +14 -3
- package/lib/otel-export.js +16 -14
- package/lib/pagination.js +23 -8
- package/lib/parsers/index.js +11 -5
- package/lib/parsers/safe-env.js +43 -14
- package/lib/parsers/safe-ini.js +29 -10
- package/lib/parsers/safe-toml.js +55 -25
- package/lib/parsers/safe-xml.js +22 -11
- package/lib/parsers/safe-yaml.js +55 -17
- package/lib/permissions.js +24 -33
- package/lib/pqc-agent.js +4 -2
- package/lib/pqc-gate.js +28 -15
- package/lib/protobuf-encoder.js +9 -3
- package/lib/protocol-dispatcher.js +29 -12
- package/lib/pubsub-redis.js +25 -8
- package/lib/pubsub.js +84 -12
- package/lib/queue-local.js +13 -4
- package/lib/queue-redis.js +2 -2
- package/lib/queue-sqs.js +21 -16
- package/lib/queue.js +8 -4
- package/lib/redis-client.js +13 -10
- package/lib/request-helpers.js +33 -2
- package/lib/restore-bundle.js +2 -4
- package/lib/restore-rollback.js +4 -1
- package/lib/restore.js +12 -10
- package/lib/retry.js +58 -9
- package/lib/router.js +96 -27
- package/lib/safe-async.js +97 -4
- package/lib/safe-buffer.js +47 -0
- package/lib/safe-json.js +18 -9
- package/lib/safe-schema.js +54 -18
- package/lib/safe-url.js +6 -2
- package/lib/scheduler.js +25 -14
- package/lib/security-assert.js +31 -26
- package/lib/seeders.js +71 -60
- package/lib/session.js +13 -7
- package/lib/slug.js +35 -8
- package/lib/ssrf-guard.js +58 -43
- package/lib/static.js +11 -7
- package/lib/storage.js +2 -1
- package/lib/template.js +17 -7
- package/lib/testing.js +28 -8
- package/lib/time.js +26 -5
- package/lib/totp.js +18 -9
- package/lib/tracing.js +15 -5
- package/lib/uuid.js +47 -20
- package/lib/validate-opts.js +213 -0
- package/lib/vault/index.js +36 -27
- package/lib/vault/rotate.js +41 -22
- package/lib/vault/wrap.js +26 -19
- package/lib/webhook.js +37 -68
- package/lib/websocket.js +60 -46
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
- package/lib/auth/totp.js +0 -17
- package/lib/object-store/retry.js +0 -15
package/lib/log-stream-syslog.js
CHANGED
|
@@ -32,11 +32,17 @@ var net = require("net");
|
|
|
32
32
|
var os = require("os");
|
|
33
33
|
var tls = require("tls");
|
|
34
34
|
var C = require("./constants");
|
|
35
|
+
var { boot } = require("./log");
|
|
36
|
+
var safeAsync = require("./safe-async");
|
|
37
|
+
var safeUrl = require("./safe-url");
|
|
35
38
|
var { LogStreamError } = require("./framework-error");
|
|
36
39
|
|
|
37
40
|
var _err = LogStreamError.factory;
|
|
41
|
+
var log = boot("log-stream-syslog");
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
// RFC 5424 facility codes (0-23). 0x10 = 16 = local0 — the
|
|
44
|
+
// conventional facility for app-emitted records.
|
|
45
|
+
var DEFAULT_FACILITY = 0x10;
|
|
40
46
|
var DEFAULT_APP_NAME = "blamejs";
|
|
41
47
|
var DEFAULT_PROC_ID = String(process.pid);
|
|
42
48
|
var DEFAULT_MSG_ID = "-";
|
|
@@ -46,8 +52,8 @@ var TLS_DEFAULT_PORT = 6514;
|
|
|
46
52
|
var UDP_DEFAULT_PORT = 514;
|
|
47
53
|
var DEFAULT_TIMEOUT_MS = C.TIME.seconds(10);
|
|
48
54
|
var DEFAULT_RECONNECT_BASE_MS = 250;
|
|
49
|
-
var DEFAULT_RECONNECT_MAX_MS =
|
|
50
|
-
var DEFAULT_BUFFER_LIMIT = 10000;
|
|
55
|
+
var DEFAULT_RECONNECT_MAX_MS = C.TIME.seconds(30);
|
|
56
|
+
var DEFAULT_BUFFER_LIMIT = C.BYTES.bytes(10000);
|
|
51
57
|
|
|
52
58
|
// RFC 5424 severity codes from the framework's level names.
|
|
53
59
|
var LEVEL_TO_SEVERITY = {
|
|
@@ -64,7 +70,8 @@ function _toRfc3339(tsMs) {
|
|
|
64
70
|
function _formatRfc5424(record, cfg) {
|
|
65
71
|
var severity = LEVEL_TO_SEVERITY[record.level] != null
|
|
66
72
|
? LEVEL_TO_SEVERITY[record.level] : 6;
|
|
67
|
-
|
|
73
|
+
// RFC 5424 §6.2.1: PRI = facility << 3 | severity (severity is 3 bits).
|
|
74
|
+
var pri = (cfg.facility << 3) + severity;
|
|
68
75
|
var ts = _toRfc3339(record.ts || Date.now());
|
|
69
76
|
// Body — JSON-encode meta + message together so the structured
|
|
70
77
|
// payload survives the wire as a single MSG token. Operators with
|
|
@@ -86,21 +93,18 @@ function create(config) {
|
|
|
86
93
|
if (typeof url !== "string" || url.length === 0) {
|
|
87
94
|
throw _err("BAD_OPT", "log-stream syslog requires { url } (string)");
|
|
88
95
|
}
|
|
89
|
-
// Parse URL — accept udp://, tcp://, tls
|
|
90
|
-
// by default; allow these explicit transports.
|
|
96
|
+
// Parse URL — accept udp://, tcp://, tls:// only.
|
|
91
97
|
var parsed;
|
|
92
98
|
try {
|
|
93
|
-
parsed =
|
|
99
|
+
parsed = safeUrl.parse(url, {
|
|
100
|
+
allowedProtocols: ["udp:", "tcp:", "tls:"],
|
|
101
|
+
errorClass: LogStreamError,
|
|
102
|
+
});
|
|
94
103
|
} catch (e) {
|
|
95
104
|
throw _err("BAD_URL", "log-stream syslog: bad url '" + url + "': " +
|
|
96
105
|
((e && e.message) || String(e)));
|
|
97
106
|
}
|
|
98
107
|
var transport = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
99
|
-
if (transport !== "udp" && transport !== "tcp" && transport !== "tls") {
|
|
100
|
-
throw _err("BAD_URL",
|
|
101
|
-
"log-stream syslog: protocol must be udp / tcp / tls, got " +
|
|
102
|
-
JSON.stringify(parsed.protocol));
|
|
103
|
-
}
|
|
104
108
|
var defaultPort = transport === "tls" ? TLS_DEFAULT_PORT
|
|
105
109
|
: transport === "tcp" ? TCP_DEFAULT_PORT
|
|
106
110
|
: UDP_DEFAULT_PORT;
|
|
@@ -135,11 +139,8 @@ function create(config) {
|
|
|
135
139
|
}
|
|
136
140
|
// Track the operator's onDrop so dropped events surface.
|
|
137
141
|
var onDrop = typeof config.onDrop === "function" ? config.onDrop : null;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
try { onDrop({ reason: reason, batch: batch, error: err || null }); }
|
|
141
|
-
catch (_e) {}
|
|
142
|
-
}
|
|
142
|
+
var _emitDrop = safeAsync.makeDropCallback(onDrop,
|
|
143
|
+
function (e) { log.warn("onDrop-callback-failed: " + e.message); });
|
|
143
144
|
|
|
144
145
|
// ---- UDP transport ----
|
|
145
146
|
if (transport === "udp") {
|
|
@@ -167,7 +168,8 @@ function create(config) {
|
|
|
167
168
|
},
|
|
168
169
|
close: function () {
|
|
169
170
|
udpClosed = true;
|
|
170
|
-
try { udpSock.close(); }
|
|
171
|
+
try { udpSock.close(); }
|
|
172
|
+
catch (e) { log.warn("udp-close-failed: " + e.message); }
|
|
171
173
|
return Promise.resolve();
|
|
172
174
|
},
|
|
173
175
|
};
|
|
@@ -222,7 +224,8 @@ function create(config) {
|
|
|
222
224
|
sock.on("close", function () {
|
|
223
225
|
sockReady = false;
|
|
224
226
|
connecting = false;
|
|
225
|
-
try { sock.destroy(); }
|
|
227
|
+
try { sock.destroy(); }
|
|
228
|
+
catch (e) { log.warn("sock-destroy-failed: " + e.message); }
|
|
226
229
|
sock = null;
|
|
227
230
|
if (closed) return;
|
|
228
231
|
reconnectAttempt += 1;
|
|
@@ -263,15 +266,17 @@ function create(config) {
|
|
|
263
266
|
// shutdown race the slower TLS handshake and surface as
|
|
264
267
|
// "sink-closed" drops even though the framework had a viable
|
|
265
268
|
// connection in progress.
|
|
266
|
-
var DRAIN_TIMEOUT_MS =
|
|
269
|
+
var DRAIN_TIMEOUT_MS = C.TIME.seconds(3);
|
|
267
270
|
var started = Date.now();
|
|
268
271
|
return new Promise(function (resolve) {
|
|
269
272
|
function _finish() {
|
|
270
273
|
closed = true;
|
|
271
274
|
var pending = queue.splice(0, queue.length);
|
|
272
275
|
if (pending.length > 0) _emitDrop("sink-closed", pending, null);
|
|
273
|
-
try { if (sock) sock.end(); }
|
|
274
|
-
|
|
276
|
+
try { if (sock) sock.end(); }
|
|
277
|
+
catch (e) { log.warn("sock-end-failed: " + e.message); }
|
|
278
|
+
try { if (sock) sock.destroy(); }
|
|
279
|
+
catch (e) { log.warn("sock-destroy-failed: " + e.message); }
|
|
275
280
|
sock = null;
|
|
276
281
|
resolve();
|
|
277
282
|
}
|
|
@@ -33,6 +33,7 @@ var C = require("./constants");
|
|
|
33
33
|
var retryHelper = require("./retry");
|
|
34
34
|
var { LogStreamError } = require("./framework-error");
|
|
35
35
|
var httpClient = require("./http-client");
|
|
36
|
+
var safeAsync = require("./safe-async");
|
|
36
37
|
var safeUrl = require("./safe-url");
|
|
37
38
|
var authHeader = require("./auth-header");
|
|
38
39
|
|
|
@@ -46,7 +47,7 @@ var DEFAULTS = {
|
|
|
46
47
|
contentType: "application/json",
|
|
47
48
|
bodyShape: "array",
|
|
48
49
|
timeoutMs: C.TIME.seconds(30),
|
|
49
|
-
bufferLimit: 10000,
|
|
50
|
+
bufferLimit: C.BYTES.bytes(10000),
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
var _err = LogStreamError.factory;
|
|
@@ -105,28 +106,18 @@ function create(config) {
|
|
|
105
106
|
// would otherwise lose drops silently. The callback is invoked
|
|
106
107
|
// best-effort — a throw inside it is swallowed.
|
|
107
108
|
var onDrop = typeof cfg.onDrop === "function" ? cfg.onDrop : null;
|
|
108
|
-
|
|
109
|
-
if (!onDrop) return;
|
|
110
|
-
try { onDrop({ reason: reason, batch: batch, error: err || null }); }
|
|
111
|
-
catch (_e) { /* drop callback is best-effort by design */ }
|
|
112
|
-
}
|
|
109
|
+
var _emitDrop = safeAsync.makeDropCallback(onDrop);
|
|
113
110
|
var buffer = [];
|
|
114
111
|
var dropCount = 0;
|
|
115
|
-
var flushTimer = null;
|
|
116
112
|
var inFlight = false;
|
|
117
113
|
var closed = false;
|
|
118
|
-
|
|
119
|
-
function _scheduleFlush() {
|
|
120
|
-
if (flushTimer) return;
|
|
121
|
-
flushTimer = setTimeout(function () { flushTimer = null; _flush(); }, cfg.maxBatchAgeMs);
|
|
122
|
-
flushTimer.unref();
|
|
123
|
-
}
|
|
114
|
+
var flushScheduler = safeAsync.makeScheduledFlush(cfg.maxBatchAgeMs, function () { return _flush(); });
|
|
124
115
|
|
|
125
116
|
// Track the in-flight flush as a promise so close() can await it.
|
|
126
117
|
// Without this, a record arriving mid-flush gets buffered, the
|
|
127
118
|
// emit-time _flush() early-returns on `if (inFlight) return`, and
|
|
128
119
|
// the buffered record is stranded if shutdown fires before
|
|
129
|
-
//
|
|
120
|
+
// flushScheduler.schedule() drains it.
|
|
130
121
|
var inFlightPromise = null;
|
|
131
122
|
async function _flush() {
|
|
132
123
|
if (inFlight) return inFlightPromise;
|
|
@@ -154,7 +145,7 @@ function create(config) {
|
|
|
154
145
|
} finally {
|
|
155
146
|
inFlight = false;
|
|
156
147
|
inFlightPromise = null;
|
|
157
|
-
if (buffer.length > 0)
|
|
148
|
+
if (buffer.length > 0) flushScheduler.schedule();
|
|
158
149
|
}
|
|
159
150
|
})();
|
|
160
151
|
return inFlightPromise;
|
|
@@ -172,7 +163,7 @@ function create(config) {
|
|
|
172
163
|
// Don't await — non-blocking flush. Caller's emit returns immediately.
|
|
173
164
|
_flush().catch(function () {});
|
|
174
165
|
} else {
|
|
175
|
-
|
|
166
|
+
flushScheduler.schedule();
|
|
176
167
|
}
|
|
177
168
|
return Promise.resolve({ accepted: true, queued: buffer.length });
|
|
178
169
|
}
|
|
@@ -184,7 +175,7 @@ function create(config) {
|
|
|
184
175
|
// calling shutdown(). Order: stop the timer, await any in-flight
|
|
185
176
|
// flush so its records POST before we touch the buffer, drain
|
|
186
177
|
// anything still queued, THEN refuse new enqueues.
|
|
187
|
-
|
|
178
|
+
flushScheduler.cancel();
|
|
188
179
|
if (inFlightPromise) {
|
|
189
180
|
try { await inFlightPromise; } catch (_e) { /* surfaced via onDrop */ }
|
|
190
181
|
}
|
package/lib/log-stream.js
CHANGED
|
@@ -58,11 +58,14 @@ var otlpProto = require("./log-stream-otlp");
|
|
|
58
58
|
var otlpGrpcProto = require("./log-stream-otlp-grpc");
|
|
59
59
|
var cloudwatchProto = require("./log-stream-cloudwatch");
|
|
60
60
|
var syslogProto = require("./log-stream-syslog");
|
|
61
|
+
var { boot } = require("./log");
|
|
61
62
|
var redactor = require("./redact");
|
|
62
63
|
var lazyRequire = require("./lazy-require");
|
|
63
64
|
var protocolDispatcher = require("./protocol-dispatcher");
|
|
64
65
|
var { LogStreamError } = require("./framework-error");
|
|
65
66
|
|
|
67
|
+
var _log = boot("log-stream");
|
|
68
|
+
|
|
66
69
|
var dispatcher = protocolDispatcher.create({
|
|
67
70
|
name: "log-stream",
|
|
68
71
|
errorClass: LogStreamError,
|
|
@@ -79,7 +82,10 @@ var dispatcher = protocolDispatcher.create({
|
|
|
79
82
|
});
|
|
80
83
|
|
|
81
84
|
var LEVELS = ["debug", "info", "warn", "error"];
|
|
82
|
-
|
|
85
|
+
// Ordinal priority — higher = more severe. Values are spaced (1..4)
|
|
86
|
+
// only so future intermediate levels (e.g. 'notice' between info/warn)
|
|
87
|
+
// could slot in without renumbering the existing four.
|
|
88
|
+
var LEVEL_PRIORITY = { debug: 1, info: 2, warn: 3, error: 4 };
|
|
83
89
|
|
|
84
90
|
var _err = LogStreamError.factory;
|
|
85
91
|
|
|
@@ -292,7 +298,12 @@ function bootFromEnv(opts) {
|
|
|
292
298
|
|
|
293
299
|
function _resetForTest() {
|
|
294
300
|
Object.keys(sinks).forEach(function (n) {
|
|
295
|
-
try { if (sinks[n].raw.close) sinks[n].raw.close(); }
|
|
301
|
+
try { if (sinks[n].raw.close) sinks[n].raw.close(); }
|
|
302
|
+
catch (e) {
|
|
303
|
+
// Test-reset path; best-effort — surface for diagnosability via
|
|
304
|
+
// the framework's boot logger so flaky teardown doesn't go silent.
|
|
305
|
+
_log.debug("reset-close-failed: " + (e && e.message || e));
|
|
306
|
+
}
|
|
296
307
|
});
|
|
297
308
|
sinks = {};
|
|
298
309
|
incomingHandlers = [];
|
package/lib/log.js
CHANGED
|
@@ -60,11 +60,31 @@
|
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
62
|
var { AsyncLocalStorage } = require("node:async_hooks");
|
|
63
|
-
var
|
|
63
|
+
var C = require("./constants");
|
|
64
|
+
var lazyRequire = require("./lazy-require");
|
|
64
65
|
var redact = require("./redact");
|
|
66
|
+
var safeBuffer = require("./safe-buffer");
|
|
65
67
|
var validateOpts = require("./validate-opts");
|
|
66
68
|
var { FrameworkError } = require("./framework-error");
|
|
67
69
|
|
|
70
|
+
// Lazy resolves to break load-order cycles:
|
|
71
|
+
// - parsers/safe-env requires log (boot logger for env-read errors).
|
|
72
|
+
// log can't require safe-env at top because safe-env's top-level
|
|
73
|
+
// `var log = boot("env")` would see log's exports half-built.
|
|
74
|
+
// - crypto requires constants + framework-error + vendor; it does NOT
|
|
75
|
+
// require log, so a top-of-file require would technically work, but
|
|
76
|
+
// keeping crypto lazy keeps log loadable from anywhere without
|
|
77
|
+
// pulling the whole crypto bundle into the framework's earliest
|
|
78
|
+
// boot path (request-id middleware needs only generateToken).
|
|
79
|
+
var safeEnv = lazyRequire(function () { return require("./parsers/safe-env"); });
|
|
80
|
+
var crypto = lazyRequire(function () { return require("./crypto"); });
|
|
81
|
+
|
|
82
|
+
// Request-id correlation token — 8 bytes hex-encoded (16 chars). Short
|
|
83
|
+
// enough to read in a log line, long enough to keep collisions far below
|
|
84
|
+
// audible-noise even at 1M req/s sustained. Routed through C.BYTES so
|
|
85
|
+
// the file's byte arithmetic has a single source of truth.
|
|
86
|
+
var REQUEST_ID_BYTES = C.BYTES.bytes(8);
|
|
87
|
+
|
|
68
88
|
var LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 };
|
|
69
89
|
var LEVEL_NAMES = Object.keys(LEVELS);
|
|
70
90
|
|
|
@@ -185,7 +205,10 @@ function create(opts) {
|
|
|
185
205
|
], "b.log");
|
|
186
206
|
|
|
187
207
|
// Resolve initial level: env > opts > default
|
|
188
|
-
|
|
208
|
+
// safeEnv enforces the size cap + missing/empty handling; LEVELS lookup
|
|
209
|
+
// gates an unrecognised value (silently falls through to opts/default
|
|
210
|
+
// rather than throwing — bad LOG_LEVEL on the env should not crash boot).
|
|
211
|
+
var envLevel = safeEnv().readVar("LOG_LEVEL");
|
|
189
212
|
var level;
|
|
190
213
|
if (envLevel && LEVELS[envLevel] !== undefined) {
|
|
191
214
|
level = LEVELS[envLevel];
|
|
@@ -315,8 +338,10 @@ function create(opts) {
|
|
|
315
338
|
var generate = typeof mwOpts.generate === "function"
|
|
316
339
|
? mwOpts.generate
|
|
317
340
|
: function () {
|
|
318
|
-
// 16 random hex chars — short, sufficient correlation entropy
|
|
319
|
-
|
|
341
|
+
// 16 random hex chars — short, sufficient correlation entropy.
|
|
342
|
+
// Routes through the framework token primitive so the entropy
|
|
343
|
+
// source matches the rest of the codebase.
|
|
344
|
+
return crypto().generateToken(REQUEST_ID_BYTES);
|
|
320
345
|
};
|
|
321
346
|
return function logRequestIdMiddleware(req, res, next) {
|
|
322
347
|
var inbound = req.headers && req.headers[headerName];
|
|
@@ -324,7 +349,7 @@ function create(opts) {
|
|
|
324
349
|
? inbound
|
|
325
350
|
: generate();
|
|
326
351
|
// Strip CRLF defensively before reflecting back into a header
|
|
327
|
-
id = String(id)
|
|
352
|
+
id = safeBuffer.stripCrlf(String(id));
|
|
328
353
|
req.id = id;
|
|
329
354
|
if (setOnRes && typeof res.setHeader === "function") {
|
|
330
355
|
try { res.setHeader("X-Request-Id", id); } catch (_e) { /* header may be locked */ }
|
|
@@ -397,12 +422,25 @@ function boot(name) {
|
|
|
397
422
|
sink(JSON.stringify(entry));
|
|
398
423
|
}
|
|
399
424
|
|
|
425
|
+
function debug(msg, fields) {
|
|
426
|
+
// Boot-time debug entries route through console.log unless LOG_LEVEL
|
|
427
|
+
// suppresses them. fields (when present) get JSON-appended so
|
|
428
|
+
// operators see the structured context.
|
|
429
|
+
if (LEVELS.debug < _bootMinLevel()) return;
|
|
430
|
+
var rendered = msg;
|
|
431
|
+
if (fields !== undefined) {
|
|
432
|
+
try { rendered = msg + " " + JSON.stringify(fields); }
|
|
433
|
+
catch (_e) { rendered = msg; }
|
|
434
|
+
}
|
|
435
|
+
_emit("debug", rendered);
|
|
436
|
+
}
|
|
400
437
|
function info(msg) { _emit("info", msg); }
|
|
401
438
|
function warn(msg) { _emit("warn", msg); }
|
|
402
439
|
function error(msg) { _emit("error", msg); }
|
|
403
440
|
|
|
404
441
|
// The returned function is the info path so `log(msg)` matches the
|
|
405
442
|
// existing call shape across the codebase.
|
|
443
|
+
info.debug = debug;
|
|
406
444
|
info.info = info;
|
|
407
445
|
info.warn = warn;
|
|
408
446
|
info.error = error;
|
|
@@ -410,9 +448,49 @@ function boot(name) {
|
|
|
410
448
|
return info;
|
|
411
449
|
}
|
|
412
450
|
|
|
451
|
+
// makeViaOrFallback — closure factory for operator-log routing. Used by
|
|
452
|
+
// bundler / dev / error-page / pqc-gate (and similar primitives) that
|
|
453
|
+
// accept opts.log but must keep emitting through a per-module fallback
|
|
454
|
+
// when the operator didn't pass one. Replaces the per-file
|
|
455
|
+
// `function _logVia(log, level, message, fields) { if (log && typeof
|
|
456
|
+
// log[level] === "function") { try { log[level](message, fields); }
|
|
457
|
+
// catch ... } return; } ... fallback;` boilerplate.
|
|
458
|
+
//
|
|
459
|
+
// var _logVia = log.makeViaOrFallback(opts.log, log.boot("bundler"));
|
|
460
|
+
// _logVia("error", "build-failed", { reason: "..." });
|
|
461
|
+
//
|
|
462
|
+
// The operator log call is best-effort: a misbehaving log[level] swallows
|
|
463
|
+
// internally rather than crash the caller. Fallback is invoked only when
|
|
464
|
+
// the operator log is absent or doesn't expose the requested level.
|
|
465
|
+
function makeViaOrFallback(operatorLog, fallbackLog) {
|
|
466
|
+
return function (level, message, fields) {
|
|
467
|
+
if (operatorLog && typeof operatorLog[level] === "function") {
|
|
468
|
+
try { operatorLog[level](message, fields); }
|
|
469
|
+
catch (_e) { /* operator log best-effort */ }
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
var line = message + (fields ? " " + JSON.stringify(fields) : "");
|
|
473
|
+
var fb = (level === "error" || level === "fatal") ? fallbackLog.error
|
|
474
|
+
: (level === "warn") ? fallbackLog.warn
|
|
475
|
+
: fallbackLog.info;
|
|
476
|
+
if (typeof fb === "function") fb(line);
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Boot-time minimum level (debug suppressed unless explicitly enabled).
|
|
481
|
+
// Uses raw process.env per the documented load-cycle exception: log.js
|
|
482
|
+
// runs before safeEnv on the boot path; safeEnv requires log, so log
|
|
483
|
+
// can't go through safeEnv to read its own level.
|
|
484
|
+
function _bootMinLevel() {
|
|
485
|
+
// allow:raw-process-env — see header comment above
|
|
486
|
+
var raw = process.env.BLAMEJS_BOOT_LOG_LEVEL || process.env.LOG_LEVEL || "info";
|
|
487
|
+
return LEVELS[raw] != null ? LEVELS[raw] : LEVELS.info;
|
|
488
|
+
}
|
|
489
|
+
|
|
413
490
|
module.exports = {
|
|
414
491
|
create: create,
|
|
415
492
|
boot: boot,
|
|
493
|
+
makeViaOrFallback: makeViaOrFallback,
|
|
416
494
|
LEVELS: LEVELS,
|
|
417
495
|
LogError: LogError,
|
|
418
496
|
// Module-level helpers for code paths that don't have a logger
|
package/lib/mail-bounce.js
CHANGED
|
@@ -66,10 +66,14 @@
|
|
|
66
66
|
var lazyRequire = require("./lazy-require");
|
|
67
67
|
var numericBounds = require("./numeric-bounds");
|
|
68
68
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
69
|
+
var safeBuffer = require("./safe-buffer");
|
|
69
70
|
var safeJson = require("./safe-json");
|
|
70
71
|
var C = require("./constants");
|
|
72
|
+
var requestHelpers = require("./request-helpers");
|
|
71
73
|
var { defineClass } = require("./framework-error");
|
|
72
74
|
|
|
75
|
+
var HTTP = requestHelpers.HTTP_STATUS;
|
|
76
|
+
|
|
73
77
|
var MailBounceError = defineClass("MailBounceError", { withStatusCode: true });
|
|
74
78
|
|
|
75
79
|
// Cap on inbound webhook bodies — any vendor bigger than this is
|
|
@@ -441,27 +445,30 @@ function handler(opts) {
|
|
|
441
445
|
}
|
|
442
446
|
|
|
443
447
|
return function mailBounceMiddleware(req, res) {
|
|
444
|
-
var
|
|
445
|
-
|
|
448
|
+
var collector = safeBuffer.boundedChunkCollector({
|
|
449
|
+
maxBytes: maxBytes,
|
|
450
|
+
errorClass: MailBounceError,
|
|
451
|
+
sizeCode: "mailBounce/too-large",
|
|
452
|
+
sizeMessage: "payload exceeds maxBytes",
|
|
453
|
+
});
|
|
446
454
|
var aborted = false;
|
|
447
455
|
|
|
448
456
|
req.on("data", function (chunk) {
|
|
449
457
|
if (aborted) return;
|
|
450
|
-
|
|
451
|
-
|
|
458
|
+
try { collector.push(chunk); }
|
|
459
|
+
catch (_e) {
|
|
452
460
|
aborted = true;
|
|
453
|
-
return _send(res,
|
|
461
|
+
return _send(res, HTTP.PAYLOAD_TOO_LARGE, { error: "payload too large" });
|
|
454
462
|
}
|
|
455
|
-
chunks.push(chunk);
|
|
456
463
|
});
|
|
457
464
|
|
|
458
465
|
req.on("end", function () {
|
|
459
466
|
if (aborted) return;
|
|
460
|
-
var raw =
|
|
467
|
+
var raw = collector.result();
|
|
461
468
|
var body;
|
|
462
469
|
try { body = safeJson.parse(raw, { maxBytes: maxBytes }); }
|
|
463
470
|
catch (e) {
|
|
464
|
-
return _send(res,
|
|
471
|
+
return _send(res, HTTP.BAD_REQUEST, { error: "invalid JSON: " + e.message });
|
|
465
472
|
}
|
|
466
473
|
|
|
467
474
|
// Verification hook — operator decides what counts as authentic.
|
|
@@ -472,15 +479,15 @@ function handler(opts) {
|
|
|
472
479
|
if (verify) {
|
|
473
480
|
var ok;
|
|
474
481
|
try { ok = verify(req, body, raw); }
|
|
475
|
-
catch { return _send(res,
|
|
476
|
-
if (!ok) return _send(res,
|
|
482
|
+
catch { return _send(res, HTTP.UNAUTHORIZED, { error: "verification error" }); }
|
|
483
|
+
if (!ok) return _send(res, HTTP.UNAUTHORIZED, { error: "verification failed" });
|
|
477
484
|
}
|
|
478
485
|
|
|
479
486
|
var event;
|
|
480
487
|
try {
|
|
481
488
|
event = customParser ? _customParser(customParser)(body) : VENDORS[vendor](body);
|
|
482
489
|
} catch (e) {
|
|
483
|
-
return _send(res,
|
|
490
|
+
return _send(res, HTTP.BAD_REQUEST, {
|
|
484
491
|
error: e.message || "parse failed",
|
|
485
492
|
code: e.code || "parse-failed",
|
|
486
493
|
});
|
|
@@ -488,20 +495,20 @@ function handler(opts) {
|
|
|
488
495
|
|
|
489
496
|
_emit(event);
|
|
490
497
|
|
|
491
|
-
if (!onBounce) return _send(res,
|
|
498
|
+
if (!onBounce) return _send(res, HTTP.OK, { ok: true });
|
|
492
499
|
|
|
493
500
|
Promise.resolve()
|
|
494
501
|
.then(function () { return onBounce(event); })
|
|
495
|
-
.then(function () { _send(res,
|
|
502
|
+
.then(function () { _send(res, HTTP.OK, { ok: true }); },
|
|
496
503
|
function (e) {
|
|
497
|
-
_send(res,
|
|
504
|
+
_send(res, HTTP.INTERNAL_SERVER_ERROR, {
|
|
498
505
|
error: "onBounce threw: " + (e && e.message),
|
|
499
506
|
});
|
|
500
507
|
});
|
|
501
508
|
});
|
|
502
509
|
|
|
503
510
|
req.on("error", function () {
|
|
504
|
-
if (!aborted) _send(res,
|
|
511
|
+
if (!aborted) _send(res, HTTP.BAD_REQUEST, { error: "request error" });
|
|
505
512
|
});
|
|
506
513
|
};
|
|
507
514
|
}
|
package/lib/mail-dkim.js
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
var lazyRequire = require("./lazy-require");
|
|
42
42
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
43
43
|
var nodeCrypto = require("crypto");
|
|
44
|
+
var safeBuffer = require("./safe-buffer");
|
|
44
45
|
var validateOpts = require("./validate-opts");
|
|
45
46
|
var { FrameworkError } = require("./framework-error");
|
|
46
47
|
|
|
@@ -85,7 +86,7 @@ function _canonBodyRelaxed(body) {
|
|
|
85
86
|
var normalized = body.replace(/\r?\n/g, "\r\n");
|
|
86
87
|
var lines = normalized.split("\r\n");
|
|
87
88
|
for (var i = 0; i < lines.length; i++) {
|
|
88
|
-
lines[i] = lines[i].replace(/[ \t]+/g, " ")
|
|
89
|
+
lines[i] = safeBuffer.stripTrailingHspace(lines[i].replace(/[ \t]+/g, " "));
|
|
89
90
|
}
|
|
90
91
|
// Drop trailing empty lines.
|
|
91
92
|
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
@@ -240,11 +241,7 @@ function create(opts) {
|
|
|
240
241
|
"headersToSign[" + i + "] must be a non-empty string");
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
|
-
|
|
244
|
-
(typeof opts.bodyLength !== "number" || !isFinite(opts.bodyLength) || opts.bodyLength < 0)) {
|
|
245
|
-
throw new DkimError("dkim/bad-body-length",
|
|
246
|
-
"bodyLength must be a non-negative finite number");
|
|
247
|
-
}
|
|
244
|
+
validateOpts.optionalFiniteNonNegative(opts.bodyLength, "bodyLength", DkimError, "dkim/bad-body-length");
|
|
248
245
|
|
|
249
246
|
var auditOn = opts.audit !== false;
|
|
250
247
|
// Try to parse the private key once at create time so misconfigured
|