@blamejs/core 0.15.12 → 0.15.14
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 +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- 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 +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- 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 +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- 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/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- 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 +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- 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 +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- 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 +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- 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 +13 -14
- 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 +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- 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/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- 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-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- 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 +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- 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 +54 -41
- 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 +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- 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/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- 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 +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- 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 +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -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-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- 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 +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- 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/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -103,7 +103,6 @@
|
|
|
103
103
|
|
|
104
104
|
var net = require("node:net");
|
|
105
105
|
var nodeTls = require("node:tls");
|
|
106
|
-
var lazyRequire = require("./lazy-require");
|
|
107
106
|
var C = require("./constants");
|
|
108
107
|
var bCrypto = require("./crypto");
|
|
109
108
|
var numericBounds = require("./numeric-bounds");
|
|
@@ -115,9 +114,10 @@ var guardSmtpCommand = require("./guard-smtp-command");
|
|
|
115
114
|
var guardDomain = require("./guard-domain");
|
|
116
115
|
var mailServerRateLimit = require("./mail-server-rate-limit");
|
|
117
116
|
var mailServerTls = require("./mail-server-tls");
|
|
117
|
+
var mailServerNet = require("./mail-server-net");
|
|
118
118
|
var { defineClass } = require("./framework-error");
|
|
119
119
|
|
|
120
|
-
var
|
|
120
|
+
var auditEmit = require("./audit-emit");
|
|
121
121
|
|
|
122
122
|
var MailServerSubmissionError = defineClass("MailServerSubmissionError", { alwaysPermanent: true });
|
|
123
123
|
|
|
@@ -341,14 +341,7 @@ function create(opts) {
|
|
|
341
341
|
// Default-on per-IP rate limit (see lib/mail-server-rate-limit.js).
|
|
342
342
|
// Operators pass `rateLimit: false` to disable, a rate-limit handle
|
|
343
343
|
// to share across listeners, or an opts object to override defaults.
|
|
344
|
-
var rateLimit;
|
|
345
|
-
if (opts.rateLimit === false) {
|
|
346
|
-
rateLimit = mailServerRateLimit.create({ disabled: true });
|
|
347
|
-
} else if (opts.rateLimit && typeof opts.rateLimit.admitConnection === "function") {
|
|
348
|
-
rateLimit = opts.rateLimit;
|
|
349
|
-
} else {
|
|
350
|
-
rateLimit = mailServerRateLimit.create(opts.rateLimit || {});
|
|
351
|
-
}
|
|
344
|
+
var rateLimit = mailServerRateLimit.resolve(opts.rateLimit);
|
|
352
345
|
|
|
353
346
|
// Default-on guardDomain hardening for HELO / MAIL FROM / RCPT TO.
|
|
354
347
|
// Same posture as mail-server-mx — IDN homograph / Punycode-spoof
|
|
@@ -367,45 +360,25 @@ function create(opts) {
|
|
|
367
360
|
});
|
|
368
361
|
}
|
|
369
362
|
function _validateDomainHardened(d, label) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
_emit
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
label: label,
|
|
377
|
-
}, "denied");
|
|
378
|
-
}
|
|
379
|
-
return verdict;
|
|
363
|
+
return mailServerNet.validateDomainHardened(d, label, {
|
|
364
|
+
guardDomainProfile: guardDomainProfile,
|
|
365
|
+
guardDomain: guardDomain,
|
|
366
|
+
emit: _emit,
|
|
367
|
+
refusedEvent: "mail.server.submission.domain_refused",
|
|
368
|
+
});
|
|
380
369
|
}
|
|
381
370
|
|
|
382
|
-
var tcpServer = null;
|
|
383
|
-
var listening = false;
|
|
384
371
|
var connections = new Set();
|
|
385
372
|
|
|
386
|
-
|
|
387
|
-
try {
|
|
388
|
-
audit().safeEmit({
|
|
389
|
-
action: action,
|
|
390
|
-
outcome: outcome || "success",
|
|
391
|
-
metadata: metadata || {},
|
|
392
|
-
});
|
|
393
|
-
} catch (_e) { /* drop-silent */ }
|
|
394
|
-
}
|
|
373
|
+
var _emit = auditEmit.emit;
|
|
395
374
|
|
|
396
375
|
function _handleConnection(rawSocket) {
|
|
397
|
-
|
|
398
|
-
var
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
try {
|
|
404
|
-
rawSocket.write("421 4.7.0 Too many connections from your IP\r\n");
|
|
405
|
-
} catch (_e) { /* socket may already be torn down */ }
|
|
406
|
-
try { rawSocket.destroy(); } catch (_e2) { /* idempotent */ }
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
376
|
+
// 421 4.7.0 — transient; sender retries elsewhere.
|
|
377
|
+
var remoteAddress = mailServerNet.admitConnection(rawSocket, rateLimit, _emit, {
|
|
378
|
+
refusedEvent: "mail.server.submission.rate_limit_refused",
|
|
379
|
+
refusalLine: "421 4.7.0 Too many connections from your IP\r\n",
|
|
380
|
+
});
|
|
381
|
+
if (remoteAddress === null) return;
|
|
409
382
|
rawSocket.once("close", function () { rateLimit.releaseConnection(remoteAddress); });
|
|
410
383
|
|
|
411
384
|
var connectionId = "submitconn-" + bCrypto.generateToken(8); // connection-id length
|
|
@@ -570,7 +543,7 @@ function create(opts) {
|
|
|
570
543
|
}
|
|
571
544
|
|
|
572
545
|
lineBuffer = lineBuffer.length === 0 ? chunk : Buffer.concat([lineBuffer, chunk]);
|
|
573
|
-
if (lineBuffer
|
|
546
|
+
if (safeBuffer.byteLengthOf(lineBuffer) > maxLineBytes * 4) {
|
|
574
547
|
_writeReply(socket, REPLY_500_SYNTAX,
|
|
575
548
|
"5.5.6 Line too long (>" + maxLineBytes + " bytes)");
|
|
576
549
|
_closeConnection(socket);
|
|
@@ -1310,37 +1283,25 @@ function create(opts) {
|
|
|
1310
1283
|
}
|
|
1311
1284
|
}
|
|
1312
1285
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
tcpServer = net.createServer(function (socket) { _handleConnection(socket); });
|
|
1326
|
-
return new Promise(function (resolve, reject) {
|
|
1327
|
-
tcpServer.once("error", reject);
|
|
1328
|
-
tcpServer.listen(port, address, function () {
|
|
1329
|
-
listening = true;
|
|
1330
|
-
tcpServer.removeListener("error", reject);
|
|
1331
|
-
_emit("mail.server.submission.listening",
|
|
1332
|
-
{ port: port, address: address, implicitTls: implicitTls });
|
|
1333
|
-
resolve({ port: tcpServer.address().port, address: address });
|
|
1334
|
-
});
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1286
|
+
// Port 0 (ephemeral, test mode) must NOT fall back to the protocol default —
|
|
1287
|
+
// the `|| <default>` short-circuit was a footgun on the test path;
|
|
1288
|
+
// createTcpListener honors an explicit 0 (only an OMITTED port defaults). The
|
|
1289
|
+
// listening event reports implicitTls so an operator can confirm the wire mode.
|
|
1290
|
+
var _tcpListener = mailServerNet.createTcpListener(net, {
|
|
1291
|
+
defaultPort: implicitTls ? 465 : 587, // RFC 8314 implicit-TLS / RFC 6409 submission ports
|
|
1292
|
+
handleConnection: _handleConnection,
|
|
1293
|
+
errorFactory: function (code, message) { return new MailServerSubmissionError("mail-server-submission/" + code, message); },
|
|
1294
|
+
emit: _emit,
|
|
1295
|
+
listeningEvent: "mail.server.submission.listening",
|
|
1296
|
+
listeningExtra: function () { return { implicitTls: implicitTls }; },
|
|
1297
|
+
});
|
|
1337
1298
|
|
|
1338
1299
|
async function close(closeOpts) {
|
|
1339
1300
|
closeOpts = closeOpts || {};
|
|
1340
|
-
if (!
|
|
1301
|
+
if (!_tcpListener.isListening()) return;
|
|
1341
1302
|
var timeoutMs = closeOpts.timeoutMs || C.TIME.seconds(30);
|
|
1342
|
-
|
|
1343
|
-
|
|
1303
|
+
_tcpListener.markClosed();
|
|
1304
|
+
_tcpListener.getServer().close();
|
|
1344
1305
|
connections.forEach(function (sock) {
|
|
1345
1306
|
try { _writeReply(sock, REPLY_421_SERVICE_NOT_AVAIL, "4.3.0 Server shutting down"); }
|
|
1346
1307
|
catch (_e) { /* socket already gone */ }
|
|
@@ -1359,10 +1320,10 @@ function create(opts) {
|
|
|
1359
1320
|
function connectionCount() { return connections.size; }
|
|
1360
1321
|
|
|
1361
1322
|
return {
|
|
1362
|
-
listen: listen,
|
|
1323
|
+
listen: _tcpListener.listen,
|
|
1363
1324
|
close: close,
|
|
1364
1325
|
connectionCount: connectionCount,
|
|
1365
|
-
_portForTest: function () { return
|
|
1326
|
+
_portForTest: function () { var s = _tcpListener.getServer(); return s ? s.address().port : null; },
|
|
1366
1327
|
};
|
|
1367
1328
|
}
|
|
1368
1329
|
|
package/lib/mail-server-tls.js
CHANGED
|
@@ -93,6 +93,7 @@ var nodeFs = require("node:fs");
|
|
|
93
93
|
var nodeTls = require("node:tls");
|
|
94
94
|
var lazyRequire = require("./lazy-require");
|
|
95
95
|
var C = require("./constants");
|
|
96
|
+
var atomicFile = require("./atomic-file");
|
|
96
97
|
var validateOpts = require("./validate-opts");
|
|
97
98
|
var { defineClass } = require("./framework-error");
|
|
98
99
|
|
|
@@ -177,7 +178,11 @@ function context(opts) {
|
|
|
177
178
|
var stopped = false;
|
|
178
179
|
|
|
179
180
|
function _readKey() {
|
|
180
|
-
|
|
181
|
+
// Cap + fd-bound TLS-private-key read. NO refuseSymlink: keyFile is commonly
|
|
182
|
+
// certbot's /etc/letsencrypt/live/<host>/privkey.pem, a symlink chain into
|
|
183
|
+
// ../../archive — refusing symlinks would break the documented Let's Encrypt
|
|
184
|
+
// layout. The cap is the OOM-before-use defense.
|
|
185
|
+
var raw = atomicFile.fdSafeReadSync(keyFile, { maxBytes: C.BYTES.kib(64), encoding: "utf8" });
|
|
181
186
|
// b.vault.sealPemFile produces blobs that decrypt via vault.unseal.
|
|
182
187
|
// Detect by the sealed-cell prefix the framework's vault layer
|
|
183
188
|
// already documents (everything else passes through as plain PEM).
|
|
@@ -196,7 +201,9 @@ function context(opts) {
|
|
|
196
201
|
function _build() {
|
|
197
202
|
var certPem;
|
|
198
203
|
try {
|
|
199
|
-
|
|
204
|
+
// Cap + fd-bound cert-chain read. NO refuseSymlink (certbot fullchain.pem
|
|
205
|
+
// is a symlink); the cap is the safe hardening.
|
|
206
|
+
certPem = atomicFile.fdSafeReadSync(certFile, { maxBytes: C.BYTES.mib(1), encoding: "utf8" });
|
|
200
207
|
} catch (e) {
|
|
201
208
|
throw new MailServerTlsError("mail-server-tls/cert-unreadable",
|
|
202
209
|
"b.mail.server.tls.context: cannot read certFile " + certFile + ": " +
|
|
@@ -438,8 +445,97 @@ function upgradeSocket(opts) {
|
|
|
438
445
|
return tlsSocket;
|
|
439
446
|
}
|
|
440
447
|
|
|
448
|
+
/**
|
|
449
|
+
* @primitive b.mail.server.tls.upgradeLineProtocol
|
|
450
|
+
* @signature b.mail.server.tls.upgradeLineProtocol(opts)
|
|
451
|
+
* @since 0.15.13
|
|
452
|
+
* @status stable
|
|
453
|
+
* @related b.mail.server.tls.upgradeSocket
|
|
454
|
+
*
|
|
455
|
+
* STARTTLS / STLS completion for the line-buffered store listeners
|
|
456
|
+
* (IMAP / POP3 / ManageSieve), layered over `upgradeSocket`. The
|
|
457
|
+
* caller has already validated protocol state and written its
|
|
458
|
+
* "begin TLS" response; this owns the steps that recur identically
|
|
459
|
+
* across the three:
|
|
460
|
+
*
|
|
461
|
+
* 1. Drop the pre-handshake `state.lineBuffer` (always) plus any
|
|
462
|
+
* protocol-specific half-parsed command / literal / auth fields
|
|
463
|
+
* (`clearFields`) so bytes the peer pipelined before the upgrade
|
|
464
|
+
* cannot survive into the post-TLS session (CVE-2021-33515 /
|
|
465
|
+
* CVE-2021-38371 STARTTLS-injection class). Centralizing the
|
|
466
|
+
* `lineBuffer` reset makes it impossible for a listener to forget.
|
|
467
|
+
* 2. Mark `state.tls = true` on the secure event, then run the
|
|
468
|
+
* caller's optional `onSecure` for protocol-specific work (e.g.
|
|
469
|
+
* ManageSieve re-emitting its capability banner per RFC 5804).
|
|
470
|
+
* 3. Feed every post-handshake chunk through the caller's `drain`
|
|
471
|
+
* via the standard `state.lineBuffer` append.
|
|
472
|
+
*
|
|
473
|
+
* The transfer listeners (MX / submission) ingest via a serialized
|
|
474
|
+
* feed pump, not the line buffer, so they call `upgradeSocket`
|
|
475
|
+
* directly.
|
|
476
|
+
*
|
|
477
|
+
* @opts
|
|
478
|
+
* state: object, // connection state ({ lineBuffer, tls, … })
|
|
479
|
+
* socket: net.Socket, // pre-upgrade plain socket
|
|
480
|
+
* secureContext: tls.SecureContext, // from b.mail.server.tls.context
|
|
481
|
+
* idleTimeoutMs: number, // re-armed post-handshake
|
|
482
|
+
* clearFields: Array<string>, // extra state fields to null pre-upgrade
|
|
483
|
+
* drain: function(state, tlsSocket), // the protocol line drainer
|
|
484
|
+
* onSecure: function(tlsSocket), // optional post-secure work
|
|
485
|
+
* onError: function(err), // handshake / runtime error
|
|
486
|
+
* onTimeout: function(tlsSocket), // optional idle-timeout handler
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* b.mail.server.tls.upgradeLineProtocol({
|
|
490
|
+
* state: state, socket: socket, secureContext: opts.tlsContext,
|
|
491
|
+
* idleTimeoutMs: idleTimeoutMs, clearFields: ["pendingLiteral"],
|
|
492
|
+
* drain: _drainBuffer,
|
|
493
|
+
* onError: function (err) { _emit("imap.tls_failed", { err: err.message }); _close(socket, state); },
|
|
494
|
+
* });
|
|
495
|
+
*/
|
|
496
|
+
function upgradeLineProtocol(opts) {
|
|
497
|
+
if (!opts || typeof opts !== "object") {
|
|
498
|
+
throw new MailServerTlsError("mail-server-tls/bad-upgrade-line-opts",
|
|
499
|
+
"upgradeLineProtocol: opts required");
|
|
500
|
+
}
|
|
501
|
+
var state = opts.state;
|
|
502
|
+
if (!state || typeof state !== "object") {
|
|
503
|
+
throw new MailServerTlsError("mail-server-tls/bad-upgrade-line-state",
|
|
504
|
+
"upgradeLineProtocol: opts.state object required");
|
|
505
|
+
}
|
|
506
|
+
if (typeof opts.drain !== "function") {
|
|
507
|
+
throw new MailServerTlsError("mail-server-tls/bad-upgrade-line-drain",
|
|
508
|
+
"upgradeLineProtocol: opts.drain(state, tlsSocket) required");
|
|
509
|
+
}
|
|
510
|
+
// CVE-2021-33515 / CVE-2021-38371 injection defense: discard the
|
|
511
|
+
// pre-handshake line buffer plus every protocol-specific half-parsed
|
|
512
|
+
// field so nothing the peer pipelined pre-upgrade survives the TLS
|
|
513
|
+
// boundary.
|
|
514
|
+
state.lineBuffer = Buffer.alloc(0);
|
|
515
|
+
var clearFields = opts.clearFields;
|
|
516
|
+
if (clearFields) {
|
|
517
|
+
for (var i = 0; i < clearFields.length; i += 1) state[clearFields[i]] = null;
|
|
518
|
+
}
|
|
519
|
+
return upgradeSocket({
|
|
520
|
+
plainSocket: opts.socket,
|
|
521
|
+
secureContext: opts.secureContext,
|
|
522
|
+
idleTimeoutMs: opts.idleTimeoutMs,
|
|
523
|
+
onSecure: function (tlsSocket) {
|
|
524
|
+
state.tls = true;
|
|
525
|
+
if (typeof opts.onSecure === "function") opts.onSecure(tlsSocket);
|
|
526
|
+
},
|
|
527
|
+
onData: function (tlsSocket, chunk) {
|
|
528
|
+
state.lineBuffer = Buffer.concat([state.lineBuffer, chunk]);
|
|
529
|
+
opts.drain(state, tlsSocket);
|
|
530
|
+
},
|
|
531
|
+
onError: opts.onError,
|
|
532
|
+
onTimeout: opts.onTimeout,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
441
536
|
module.exports = {
|
|
442
537
|
context: context,
|
|
443
538
|
upgradeSocket: upgradeSocket,
|
|
539
|
+
upgradeLineProtocol: upgradeLineProtocol,
|
|
444
540
|
MailServerTlsError: MailServerTlsError,
|
|
445
541
|
};
|
package/lib/mail-spam-score.js
CHANGED
|
@@ -71,6 +71,7 @@ var { defineClass } = require("./framework-error");
|
|
|
71
71
|
var lazyRequire = require("./lazy-require");
|
|
72
72
|
var validateOpts = require("./validate-opts");
|
|
73
73
|
var gateContract = require("./gate-contract");
|
|
74
|
+
var codepointClass = require("./codepoint-class");
|
|
74
75
|
|
|
75
76
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
76
77
|
|
|
@@ -131,8 +132,8 @@ function create(opts) {
|
|
|
131
132
|
throw new MailSpamScoreError("mail-spam-score/bad-scorer",
|
|
132
133
|
"mail.spamScore.create.scorer must be a function; got " + (typeof opts.scorer));
|
|
133
134
|
}
|
|
134
|
-
var profile =
|
|
135
|
-
if (!PROFILES
|
|
135
|
+
var profile = gateContract.resolveProfileName(opts, COMPLIANCE_POSTURES, DEFAULT_PROFILE);
|
|
136
|
+
if (!Object.prototype.hasOwnProperty.call(PROFILES, profile)) {
|
|
136
137
|
throw new MailSpamScoreError("mail-spam-score/bad-profile",
|
|
137
138
|
"mail.spamScore.create.profile: unknown '" + profile +
|
|
138
139
|
"' (valid: strict / balanced / permissive)");
|
|
@@ -221,9 +222,7 @@ function create(opts) {
|
|
|
221
222
|
* @example
|
|
222
223
|
* b.mail.spamScore.compliancePosture("hipaa"); // → "strict"
|
|
223
224
|
*/
|
|
224
|
-
|
|
225
|
-
return COMPLIANCE_POSTURES[posture] || null;
|
|
226
|
-
}
|
|
225
|
+
var compliancePosture = gateContract.makePostureAccessor(COMPLIANCE_POSTURES);
|
|
227
226
|
|
|
228
227
|
function _sanitizeReasons(reasons, caps) {
|
|
229
228
|
if (reasons === undefined || reasons === null) return [];
|
|
@@ -250,13 +249,11 @@ function _sanitizeReasons(reasons, caps) {
|
|
|
250
249
|
// Refuse control bytes (CR / LF / NUL / etc.) — a compromised
|
|
251
250
|
// scorer could try to smuggle CRLF into an outbound X-Spam-Status
|
|
252
251
|
// header.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
cc.toString(16)); // hex radix
|
|
259
|
-
}
|
|
252
|
+
var _ctlOff = codepointClass.firstControlCharOffset(r, { forbidTab: true });
|
|
253
|
+
if (_ctlOff !== -1) {
|
|
254
|
+
throw new MailSpamScoreError("mail-spam-score/control-byte",
|
|
255
|
+
"mail.spamScore.score: reasons[" + i + "] contains control byte 0x" +
|
|
256
|
+
r.charCodeAt(_ctlOff).toString(16)); // hex radix
|
|
260
257
|
}
|
|
261
258
|
out.push(r);
|
|
262
259
|
}
|
package/lib/mail-store-fts.js
CHANGED
|
@@ -321,7 +321,7 @@ var QUERY_KEY_MAP = {
|
|
|
321
321
|
* // → { column: "addr_toks", field: "addr" }
|
|
322
322
|
*/
|
|
323
323
|
function columnAndFieldFor(key) {
|
|
324
|
-
return QUERY_KEY_MAP[key]
|
|
324
|
+
return Object.prototype.hasOwnProperty.call(QUERY_KEY_MAP, key) ? QUERY_KEY_MAP[key] : null;
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
// Rewrite an operator query term into a FTS5 MATCH expression. The
|
package/lib/mail-store.js
CHANGED
|
@@ -61,6 +61,7 @@ var C = require("./constants");
|
|
|
61
61
|
var bCrypto = require("./crypto");
|
|
62
62
|
var cryptoField = require("./crypto-field");
|
|
63
63
|
var vault = require("./vault");
|
|
64
|
+
var safeBuffer = require("./safe-buffer");
|
|
64
65
|
var safeMime = require("./safe-mime");
|
|
65
66
|
var safeSql = require("./safe-sql");
|
|
66
67
|
var sql = require("./sql");
|
|
@@ -798,7 +799,7 @@ function _appendMessage(args) {
|
|
|
798
799
|
"appendMessage: rawBytes must be Buffer or string");
|
|
799
800
|
}
|
|
800
801
|
var buf = Buffer.isBuffer(rawBytes) ? rawBytes : Buffer.from(rawBytes, "utf8");
|
|
801
|
-
if (buf
|
|
802
|
+
if (safeBuffer.byteLengthOf(buf) > args.maxMessageBytes) {
|
|
802
803
|
throw new MailStoreError("mail-store/oversize-message",
|
|
803
804
|
"appendMessage: " + buf.length + " bytes exceeds maxMessageBytes=" + args.maxMessageBytes);
|
|
804
805
|
}
|
|
@@ -854,7 +855,7 @@ function _appendMessage(args) {
|
|
|
854
855
|
var bodyText = textPart ? textPart.body : "";
|
|
855
856
|
var bodyHtml = htmlPart && htmlPart.contentType === "text/html" ? htmlPart.body : "";
|
|
856
857
|
|
|
857
|
-
if (bodyText
|
|
858
|
+
if (safeBuffer.byteLengthOf(bodyText) > args.maxBodyBytes || safeBuffer.byteLengthOf(bodyHtml) > args.maxBodyBytes) {
|
|
858
859
|
throw new MailStoreError("mail-store/oversize-body",
|
|
859
860
|
"appendMessage: body exceeds maxBodyBytes=" + args.maxBodyBytes);
|
|
860
861
|
}
|
package/lib/mail.js
CHANGED
|
@@ -57,6 +57,8 @@
|
|
|
57
57
|
*/
|
|
58
58
|
var C = require("./constants");
|
|
59
59
|
var bCrypto = require("./crypto");
|
|
60
|
+
var codepointClass = require("./codepoint-class");
|
|
61
|
+
var markupEscape = require("./markup-escape").markupEscape;
|
|
60
62
|
var lazyRequire = require("./lazy-require");
|
|
61
63
|
var safeBuffer = require("./safe-buffer");
|
|
62
64
|
var guardDomain = require("./guard-domain");
|
|
@@ -388,11 +390,7 @@ function _renderPostalAddressHtml(addr) {
|
|
|
388
390
|
}
|
|
389
391
|
|
|
390
392
|
function _htmlEscape(s) {
|
|
391
|
-
return
|
|
392
|
-
.replace(/&/g, "&")
|
|
393
|
-
.replace(/</g, "<")
|
|
394
|
-
.replace(/>/g, ">")
|
|
395
|
-
.replace(/"/g, """);
|
|
393
|
+
return markupEscape(s);
|
|
396
394
|
}
|
|
397
395
|
|
|
398
396
|
function _hasUnsubscribe(message) {
|
|
@@ -432,6 +430,26 @@ function _validateMessage(message) {
|
|
|
432
430
|
throw new MailError("mail/invalid-subject",
|
|
433
431
|
"message.subject contains forbidden CRLF", true);
|
|
434
432
|
}
|
|
433
|
+
// Reply-To and custom header KEYS go straight onto the wire in _buildRfc822;
|
|
434
|
+
// a CRLF in either smuggles arbitrary headers (Bcc / Reply-To override /
|
|
435
|
+
// Content-Type) — RFC 5322 / CWE-93 header injection. Fail closed.
|
|
436
|
+
if (message.replyTo && safeBuffer.hasCrlf(String(message.replyTo))) {
|
|
437
|
+
throw new MailError("mail/invalid-reply-to",
|
|
438
|
+
"message.replyTo contains forbidden CRLF (header injection)", true);
|
|
439
|
+
}
|
|
440
|
+
if (message.headers && typeof message.headers === "object") {
|
|
441
|
+
for (var _hk in message.headers) {
|
|
442
|
+
if (!Object.prototype.hasOwnProperty.call(message.headers, _hk)) continue;
|
|
443
|
+
if (safeBuffer.hasCrlf(_hk) || _hk.indexOf("\0") !== -1) {
|
|
444
|
+
throw new MailError("mail/invalid-header",
|
|
445
|
+
"message.headers key contains forbidden CRLF/NUL (header injection)", true);
|
|
446
|
+
}
|
|
447
|
+
if (safeBuffer.hasCrlf(String(message.headers[_hk]))) {
|
|
448
|
+
throw new MailError("mail/invalid-header",
|
|
449
|
+
"message.headers value for '" + _hk + "' contains forbidden CRLF (header injection)", true);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
435
453
|
|
|
436
454
|
if (!message.text && !message.html && !message.calendar) {
|
|
437
455
|
throw new MailError("mail/missing-body",
|
|
@@ -713,7 +731,7 @@ function _buildRfc822(message) {
|
|
|
713
731
|
headers.push("From: " + message.from);
|
|
714
732
|
headers.push("To: " + (Array.isArray(message.to) ? message.to.join(", ") : message.to));
|
|
715
733
|
if (message.cc) headers.push("Cc: " + (Array.isArray(message.cc) ? message.cc.join(", ") : message.cc));
|
|
716
|
-
if (message.replyTo) headers.push("Reply-To: " + message.replyTo);
|
|
734
|
+
if (message.replyTo) headers.push("Reply-To: " + safeBuffer.stripCrlf(String(message.replyTo)));
|
|
717
735
|
if (message.subject) headers.push("Subject: " + message.subject);
|
|
718
736
|
headers.push("MIME-Version: 1.0");
|
|
719
737
|
headers.push("Date: " + new Date().toUTCString());
|
|
@@ -723,7 +741,7 @@ function _buildRfc822(message) {
|
|
|
723
741
|
// Strip CRLF defensively even though we already validated the
|
|
724
742
|
// message; custom headers go straight onto the wire.
|
|
725
743
|
var v = safeBuffer.stripCrlf(String(message.headers[k]));
|
|
726
|
-
headers.push(k + ": " + v);
|
|
744
|
+
headers.push(safeBuffer.stripCrlf(String(k)) + ": " + v);
|
|
727
745
|
}
|
|
728
746
|
}
|
|
729
747
|
}
|
|
@@ -1920,12 +1938,9 @@ function feedbackId(opts) {
|
|
|
1920
1938
|
// Refuse CR/LF (header-injection) + control chars. Walk codepoints
|
|
1921
1939
|
// manually because eslint's no-control-regex refuses control-char
|
|
1922
1940
|
// ranges in regex literals regardless of escape form.
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
throw new MailError("mail/bad-feedback-id-field",
|
|
1927
|
-
"feedbackId: " + f.key + " contains control characters");
|
|
1928
|
-
}
|
|
1941
|
+
if (codepointClass.firstControlCharOffset(f.value, { forbidTab: true }) !== -1) { // C0 + DEL codepoint range
|
|
1942
|
+
throw new MailError("mail/bad-feedback-id-field",
|
|
1943
|
+
"feedbackId: " + f.key + " contains control characters");
|
|
1929
1944
|
}
|
|
1930
1945
|
parts.push(f.value);
|
|
1931
1946
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// markup-escape — the shared XML/HTML text + attribute escaper used wherever
|
|
4
|
+
// the framework SERIALIZES a value into markup (mail report / DAV / autoconfig
|
|
5
|
+
// XML, the Azure blob XML API, and — once routed — the b.guardHtml / b.guardSvg
|
|
6
|
+
// sanitizer output). Distinct from markup-tokenizer (which LEXES markup for the
|
|
7
|
+
// sanitizers) and xml-c14n (which canonicalizes for XMLDSig with a different
|
|
8
|
+
// escape set, e.g. \r\n\t and NO ">").
|
|
9
|
+
//
|
|
10
|
+
// markupEscape(str, opts) escapes the four always-dangerous markup
|
|
11
|
+
// metacharacters — & < > " — to their entity forms, with "&" first so the
|
|
12
|
+
// entities it emits are not double-escaped. The one axis that genuinely varies
|
|
13
|
+
// across callers is the apostrophe, so it is a parameter:
|
|
14
|
+
// opts.apos omitted / falsy → "'" is left as-is (element content and
|
|
15
|
+
// double-quoted attributes don't require it escaped)
|
|
16
|
+
// opts.apos === "'" → HTML numeric form
|
|
17
|
+
// opts.apos === "'" → XML named form
|
|
18
|
+
// Input COERCION stays with the caller (some map null/undefined or any
|
|
19
|
+
// non-string to "" before serializing); markupEscape String()s defensively so
|
|
20
|
+
// a stray number/boolean still escapes rather than throwing.
|
|
21
|
+
function markupEscape(str, opts) {
|
|
22
|
+
var s = String(str)
|
|
23
|
+
.replace(/&/g, "&")
|
|
24
|
+
.replace(/</g, "<")
|
|
25
|
+
.replace(/>/g, ">")
|
|
26
|
+
.replace(/"/g, """);
|
|
27
|
+
var apos = opts && opts.apos;
|
|
28
|
+
return apos ? s.replace(/'/g, apos) : s;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { markupEscape: markupEscape };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// markup-tokenizer — neutral lexing helpers shared by the markup sanitizers
|
|
4
|
+
// (b.guardHtml, b.guardSvg) and the BIMI SVG Tiny PS validator (b.mail.bimi).
|
|
5
|
+
// Each of those keeps its OWN tokenizer loop because their security postures
|
|
6
|
+
// genuinely diverge — guard-html/guard-svg recover leniently from a truncated
|
|
7
|
+
// tag (emit what was scanned, keep going), while the BIMI validator fails
|
|
8
|
+
// closed (throws on any malformation), and each recognizes a different set of
|
|
9
|
+
// declaration forms (`<?`, `<!ENTITY>`, balanced `<!DOCTYPE [...]>`). What they
|
|
10
|
+
// share verbatim is the one quote-aware step below; centralizing it keeps the
|
|
11
|
+
// attribute-quote handling — the part a bypass hides in — identical everywhere.
|
|
12
|
+
|
|
13
|
+
// scanToTagEnd(s, from, len) — advance from `from` (the index just past the
|
|
14
|
+
// opening "<") to the tag's closing ">", treating a ">" that appears inside a
|
|
15
|
+
// single- or double-quoted attribute value as a literal, not a terminator
|
|
16
|
+
// (e.g. `<a title="a>b">` ends at the SECOND ">"). Returns the index of the
|
|
17
|
+
// terminating ">", or `len` if the tag is unterminated (the caller decides
|
|
18
|
+
// whether that is lenient end-of-input or a hard error).
|
|
19
|
+
function scanToTagEnd(s, from, len) {
|
|
20
|
+
var p = from;
|
|
21
|
+
var inQuote = "";
|
|
22
|
+
while (p < len) {
|
|
23
|
+
var ch = s.charAt(p);
|
|
24
|
+
if (inQuote) {
|
|
25
|
+
if (ch === inQuote) inQuote = "";
|
|
26
|
+
} else {
|
|
27
|
+
if (ch === '"' || ch === "'") inQuote = ch;
|
|
28
|
+
else if (ch === ">") break;
|
|
29
|
+
}
|
|
30
|
+
p += 1;
|
|
31
|
+
}
|
|
32
|
+
return p;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// splitTagNameAttrs(inner, tagNameRe) — given a start tag's inner text (the
|
|
36
|
+
// bytes between "<" and ">", with any trailing self-closing "/" already
|
|
37
|
+
// stripped by the caller), split it into the lower-cased `tagName` and the raw
|
|
38
|
+
// `attrSrc` remainder. `tagNameRe` is the caller's tag-name grammar (it must
|
|
39
|
+
// capture the name in group 1) — HTML allows `[A-Za-z][A-Za-z0-9:-]*`, while
|
|
40
|
+
// the XML-family grammars (SVG, BIMI SVG Tiny PS) also allow `_`. A tag whose
|
|
41
|
+
// start does not match the grammar yields an empty name + empty attrSrc (the
|
|
42
|
+
// caller treats it as a bogus tag).
|
|
43
|
+
function splitTagNameAttrs(inner, tagNameRe) {
|
|
44
|
+
var nameMatch = inner.match(tagNameRe);
|
|
45
|
+
return {
|
|
46
|
+
tagName: nameMatch ? nameMatch[1].toLowerCase() : "",
|
|
47
|
+
attrSrc: nameMatch ? inner.slice(nameMatch[0].length) : "",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// htmlCommentEnd(s, lt) — given that an HTML comment opens at index `lt`
|
|
52
|
+
// (s.startsWith("<!--", lt)), return the index ONE PAST the comment's
|
|
53
|
+
// terminator per the WHATWG HTML tokenizer, not just the legacy "-->" form.
|
|
54
|
+
// A browser also closes a comment at "--!>" (comment-end-bang state) and
|
|
55
|
+
// ABRUPTLY closes one that begins "<!-->" or "<!--->". A scanner that honours
|
|
56
|
+
// only "-->" therefore disagrees with the browser about where the comment
|
|
57
|
+
// ends, so markup AFTER an early "--!>" / abrupt close is swallowed as inert
|
|
58
|
+
// comment by the sanitizer but parsed as a LIVE element by the browser (mXSS,
|
|
59
|
+
// the comment-parser differential). Returns -1 if the comment is unterminated
|
|
60
|
+
// so each caller keeps its own policy (lenient end-of-input vs. fail-closed
|
|
61
|
+
// throw). NOTE: HTML/SVG-in-HTML only — XML comments do NOT have these forms.
|
|
62
|
+
function htmlCommentEnd(s, lt) {
|
|
63
|
+
var i = lt + 4; // first char after "<!--"
|
|
64
|
+
if (s.charAt(i) === ">") return i + 1; // <!--> abrupt close
|
|
65
|
+
if (s.charAt(i) === "-" && s.charAt(i + 1) === ">") return i + 2; // <!---> abrupt close
|
|
66
|
+
var a = s.indexOf("-->", i);
|
|
67
|
+
var b = s.indexOf("--!>", i);
|
|
68
|
+
if (a === -1 && b === -1) return -1; // unterminated
|
|
69
|
+
if (a === -1) return b + 4;
|
|
70
|
+
if (b === -1) return a + 3;
|
|
71
|
+
return a <= b ? a + 3 : b + 4; // earliest terminator wins
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
scanToTagEnd: scanToTagEnd,
|
|
76
|
+
splitTagNameAttrs: splitTagNameAttrs,
|
|
77
|
+
htmlCommentEnd: htmlCommentEnd,
|
|
78
|
+
};
|
package/lib/mcp-tool-registry.js
CHANGED
|
@@ -65,8 +65,8 @@ var validateOpts = require("./validate-opts");
|
|
|
65
65
|
var { McpError } = require("./framework-error");
|
|
66
66
|
|
|
67
67
|
var bCrypto = lazyRequire(function () { return require("./crypto"); });
|
|
68
|
-
var
|
|
69
|
-
var C
|
|
68
|
+
var auditEmit = require("./audit-emit");
|
|
69
|
+
var C = require("./constants");
|
|
70
70
|
|
|
71
71
|
// Shared name-shape regex across mcp.js / mcp-tool-registry.js / a2a-tasks.js
|
|
72
72
|
// — every agent-protocol identifier follows the same RFC-3986-unreserved
|
|
@@ -80,15 +80,7 @@ var ALLOWED_ALGS = Object.freeze([
|
|
|
80
80
|
"slh-dsa-shake-256f",
|
|
81
81
|
]);
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
audit().safeEmit({
|
|
86
|
-
action: action,
|
|
87
|
-
outcome: outcome || "success",
|
|
88
|
-
metadata: metadata,
|
|
89
|
-
});
|
|
90
|
-
} catch (_e) { /* best-effort */ }
|
|
91
|
-
}
|
|
83
|
+
var _emitAudit = auditEmit.emit;
|
|
92
84
|
|
|
93
85
|
function _validateAlg(alg, label) {
|
|
94
86
|
if (alg === undefined || alg === null) return "ml-dsa-87";
|
|
@@ -175,20 +167,31 @@ function create(opts) {
|
|
|
175
167
|
throw new McpError("mcp/bad-registry-opts",
|
|
176
168
|
"toolRegistry.create: opts required (tools + signingKey)", true);
|
|
177
169
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
170
|
+
validateOpts.shape(opts, {
|
|
171
|
+
tools: function (value) {
|
|
172
|
+
if (!Array.isArray(value)) {
|
|
173
|
+
throw new McpError("mcp/bad-registry-opts",
|
|
174
|
+
"toolRegistry.create: opts.tools must be an array", true);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
signingKey: { rule: "required-string", code: "mcp/bad-signing-key", label: "toolRegistry.create.signingKey" },
|
|
178
|
+
verifyingKey: { rule: "optional-string", code: "mcp/bad-verifying-key", label: "toolRegistry.create.verifyingKey" },
|
|
179
|
+
alg: function (value) {
|
|
180
|
+
// undefined → default applied below; any present value is registry-checked.
|
|
181
|
+
_validateAlg(value, "toolRegistry.create.alg");
|
|
182
|
+
},
|
|
183
|
+
ttlMs: function (value) {
|
|
184
|
+
// undefined → registry default applied below; any present value must be a
|
|
185
|
+
// finite millisecond count at or above the 1-second floor.
|
|
186
|
+
if (value === undefined) return;
|
|
187
|
+
if (typeof value !== "number" || !isFinite(value) || value < 1000) { // minimum-ttl threshold (1 second), not bytes
|
|
188
|
+
throw new McpError("mcp/bad-ttl",
|
|
189
|
+
"toolRegistry.create.ttlMs must be >= 1000 ms", true);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
}, "toolRegistry.create", McpError, "mcp/bad-registry-opts");
|
|
186
193
|
var alg = _validateAlg(opts.alg, "toolRegistry.create.alg");
|
|
187
194
|
var defaultTtlMs = opts.ttlMs !== undefined ? opts.ttlMs : C.TIME.minutes(5);
|
|
188
|
-
if (typeof defaultTtlMs !== "number" || !isFinite(defaultTtlMs) || defaultTtlMs < 1000) { // minimum-ttl threshold (1 second), not bytes
|
|
189
|
-
throw new McpError("mcp/bad-ttl",
|
|
190
|
-
"toolRegistry.create.ttlMs must be >= 1000 ms", true);
|
|
191
|
-
}
|
|
192
195
|
|
|
193
196
|
var signingKey = opts.signingKey;
|
|
194
197
|
var verifyingKey = opts.verifyingKey || null;
|