@blamejs/core 0.8.42 → 0.8.49
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 +93 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/network.js
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.network
|
|
4
|
+
* @featured true
|
|
5
|
+
* @nav Network
|
|
6
|
+
* @title Network
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* Framework network helpers — DNS-over-HTTPS dispatch, TLS
|
|
10
|
+
* configuration, OCSP/CT validation, NTP/NTS-KE bootstrap.
|
|
11
|
+
*
|
|
12
|
+
* `b.network` is the umbrella facade over the framework's outbound-
|
|
13
|
+
* network surface: DNS (default DoH on, optional DoT, lookup cache
|
|
14
|
+
* with TTL bound), TLS trust store (CA bundle / system trust /
|
|
15
|
+
* ignored-cert opt-in), proxy resolution from `HTTP_PROXY` /
|
|
16
|
+
* `HTTPS_PROXY` / `NO_PROXY`, NTP / NTS-KE drift checks, SMTP
|
|
17
|
+
* policy (MTA-STS / DANE / TLS-RPT), heartbeat watchdog, byte
|
|
18
|
+
* quota, SSRF allowlist, and socket-level defaults
|
|
19
|
+
* (TCP_NODELAY / SO_KEEPALIVE).
|
|
20
|
+
*
|
|
21
|
+
* `bootFromEnv` reads BLAMEJS_* environment variables once at
|
|
22
|
+
* startup and applies the union to the live facade — operators
|
|
23
|
+
* wire it from a process-supervisor's env without touching code.
|
|
24
|
+
* `snapshot` returns a redacted view of the current configuration
|
|
25
|
+
* for the operations dashboard. `applyToSocket` is the per-socket
|
|
26
|
+
* tuning hook for primitives building their own server (`tls`,
|
|
27
|
+
* `wsServer`, etc.).
|
|
28
|
+
*
|
|
29
|
+
* @card
|
|
30
|
+
* Framework network helpers — DNS-over-HTTPS dispatch, TLS configuration, OCSP/CT validation, NTP/NTS-KE bootstrap.
|
|
31
|
+
*/
|
|
2
32
|
|
|
33
|
+
var byteQuota = require("./network-byte-quota");
|
|
3
34
|
var ntpCheck = require("./ntp-check");
|
|
4
35
|
var nts = require("./network-nts");
|
|
5
36
|
var dns = require("./network-dns");
|
|
@@ -72,6 +103,28 @@ function _socketDefaults() {
|
|
|
72
103
|
};
|
|
73
104
|
}
|
|
74
105
|
|
|
106
|
+
/**
|
|
107
|
+
* @primitive b.network.applyToSocket
|
|
108
|
+
* @signature b.network.applyToSocket(socket)
|
|
109
|
+
* @since 0.7.68
|
|
110
|
+
* @related b.network.bootFromEnv, b.network.snapshot
|
|
111
|
+
*
|
|
112
|
+
* Apply the framework's socket defaults (`TCP_NODELAY`,
|
|
113
|
+
* `SO_KEEPALIVE` + initial-delay) to a freshly-created
|
|
114
|
+
* `net.Socket` / `tls.TLSSocket`. Best-effort: a socket that has
|
|
115
|
+
* already errored, lacks the setter methods, or rejects the call
|
|
116
|
+
* is left as-is. Returns the same socket. Used by primitives that
|
|
117
|
+
* build their own server (`b.tls`, `b.wsServer`, `b.smtp`) so
|
|
118
|
+
* every socket on the wire follows the same tuning.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* var net = require("net");
|
|
122
|
+
* var s = new net.Socket();
|
|
123
|
+
* var ret = b.network.applyToSocket(s);
|
|
124
|
+
* ret === s;
|
|
125
|
+
* // → true
|
|
126
|
+
* s.destroy();
|
|
127
|
+
*/
|
|
75
128
|
function applyToSocket(socket) {
|
|
76
129
|
if (!socket) return socket;
|
|
77
130
|
try {
|
|
@@ -103,6 +156,34 @@ var ntpFacade = {
|
|
|
103
156
|
nts: nts,
|
|
104
157
|
};
|
|
105
158
|
|
|
159
|
+
/**
|
|
160
|
+
* @primitive b.network.bootFromEnv
|
|
161
|
+
* @signature b.network.bootFromEnv(opts)
|
|
162
|
+
* @since 0.7.68
|
|
163
|
+
* @related b.network.snapshot, b.network.applyToSocket
|
|
164
|
+
*
|
|
165
|
+
* Read `BLAMEJS_*` environment variables once and apply the union to
|
|
166
|
+
* the live network facade. Recognised keys cover NTP servers /
|
|
167
|
+
* timeout / drift thresholds, DNS servers / result-order / family /
|
|
168
|
+
* lookup-timeout / cache-TTL / DoH URL or provider / DoT host+port,
|
|
169
|
+
* `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`, extra-CA file or
|
|
170
|
+
* directory, `BLAMEJS_USE_SYSTEM_TRUST`, and the socket
|
|
171
|
+
* `TCP_NODELAY` / `SO_KEEPALIVE` defaults. Returns an `applied`
|
|
172
|
+
* report — exactly which keys took effect. Audits
|
|
173
|
+
* `network.boot.from_env` unless `opts.audit:false`.
|
|
174
|
+
*
|
|
175
|
+
* @opts
|
|
176
|
+
* env: object, // default process.env — pass a fixture object in tests
|
|
177
|
+
* audit: boolean, // default true — emit `network.boot.from_env`
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* var applied = b.network.bootFromEnv({
|
|
181
|
+
* env: { BLAMEJS_NTP_SERVERS: "time.cloudflare.com,time.google.com" },
|
|
182
|
+
* audit: false,
|
|
183
|
+
* });
|
|
184
|
+
* applied.ntp.servers;
|
|
185
|
+
* // → 2
|
|
186
|
+
*/
|
|
106
187
|
function bootFromEnv(opts) {
|
|
107
188
|
opts = opts || {};
|
|
108
189
|
validateOpts(opts, ["env", "audit"], "network.bootFromEnv");
|
|
@@ -181,6 +262,24 @@ function bootFromEnv(opts) {
|
|
|
181
262
|
return applied;
|
|
182
263
|
}
|
|
183
264
|
|
|
265
|
+
/**
|
|
266
|
+
* @primitive b.network.snapshot
|
|
267
|
+
* @signature b.network.snapshot()
|
|
268
|
+
* @since 0.7.68
|
|
269
|
+
* @related b.network.bootFromEnv
|
|
270
|
+
*
|
|
271
|
+
* Return a redacted snapshot of the network facade's current
|
|
272
|
+
* configuration: NTP servers + drift thresholds, DNS state (servers,
|
|
273
|
+
* result-order, family, DoH/DoT, cache TTL), proxy resolution, TLS
|
|
274
|
+
* trust-store size + system-trust flag, heartbeat statuses, and
|
|
275
|
+
* socket defaults. Cheap; safe to call from a `/healthz` or
|
|
276
|
+
* operations endpoint. No secrets are returned.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* var snap = b.network.snapshot();
|
|
280
|
+
* typeof snap.tls.caCount;
|
|
281
|
+
* // → "number"
|
|
282
|
+
*/
|
|
184
283
|
function snapshot() {
|
|
185
284
|
return {
|
|
186
285
|
ntp: {
|
|
@@ -228,6 +327,10 @@ module.exports = {
|
|
|
228
327
|
tlsRpt: smtpPolicy.tlsRpt,
|
|
229
328
|
},
|
|
230
329
|
allowlist: { create: ssrfGuard.createAllowlist },
|
|
330
|
+
byteQuota: {
|
|
331
|
+
create: byteQuota.create,
|
|
332
|
+
ByteQuotaError: byteQuota.ByteQuotaError,
|
|
333
|
+
},
|
|
231
334
|
socket: {
|
|
232
335
|
setDefaultNoDelay: _setSocketNoDelay,
|
|
233
336
|
setDefaultKeepAlive: _setSocketKeepAlive,
|
package/lib/notify.js
CHANGED
|
@@ -1,50 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.notify
|
|
3
|
+
* @module b.notify
|
|
4
|
+
* @nav Communication
|
|
5
|
+
* @title Notify
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* redaction (b.redact.redact). Notify never re-implements any of these
|
|
12
|
-
* — its job is to coordinate them around a transport abstraction.
|
|
7
|
+
* @intro
|
|
8
|
+
* Pluggable notification dispatcher. One contract — `{ name, send }`
|
|
9
|
+
* — adapts any transport (Slack incoming-webhook, Discord, Microsoft
|
|
10
|
+
* Teams, PagerDuty, Twilio, FCM / APNs operator shim, plain
|
|
11
|
+
* developer log) and the dispatcher coordinates retry / timeout /
|
|
12
|
+
* circuit-breaker / observability / audit / PII redaction around it.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
14
|
+
* Composition over reinvention: every cross-cutting concern routes
|
|
15
|
+
* through an existing primitive — `b.retry.withRetry` for backoff +
|
|
16
|
+
* classification, `b.safeAsync.withTimeout` for per-call timeouts,
|
|
17
|
+
* `b.retry.CircuitBreaker` for per-channel breakers, `b.observability.
|
|
18
|
+
* tap` for span+counter wrapping, `b.audit.safeEmit` for audit rows
|
|
19
|
+
* (drop-silent on transport failure — observability sinks must not
|
|
20
|
+
* crash send), `b.requestHelpers.extractActorContext` for the 5 W's,
|
|
21
|
+
* `b.safeUrl.parse` + `b.httpClient.request` for HTTP I/O,
|
|
22
|
+
* `b.redact.redact` for default PII scrubbing of message contents
|
|
23
|
+
* before they hit the audit chain.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
25
|
+
* Built-in transports: `httpJson` (POST JSON / form to a URL — the
|
|
26
|
+
* workhorse for Slack / Discord / generic incoming-webhook
|
|
27
|
+
* integrations, with optional `b.webhook.signer` injection),
|
|
28
|
+
* `log` (fire-and-forget developer logger via `b.log`),
|
|
29
|
+
* `test` (captures sends to `.sent[]` for fixture inspection).
|
|
30
|
+
* Operators bring their own SDK shims for Twilio / FCM / APNs /
|
|
31
|
+
* Slack-API (the framework intentionally ships no vendor SDKs).
|
|
26
32
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* // with err.statusCode in b.retry.RETRYABLE_HTTP_STATUS or
|
|
33
|
-
* // err.code in b.retry.RETRYABLE_NET_ERRORS — retry classifies
|
|
34
|
-
* // them automatically. Operators can also set err.transient =
|
|
35
|
-
* // true for shapes outside that classification.
|
|
36
|
-
* },
|
|
37
|
-
* }
|
|
33
|
+
* Out of scope by design: template rendering (use `b.template`),
|
|
34
|
+
* recipient preferences (operator concern), replacing `b.mail`
|
|
35
|
+
* (SMTP / MIME stays its own primitive), replacing
|
|
36
|
+
* `b.websocketChannels` (transient pub/sub vs retry-on-fail
|
|
37
|
+
* delivery).
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* transports; the framework provides the contract + httpJson +
|
|
42
|
-
* log + test built-ins.
|
|
43
|
-
* - Render templates / handle i18n: operator uses b.template + b.i18n.
|
|
44
|
-
* - Track recipient preferences: operator app concern.
|
|
45
|
-
* - Replace b.mail (SMTP/MIME stays its own primitive).
|
|
46
|
-
* - Replace b.websocketChannels (different abstraction —
|
|
47
|
-
* transient pub/sub, not retry-on-fail delivery).
|
|
39
|
+
* @card
|
|
40
|
+
* Pluggable notification dispatcher.
|
|
48
41
|
*/
|
|
49
42
|
|
|
50
43
|
var lazyRequire = require("./lazy-require");
|
|
@@ -162,9 +155,44 @@ function _validateCreateOpts(opts) {
|
|
|
162
155
|
|
|
163
156
|
// ---- Built-in transports ----
|
|
164
157
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
158
|
+
/**
|
|
159
|
+
* @primitive b.notify.transports.httpJson
|
|
160
|
+
* @signature b.notify.transports.httpJson(opts)
|
|
161
|
+
* @since 0.6.0
|
|
162
|
+
* @status stable
|
|
163
|
+
* @related b.notify.create, b.webhook.signer
|
|
164
|
+
*
|
|
165
|
+
* Built-in transport that POSTs the message as JSON (or
|
|
166
|
+
* `application/x-www-form-urlencoded`) to a URL via `b.httpClient.
|
|
167
|
+
* request`. Validates the URL at create time so bad URLs surface at
|
|
168
|
+
* boot, not at first send. Optional `signing` slot accepts any object
|
|
169
|
+
* with a `sign(body) → headers | { headers }` function — drop a
|
|
170
|
+
* `b.webhook.signer` straight in for HMAC / PQC signed deliveries.
|
|
171
|
+
* The default success classifier accepts HTTP 2xx; non-success
|
|
172
|
+
* statuses throw a plain `Error` with `statusCode` set so
|
|
173
|
+
* `b.retry.isRetryable` classifies the response (429 / 503 / network
|
|
174
|
+
* errors retry; permanent rejections don't).
|
|
175
|
+
*
|
|
176
|
+
* @opts
|
|
177
|
+
* url: string, // required
|
|
178
|
+
* method: "POST" | "PUT" | "PATCH", // default "POST"
|
|
179
|
+
* bodyFormat: "json" | "form", // default "json"
|
|
180
|
+
* headers: { [k]: string },
|
|
181
|
+
* signing: { sign(body) => headers | { headers } },
|
|
182
|
+
* successStatus: function (status) => boolean,
|
|
183
|
+
* allowHttp: boolean, // default false (HTTPS-only)
|
|
184
|
+
* allowInternal: boolean,
|
|
185
|
+
* httpClient: object, // override b.httpClient
|
|
186
|
+
* name: string, // for audit + logs
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* var b = require("@blamejs/core");
|
|
190
|
+
* var slack = b.notify.transports.httpJson({
|
|
191
|
+
* url: "https://hooks.slack.com/services/T0/B0/X",
|
|
192
|
+
* name: "slack",
|
|
193
|
+
* });
|
|
194
|
+
* // → { name: "slack", send: async function (message, sendOpts) { ... } }
|
|
195
|
+
*/
|
|
168
196
|
function httpJson(opts) {
|
|
169
197
|
if (!opts || typeof opts !== "object") {
|
|
170
198
|
throw _err("BAD_OPT", "notify.transports.httpJson: opts must be { url, ... }");
|
|
@@ -293,6 +321,49 @@ function testTransport() {
|
|
|
293
321
|
|
|
294
322
|
// ---- Public create ----
|
|
295
323
|
|
|
324
|
+
/**
|
|
325
|
+
* @primitive b.notify.create
|
|
326
|
+
* @signature b.notify.create(opts)
|
|
327
|
+
* @since 0.6.0
|
|
328
|
+
* @status stable
|
|
329
|
+
* @compliance soc2, gdpr
|
|
330
|
+
* @related b.notify.transports.httpJson
|
|
331
|
+
*
|
|
332
|
+
* Build a dispatcher bound to a set of named channels. Returns
|
|
333
|
+
* `{ send, sendBatch, queue, addChannel, channels, transport }`:
|
|
334
|
+
* `send` delivers one message through one channel with the full retry /
|
|
335
|
+
* timeout / breaker / span+counter / audit stack; `sendBatch` settles
|
|
336
|
+
* each input independently so one channel down doesn't fail the rest;
|
|
337
|
+
* `queue` enqueues onto a `b.queue` handle for out-of-band delivery;
|
|
338
|
+
* `addChannel` registers a new channel post-construction;
|
|
339
|
+
* `channels()` lists registered names; `transport(name)` exposes the
|
|
340
|
+
* raw transport handle for diagnostics. Each channel entry is either
|
|
341
|
+
* a transport object directly (`{ send, name? }`) or a config wrapper
|
|
342
|
+
* (`{ transport, retry?, breaker?, timeoutMs?, serialize? }`) so
|
|
343
|
+
* operators tune retry / breaker / timeout / serialize per channel.
|
|
344
|
+
*
|
|
345
|
+
* @opts
|
|
346
|
+
* channels: { [name]: transport | { transport, retry?, breaker?, timeoutMs?, serialize? } },
|
|
347
|
+
* audit: object, // b.audit handle
|
|
348
|
+
* auditSuccess: boolean, // default true
|
|
349
|
+
* auditFailures: boolean, // default true
|
|
350
|
+
* redact: function (message) => any, // default b.redact.redact
|
|
351
|
+
* defaultTimeoutMs: number, // default 30s, 0 disables
|
|
352
|
+
* defaultRetry: object, // b.retry.withRetry opts
|
|
353
|
+
* defaultBreaker: object, // b.retry.CircuitBreaker opts
|
|
354
|
+
* queue: { enqueue(name, payload), registerHandler? },
|
|
355
|
+
* clock: function () => number, // ms
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* var b = require("@blamejs/core");
|
|
359
|
+
* var notify = b.notify.create({
|
|
360
|
+
* channels: {
|
|
361
|
+
* slack: b.notify.transports.httpJson({ url: "https://hooks.slack.com/services/T0/B0/X" }),
|
|
362
|
+
* log: b.notify.transports.log(),
|
|
363
|
+
* },
|
|
364
|
+
* });
|
|
365
|
+
* // → { send, sendBatch, queue, addChannel, channels, transport }
|
|
366
|
+
*/
|
|
296
367
|
function create(opts) {
|
|
297
368
|
opts = opts || {};
|
|
298
369
|
validateOpts(opts, [
|
package/lib/ntp-check.js
CHANGED
|
@@ -1,29 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.ntpCheck
|
|
4
|
+
* @nav Production
|
|
5
|
+
* @title NTP Check
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Boot-time clock-drift verification against an external NTP / NTS-KE
|
|
9
|
+
* reference. The audit chain's `monotonicCounter` orders events
|
|
10
|
+
* deterministically even when the wall clock jumps, but `recordedAt`
|
|
11
|
+
* is the human-readable timestamp auditors rely on — a clock silently
|
|
12
|
+
* off by hours (container with no RTC sync, NTP daemon stopped)
|
|
13
|
+
* makes the audit trail misleading without ever surfacing as an
|
|
14
|
+
* error.
|
|
10
15
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
+
* What this does: sends a single SNTPv4 query over UDP/123 (RFC 5905)
|
|
17
|
+
* to one or more configured servers, computes drift as
|
|
18
|
+
* `serverTransmit - localMidpoint` (round-trip-corrected), returns
|
|
19
|
+
* the drift in milliseconds. Falls through a server list in order;
|
|
20
|
+
* the first success wins.
|
|
16
21
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
22
|
+
* What this does NOT do: continuous synchronization (the host OS's
|
|
23
|
+
* NTP daemon does that), authenticated NTP / NTS / autokey (the
|
|
24
|
+
* external reference is trust-on-first-query), or median-of-N
|
|
25
|
+
* server reconciliation (single-shot only).
|
|
21
26
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
+
* Policy thresholds at boot — wired into `b.db.init`:
|
|
28
|
+
*
|
|
29
|
+
* drift |x| < warnMs (5 min default) → info, continue
|
|
30
|
+
* drift |x| in [warnMs, fatalMs) → warning, continue
|
|
31
|
+
* drift |x| >= fatalMs (1 hr default) → refuse to boot
|
|
32
|
+
* (BLAMEJS_NTP_STRICT=1)
|
|
33
|
+
* NTP unreachable → warning, continue
|
|
34
|
+
* (network may not allow
|
|
35
|
+
* UDP/123 outbound)
|
|
36
|
+
*
|
|
37
|
+
* `b.ntpCheck.monitor` runs the same check on a recurring interval
|
|
38
|
+
* after boot and emits `system.ntp.checked` /
|
|
39
|
+
* `system.ntp.drift_warn` / `system.ntp.drift_fatal` /
|
|
40
|
+
* `system.ntp.unreachable` audit events plus an `ntp.drift_ms`
|
|
41
|
+
* observability gauge — so silent clock drift mid-flight surfaces
|
|
42
|
+
* in the same evidence stream as boot drift.
|
|
43
|
+
*
|
|
44
|
+
* @card
|
|
45
|
+
* Boot-time clock-drift verification against an external NTP / NTS-KE reference.
|
|
27
46
|
*/
|
|
28
47
|
var dgram = require("dgram");
|
|
29
48
|
var C = require("./constants");
|
|
@@ -50,6 +69,32 @@ var thresholds = {
|
|
|
50
69
|
fatalMs: DEFAULT_DRIFT_FATAL_MS,
|
|
51
70
|
};
|
|
52
71
|
|
|
72
|
+
/**
|
|
73
|
+
* @primitive b.ntpCheck.setThresholds
|
|
74
|
+
* @signature b.ntpCheck.setThresholds(opts)
|
|
75
|
+
* @since 0.7.30
|
|
76
|
+
* @status stable
|
|
77
|
+
* @related b.ntpCheck.getThresholds, b.ntpCheck.bootCheck
|
|
78
|
+
*
|
|
79
|
+
* Override the warn / fatal drift thresholds applied by `bootCheck`
|
|
80
|
+
* and `monitor`. Validates that both values are non-negative finite
|
|
81
|
+
* numbers and that `warnMs <= fatalMs` (a fatal floor below the
|
|
82
|
+
* warning threshold would mean every warning is also fatal — likely
|
|
83
|
+
* a typo). Throws `TypeError` on bad shapes and `RangeError` on the
|
|
84
|
+
* ordering invariant.
|
|
85
|
+
*
|
|
86
|
+
* @opts
|
|
87
|
+
* warnMs: 300000, // ms; absolute drift at-or-above this logs warn
|
|
88
|
+
* fatalMs: 3600000, // ms; absolute drift at-or-above this refuses boot
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* b.ntpCheck.setThresholds({
|
|
92
|
+
* warnMs: 60000,
|
|
93
|
+
* fatalMs: 600000,
|
|
94
|
+
* });
|
|
95
|
+
* var t = b.ntpCheck.getThresholds();
|
|
96
|
+
* // → { warnMs: 60000, fatalMs: 600000 }
|
|
97
|
+
*/
|
|
53
98
|
function setThresholds(opts) {
|
|
54
99
|
opts = opts || {};
|
|
55
100
|
if (opts.warnMs !== undefined) {
|
|
@@ -70,6 +115,21 @@ function setThresholds(opts) {
|
|
|
70
115
|
}
|
|
71
116
|
}
|
|
72
117
|
|
|
118
|
+
/**
|
|
119
|
+
* @primitive b.ntpCheck.getThresholds
|
|
120
|
+
* @signature b.ntpCheck.getThresholds()
|
|
121
|
+
* @since 0.7.30
|
|
122
|
+
* @status stable
|
|
123
|
+
* @related b.ntpCheck.setThresholds
|
|
124
|
+
*
|
|
125
|
+
* Read the currently-effective warn / fatal drift thresholds. Returns
|
|
126
|
+
* a fresh object so mutating the result doesn't accidentally rewrite
|
|
127
|
+
* framework state.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* var t = b.ntpCheck.getThresholds();
|
|
131
|
+
* // → { warnMs: 300000, fatalMs: 3600000 }
|
|
132
|
+
*/
|
|
73
133
|
function getThresholds() {
|
|
74
134
|
return { warnMs: thresholds.warnMs, fatalMs: thresholds.fatalMs };
|
|
75
135
|
}
|
|
@@ -80,11 +140,29 @@ function _resetThresholdsForTest() {
|
|
|
80
140
|
}
|
|
81
141
|
|
|
82
142
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
143
|
+
* @primitive b.ntpCheck.querySingle
|
|
144
|
+
* @signature b.ntpCheck.querySingle(server, opts)
|
|
145
|
+
* @since 0.0.7
|
|
146
|
+
* @status stable
|
|
147
|
+
* @related b.ntpCheck.checkDrift, b.ntpCheck.bootCheck
|
|
148
|
+
*
|
|
149
|
+
* Send one SNTPv4 query to a named server over UDP/123 and resolve
|
|
150
|
+
* with `{ driftMs, serverTimeMs, server }` (round-trip-corrected
|
|
151
|
+
* drift). Rejects with `{ code, message }` where `code` is one of
|
|
152
|
+
* `ntp/timeout` (no reply within `timeoutMs`), `ntp/refused`
|
|
153
|
+
* (DNS / connection error), `ntp/bad-reply` (packet too short), or
|
|
154
|
+
* `ntp/unsynchronized` (Stratum-16 peer with zero transmit
|
|
155
|
+
* timestamp). IPv4 / IPv6 socket family is selected from the host
|
|
156
|
+
* literal so an `fd00::...` server doesn't fail with EINVAL.
|
|
157
|
+
*
|
|
158
|
+
* @opts
|
|
159
|
+
* port: 123, // UDP port (almost always 123)
|
|
160
|
+
* timeoutMs: 3000, // single-query timeout
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* b.ntpCheck.querySingle("time.cloudflare.com", { timeoutMs: 2000 })
|
|
164
|
+
* .then(function (r) { console.log("drift", r.driftMs, "ms"); })
|
|
165
|
+
* .catch(function (e) { console.error("ntp", e.code, e.message); });
|
|
88
166
|
*/
|
|
89
167
|
function querySingle(server, opts) {
|
|
90
168
|
opts = opts || {};
|
|
@@ -165,8 +243,28 @@ function querySingle(server, opts) {
|
|
|
165
243
|
}
|
|
166
244
|
|
|
167
245
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
246
|
+
* @primitive b.ntpCheck.checkDrift
|
|
247
|
+
* @signature b.ntpCheck.checkDrift(opts)
|
|
248
|
+
* @since 0.0.7
|
|
249
|
+
* @status stable
|
|
250
|
+
* @related b.ntpCheck.querySingle, b.ntpCheck.bootCheck
|
|
251
|
+
*
|
|
252
|
+
* Walk a server list in order; resolve with the first successful
|
|
253
|
+
* drift measurement (`{ driftMs, serverTimeMs, server }`). When
|
|
254
|
+
* every server in the list fails, resolves with
|
|
255
|
+
* `{ driftMs: null, error }` so the caller — typically `bootCheck` —
|
|
256
|
+
* can decide whether unreachable NTP is fatal or a soft warning.
|
|
257
|
+
*
|
|
258
|
+
* @opts
|
|
259
|
+
* servers: ["time.cloudflare.com", "pool.ntp.org"],
|
|
260
|
+
* port: 123,
|
|
261
|
+
* timeoutMs: 3000,
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* var result = await b.ntpCheck.checkDrift({
|
|
265
|
+
* servers: ["time.cloudflare.com", "pool.ntp.org"],
|
|
266
|
+
* });
|
|
267
|
+
* // → { driftMs: 12, serverTimeMs: 1714694400000, server: "time.cloudflare.com" }
|
|
170
268
|
*/
|
|
171
269
|
async function checkDrift(opts) {
|
|
172
270
|
opts = opts || {};
|
|
@@ -183,9 +281,37 @@ async function checkDrift(opts) {
|
|
|
183
281
|
}
|
|
184
282
|
|
|
185
283
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
284
|
+
* @primitive b.ntpCheck.bootCheck
|
|
285
|
+
* @signature b.ntpCheck.bootCheck(opts)
|
|
286
|
+
* @since 0.0.7
|
|
287
|
+
* @status stable
|
|
288
|
+
* @related b.ntpCheck.checkDrift, b.ntpCheck.monitor, b.ntpCheck.setThresholds
|
|
289
|
+
*
|
|
290
|
+
* Boot-time clock-drift check that integrates with the framework's
|
|
291
|
+
* logging policy. Resolves with
|
|
292
|
+
* `{ ok, severity, driftMs, server, message }` where `severity` is
|
|
293
|
+
* `info` / `warning` / `fatal`. The framework's `b.db.init` calls
|
|
294
|
+
* this and refuses to boot when `ok === false` and the operator has
|
|
295
|
+
* set `BLAMEJS_NTP_STRICT=1`. NTP unreachable returns
|
|
296
|
+
* `severity: "warning"` (network may not allow UDP/123 outbound) so
|
|
297
|
+
* the boot doesn't fail closed without operator intent.
|
|
298
|
+
*
|
|
299
|
+
* @opts
|
|
300
|
+
* servers: ["time.cloudflare.com", "pool.ntp.org"],
|
|
301
|
+
* port: 123,
|
|
302
|
+
* timeoutMs: 3000,
|
|
303
|
+
* driftWarnMs: 300000, // override registered warn threshold
|
|
304
|
+
* driftFatalMs: 3600000, // override registered fatal threshold
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* var result = await b.ntpCheck.bootCheck({
|
|
308
|
+
* servers: ["time.cloudflare.com"],
|
|
309
|
+
* driftWarnMs: 60000,
|
|
310
|
+
* driftFatalMs: 600000,
|
|
311
|
+
* });
|
|
312
|
+
* // → { ok: true, severity: "info", driftMs: 12,
|
|
313
|
+
* // server: "time.cloudflare.com",
|
|
314
|
+
* // message: "clock drift +12ms from time.cloudflare.com" }
|
|
189
315
|
*/
|
|
190
316
|
async function bootCheck(opts) {
|
|
191
317
|
opts = opts || {};
|
|
@@ -232,27 +358,42 @@ async function bootCheck(opts) {
|
|
|
232
358
|
};
|
|
233
359
|
}
|
|
234
360
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
//
|
|
255
|
-
|
|
361
|
+
/**
|
|
362
|
+
* @primitive b.ntpCheck.monitor
|
|
363
|
+
* @signature b.ntpCheck.monitor(opts)
|
|
364
|
+
* @since 0.7.30
|
|
365
|
+
* @status stable
|
|
366
|
+
* @related b.ntpCheck.bootCheck, b.audit.safeEmit, b.observability.safeEvent
|
|
367
|
+
*
|
|
368
|
+
* Periodic drift monitor — runs `bootCheck` on a recurring interval
|
|
369
|
+
* and emits audit + observability events on threshold crossings.
|
|
370
|
+
* Returns a handle with `.stop()` for graceful shutdown. Audit
|
|
371
|
+
* emissions: `system.ntp.checked` on every tick,
|
|
372
|
+
* `system.ntp.drift_warn` and `system.ntp.drift_fatal` on threshold
|
|
373
|
+
* crossings, `system.ntp.unreachable` when every server in the list
|
|
374
|
+
* failed. Observability gauge `ntp.drift_ms` rides every successful
|
|
375
|
+
* check. The optional `onDrift` hook fires only when `severity`
|
|
376
|
+
* is `warning` or `fatal`, so operators can page on drift without
|
|
377
|
+
* inspecting every healthy tick.
|
|
378
|
+
*
|
|
379
|
+
* @opts
|
|
380
|
+
* intervalMs: 900000, // tick cadence
|
|
381
|
+
* servers: ["time.cloudflare.com", "pool.ntp.org"],
|
|
382
|
+
* driftWarnMs: 2000,
|
|
383
|
+
* driftFatalMs: 30000,
|
|
384
|
+
* audit: true, // emit audit events
|
|
385
|
+
* onDrift: function (result) {}, // operator hook
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* var mon = b.ntpCheck.monitor({
|
|
389
|
+
* intervalMs: 900000,
|
|
390
|
+
* servers: ["time.cloudflare.com", "pool.ntp.org"],
|
|
391
|
+
* driftWarnMs: 2000,
|
|
392
|
+
* driftFatalMs: 30000,
|
|
393
|
+
* onDrift: function (r) { console.warn("ntp drift", r.driftMs); },
|
|
394
|
+
* });
|
|
395
|
+
* await mon.stop();
|
|
396
|
+
*/
|
|
256
397
|
function monitor(opts) {
|
|
257
398
|
opts = opts || {};
|
|
258
399
|
var intervalMs = opts.intervalMs || C.TIME.minutes(15);
|