@blamejs/core 0.18.54 → 0.18.56
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 +146 -0
- package/NOTICE +5 -5
- package/README.md +9 -9
- package/lib/agent-audit.js +27 -2
- package/lib/audit-sign.js +24 -5
- package/lib/audit.js +26 -24
- package/lib/auth/passkey.js +4 -1
- package/lib/chain-writer.js +17 -0
- package/lib/db-file-lifecycle.js +14 -3
- package/lib/db.js +505 -49
- package/lib/guard-filename.js +8 -1
- package/lib/guard-html.js +10 -2
- package/lib/guard-list-unsubscribe.js +6 -1
- package/lib/guard-managesieve-command.js +24 -3
- package/lib/guard-smtp-command.js +20 -4
- package/lib/guard-svg.js +6 -1
- package/lib/http-client.js +17 -3
- package/lib/mail-agent.js +6 -4
- package/lib/mail-auth.js +59 -2
- package/lib/mail-server-imap.js +65 -34
- package/lib/mail-server-managesieve.js +65 -42
- package/lib/mail-server-mx.js +313 -40
- package/lib/mail-server-net.js +155 -1
- package/lib/mail-server-pop3.js +16 -19
- package/lib/mail-server-rate-limit.js +104 -6
- package/lib/mail-server-submission.js +162 -33
- package/lib/mail-server-tls.js +71 -11
- package/lib/mcp.js +11 -3
- package/lib/middleware/csrf-protect.js +37 -20
- package/lib/middleware/require-mtls.js +8 -1
- package/lib/network-tls.js +18 -0
- package/lib/safe-mount-info.js +39 -6
- package/lib/safe-smtp.js +96 -1
- package/lib/safe-url.js +8 -2
- package/lib/self-update.js +4 -1
- package/lib/session-stores.js +6 -3
- package/lib/vendor/MANIFEST.json +34 -34
- package/lib/vendor/blamejs-pki.cjs +396 -37
- package/lib/vendor/browser/noble-ciphers.mjs +15 -1
- package/lib/vendor/browser/noble-hashes.mjs +12 -4
- package/lib/vendor/browser/noble-post-quantum.mjs +78 -37
- package/lib/vendor/noble-ciphers.cjs +15 -1
- package/lib/vendor/noble-curves.cjs +46 -21
- package/lib/vendor/noble-post-quantum.cjs +184 -75
- package/lib/watcher.js +31 -6
- package/lib/ws-client.js +17 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -133,7 +133,6 @@ var safeBuffer = require("./safe-buffer");
|
|
|
133
133
|
var mailServerTls = require("./mail-server-tls");
|
|
134
134
|
var mailServerNet = require("./mail-server-net");
|
|
135
135
|
var C = require("./constants");
|
|
136
|
-
var bCrypto = require("./crypto");
|
|
137
136
|
var numericBounds = require("./numeric-bounds");
|
|
138
137
|
var validateOpts = require("./validate-opts");
|
|
139
138
|
var guardManageSieveCommand = require("./guard-managesieve-command");
|
|
@@ -172,6 +171,7 @@ var ERR_CLAMP = 200;
|
|
|
172
171
|
* greeting: string, // default "blamejs ManageSieve"
|
|
173
172
|
* maxLineBytes: number, // default 8192
|
|
174
173
|
* idleTimeoutMs: number, // default 5 min
|
|
174
|
+
* maxConnections: number, // default 1024 — listener-wide ceiling
|
|
175
175
|
* profile: "strict" | "balanced" | "permissive", // default "strict"
|
|
176
176
|
* auth: {
|
|
177
177
|
* mechanisms: ["SCRAM-SHA-256", "OAUTHBEARER", ...], // SASL mechs to advertise
|
|
@@ -212,7 +212,7 @@ function create(opts) {
|
|
|
212
212
|
"operator-supplied backend)");
|
|
213
213
|
}
|
|
214
214
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
215
|
-
["maxLineBytes", "idleTimeoutMs"],
|
|
215
|
+
["maxLineBytes", "idleTimeoutMs", "maxConnections"],
|
|
216
216
|
"mail.server.managesieve.", MailServerManageSieveError, "mail-server-managesieve/bad-bound");
|
|
217
217
|
|
|
218
218
|
var greeting = opts.greeting || DEFAULT_GREETING_VENDOR;
|
|
@@ -222,7 +222,16 @@ function create(opts) {
|
|
|
222
222
|
var authConfig = opts.auth || null;
|
|
223
223
|
var mailStore = opts.mailStore;
|
|
224
224
|
var allowPlaintext = opts.allowPlaintext === true;
|
|
225
|
-
|
|
225
|
+
|
|
226
|
+
// Read at the point of use, never captured. A consumer wiring the watching
|
|
227
|
+
// loader from mail.server.tls.context installs an accessor on this option, and
|
|
228
|
+
// capturing it here would call that accessor exactly once and freeze whatever
|
|
229
|
+
// certificate was current at boot — for a listener whose siblings all rotate.
|
|
230
|
+
// Certificates renew every 60 to 90 days on any automated CA, so the frozen
|
|
231
|
+
// one expires while the operator's evidence says rotation is working: the
|
|
232
|
+
// watcher fired, the context rebuilt, and it simply never reached here. The
|
|
233
|
+
// four sibling listeners already read the option per connection.
|
|
234
|
+
function _tlsContext() { return opts.tlsContext || null; }
|
|
226
235
|
|
|
227
236
|
// safeSieve cap matches the guard's per-profile script cap (the
|
|
228
237
|
// guard caps the literal-byte announcement; safeSieve.parse caps
|
|
@@ -236,22 +245,18 @@ function create(opts) {
|
|
|
236
245
|
var _emit = auditEmit.emit;
|
|
237
246
|
|
|
238
247
|
function _handleConnection(rawSocket) {
|
|
239
|
-
var
|
|
248
|
+
var accepted = mailServerNet.acceptConnection(rawSocket, {
|
|
249
|
+
rateLimit: rateLimit,
|
|
250
|
+
connections: connections,
|
|
251
|
+
emit: _emit,
|
|
240
252
|
refusedEvent: "mail.server.managesieve.rate_limit_refused",
|
|
241
253
|
refusalLine: 'NO "Too many connections from your IP"\r\n',
|
|
254
|
+
idPrefix: "msvconn-",
|
|
242
255
|
});
|
|
243
|
-
if (
|
|
244
|
-
var
|
|
245
|
-
var
|
|
246
|
-
|
|
247
|
-
// Single close handler covers BOTH operator-driven `_close(socket)`
|
|
248
|
-
// and client-initiated disconnects (TCP FIN / RST). Releases the
|
|
249
|
-
// rate-limit slot AND removes the socket from the tracking set so
|
|
250
|
-
// long-lived deployments don't accumulate stale entries.
|
|
251
|
-
rawSocket.once("close", function () {
|
|
252
|
-
rateLimit.releaseConnection(remoteAddress);
|
|
253
|
-
connections.delete(socket);
|
|
254
|
-
});
|
|
256
|
+
if (accepted === null) return;
|
|
257
|
+
var remoteAddress = accepted.remoteAddress;
|
|
258
|
+
var connectionId = accepted.connectionId;
|
|
259
|
+
var socket = accepted.socket;
|
|
255
260
|
|
|
256
261
|
var state = {
|
|
257
262
|
id: connectionId,
|
|
@@ -284,7 +289,7 @@ function create(opts) {
|
|
|
284
289
|
_emitCapabilityBanner(state, socket);
|
|
285
290
|
_writeOk(socket, greeting + " ready");
|
|
286
291
|
|
|
287
|
-
if (allowPlaintext && !
|
|
292
|
+
if (allowPlaintext && !_tlsContext()) {
|
|
288
293
|
_emit("mail.server.managesieve.plaintext_warning",
|
|
289
294
|
{ connectionId: connectionId,
|
|
290
295
|
remark: "allowPlaintext=true; no STARTTLS available — operators MUST gate at network layer" },
|
|
@@ -302,6 +307,31 @@ function create(opts) {
|
|
|
302
307
|
// AUTHENTICATE with a non-synchronizing initial-response), the next
|
|
303
308
|
// N bytes are the literal-payload; accumulate them before resuming
|
|
304
309
|
// line-mode dispatch.
|
|
310
|
+
// The CRLF that ends the LINE a literal sat on. RFC 5804 §4 puts it after
|
|
311
|
+
// the payload, outside the declared octet count, so every literal owes one
|
|
312
|
+
// and no literal's size includes it.
|
|
313
|
+
//
|
|
314
|
+
// Consumed here when it is already buffered, and otherwise awaited: it may
|
|
315
|
+
// arrive in a later segment, and read as a line it is an EMPTY line. On the
|
|
316
|
+
// SASL path an empty line is a second response and the pipelining guard
|
|
317
|
+
// fails the exchange for it; on the PUTSCRIPT path it draws a spurious
|
|
318
|
+
// "empty command line" refusal AFTER the script was accepted, so one command
|
|
319
|
+
// is answered twice and a client that reads one reply per command spends the
|
|
320
|
+
// rest of the session attributing each answer to the command before it.
|
|
321
|
+
//
|
|
322
|
+
// A peek alone is not enough, because whether the terminator has arrived yet
|
|
323
|
+
// is a property of the network rather than of the protocol. Shared by both
|
|
324
|
+
// literal paths: one of them used to do this and the other did not, which is
|
|
325
|
+
// the entire defect.
|
|
326
|
+
function _consumeLiteralTerminator(state) {
|
|
327
|
+
if (state.lineBuffer.length >= 2 &&
|
|
328
|
+
state.lineBuffer[0] === 0x0d && state.lineBuffer[1] === 0x0a) { // CR LF
|
|
329
|
+
state.lineBuffer = state.lineBuffer.subarray(2);
|
|
330
|
+
} else {
|
|
331
|
+
state.awaitingLiteralTerminator = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
305
335
|
function _drainBuffer(state, socket) {
|
|
306
336
|
while (true) {
|
|
307
337
|
if (state.pendingLiteral) {
|
|
@@ -314,6 +344,7 @@ function create(opts) {
|
|
|
314
344
|
}
|
|
315
345
|
pl.body = Buffer.concat([pl.body, state.lineBuffer.subarray(0, need)]);
|
|
316
346
|
state.lineBuffer = state.lineBuffer.subarray(need);
|
|
347
|
+
_consumeLiteralTerminator(state);
|
|
317
348
|
state.pendingLiteral = null;
|
|
318
349
|
_completePutscript(state, socket, pl);
|
|
319
350
|
if (state.stage === "closed") return;
|
|
@@ -329,26 +360,7 @@ function create(opts) {
|
|
|
329
360
|
}
|
|
330
361
|
pa.irBody = Buffer.concat([pa.irBody, state.lineBuffer.subarray(0, needA)]);
|
|
331
362
|
state.lineBuffer = state.lineBuffer.subarray(needA);
|
|
332
|
-
|
|
333
|
-
// buffer, so the next pass read it as an empty LINE — harmless while
|
|
334
|
-
// nothing was in flight, and not harmless during a multi-step SASL
|
|
335
|
-
// exchange, where an empty line is a second response and the pipelining
|
|
336
|
-
// guard fails the exchange for it. The bug only appeared when the two
|
|
337
|
-
// writes arrived as separate TCP segments, which is why the host run
|
|
338
|
-
// was green and the container run was not.
|
|
339
|
-
// The CRLF that ends the line the literal sat on. Consumed here when it
|
|
340
|
-
// is already buffered, and otherwise awaited: it may arrive in a later
|
|
341
|
-
// segment, and read as a line it is an EMPTY line. That was harmless
|
|
342
|
-
// while nothing was in flight, and during a multi-step SASL exchange it
|
|
343
|
-
// is a second response — the pipelining guard fails the exchange for
|
|
344
|
-
// it. A peek alone is not enough, because whether the terminator has
|
|
345
|
-
// arrived yet is a property of the network, not of the protocol.
|
|
346
|
-
if (state.lineBuffer.length >= 2 &&
|
|
347
|
-
state.lineBuffer[0] === 0x0d && state.lineBuffer[1] === 0x0a) { // CR LF
|
|
348
|
-
state.lineBuffer = state.lineBuffer.subarray(2);
|
|
349
|
-
} else {
|
|
350
|
-
state.awaitingLiteralTerminator = true;
|
|
351
|
-
}
|
|
363
|
+
_consumeLiteralTerminator(state);
|
|
352
364
|
pa.irBytes = null;
|
|
353
365
|
_completeAuthenticate(state, socket);
|
|
354
366
|
if (state.stage === "closed") return;
|
|
@@ -508,7 +520,7 @@ function create(opts) {
|
|
|
508
520
|
} else {
|
|
509
521
|
socket.write('"SASL" ""\r\n');
|
|
510
522
|
}
|
|
511
|
-
if (!state.tls &&
|
|
523
|
+
if (!state.tls && _tlsContext()) {
|
|
512
524
|
socket.write('"STARTTLS"\r\n');
|
|
513
525
|
}
|
|
514
526
|
}
|
|
@@ -536,7 +548,8 @@ function create(opts) {
|
|
|
536
548
|
_writeNo(socket, "STARTTLS only valid pre-AUTH (RFC 5804 §2.2)");
|
|
537
549
|
return;
|
|
538
550
|
}
|
|
539
|
-
|
|
551
|
+
var startTlsContext = _tlsContext();
|
|
552
|
+
if (!startTlsContext) {
|
|
540
553
|
_writeNo(socket, "STARTTLS unavailable (listener configured with allowPlaintext=true and no tlsContext)");
|
|
541
554
|
return;
|
|
542
555
|
}
|
|
@@ -549,7 +562,7 @@ function create(opts) {
|
|
|
549
562
|
mailServerTls.upgradeLineProtocol({
|
|
550
563
|
state: state,
|
|
551
564
|
socket: socket,
|
|
552
|
-
secureContext:
|
|
565
|
+
secureContext: startTlsContext,
|
|
553
566
|
idleTimeoutMs: idleTimeoutMs,
|
|
554
567
|
clearFields: ["pendingLiteral", "pendingAuth"],
|
|
555
568
|
drain: _drainBuffer,
|
|
@@ -625,10 +638,19 @@ function create(opts) {
|
|
|
625
638
|
mech: mech,
|
|
626
639
|
irBytes: parsed.literalBytes,
|
|
627
640
|
irPlus: parsed.literalPlus,
|
|
628
|
-
|
|
641
|
+
// RFC 5804 §2.1 lets the initial response come inline as a quoted string
|
|
642
|
+
// instead of a literal. It arrives complete on this line, so there is
|
|
643
|
+
// nothing to wait for — the exchange runs now, with the response the
|
|
644
|
+
// client actually sent. This used to fall through to the no-initial-
|
|
645
|
+
// response branch and answer a conforming client as though it had said
|
|
646
|
+
// nothing.
|
|
647
|
+
irBody: parsed.initialResponse === null || parsed.initialResponse === undefined
|
|
648
|
+
? Buffer.alloc(0)
|
|
649
|
+
: Buffer.from(parsed.initialResponse, "utf8"),
|
|
629
650
|
};
|
|
630
651
|
if (parsed.literalBytes === null) {
|
|
631
|
-
//
|
|
652
|
+
// Either an inline initial response, complete above, or none at all —
|
|
653
|
+
// in which case verify is called with an empty client response.
|
|
632
654
|
_completeAuthenticate(state, socket);
|
|
633
655
|
return;
|
|
634
656
|
}
|
|
@@ -987,6 +1009,7 @@ function create(opts) {
|
|
|
987
1009
|
// ---- Lifecycle ----------------------------------------------------------
|
|
988
1010
|
return mailServerNet.createStoreServer(net, {
|
|
989
1011
|
defaultPort: DEFAULT_PORT,
|
|
1012
|
+
maxConnections: opts.maxConnections,
|
|
990
1013
|
handleConnection: _handleConnection,
|
|
991
1014
|
errorClass: MailServerManageSieveError,
|
|
992
1015
|
errorCodePrefix: "mail-server-managesieve/",
|