@blamejs/core 0.7.107 → 0.8.4
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 +41 -1
- package/NOTICE +17 -1
- package/README.md +4 -3
- package/index.js +15 -0
- package/lib/asyncapi-bindings.js +160 -0
- package/lib/asyncapi-traits.js +143 -0
- package/lib/asyncapi.js +531 -0
- package/lib/audit-sign.js +1 -1
- package/lib/audit.js +68 -2
- package/lib/auth/acr-vocabulary.js +265 -0
- package/lib/auth/auth-time-tracker.js +111 -0
- package/lib/auth/elevation-grant.js +306 -0
- package/lib/auth/jwt.js +13 -0
- package/lib/auth/lockout.js +16 -3
- package/lib/auth/oauth.js +15 -1
- package/lib/auth/password.js +22 -2
- package/lib/auth/sd-jwt-vc-issuer.js +2 -2
- package/lib/auth/sd-jwt-vc.js +7 -2
- package/lib/auth/step-up-policy.js +335 -0
- package/lib/auth/step-up.js +445 -0
- package/lib/break-glass.js +53 -14
- package/lib/cache-redis.js +1 -1
- package/lib/cache.js +6 -1
- package/lib/cli.js +3 -3
- package/lib/cluster.js +24 -1
- package/lib/compliance-ai-act-logging.js +190 -0
- package/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/compliance-ai-act-risk.js +189 -0
- package/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/compliance-ai-act.js +558 -0
- package/lib/compliance.js +12 -2
- package/lib/config-drift.js +2 -2
- package/lib/crypto-field.js +21 -1
- package/lib/crypto.js +114 -1
- package/lib/db.js +35 -4
- package/lib/dev.js +30 -3
- package/lib/dual-control.js +19 -1
- package/lib/external-db.js +10 -0
- package/lib/file-upload.js +30 -3
- package/lib/flag-cache.js +136 -0
- package/lib/flag-evaluation-context.js +135 -0
- package/lib/flag-providers.js +279 -0
- package/lib/flag-targeting.js +210 -0
- package/lib/flag.js +284 -0
- package/lib/guard-all.js +33 -16
- package/lib/guard-csv.js +16 -2
- package/lib/guard-html.js +35 -0
- package/lib/guard-svg.js +20 -0
- package/lib/http-client.js +57 -11
- package/lib/inbox.js +391 -0
- package/lib/log-stream-syslog.js +8 -0
- package/lib/log-stream.js +1 -1
- package/lib/mail-arc-sign.js +372 -0
- package/lib/mail-auth.js +2 -0
- package/lib/mail.js +40 -0
- package/lib/middleware/ai-act-disclosure.js +166 -0
- package/lib/middleware/asyncapi-serve.js +136 -0
- package/lib/middleware/attach-user.js +25 -2
- package/lib/middleware/bearer-auth.js +71 -6
- package/lib/middleware/body-parser.js +13 -0
- package/lib/middleware/cors.js +10 -0
- package/lib/middleware/csrf-protect.js +34 -3
- package/lib/middleware/dpop.js +3 -3
- package/lib/middleware/flag-context.js +76 -0
- package/lib/middleware/host-allowlist.js +1 -1
- package/lib/middleware/index.js +15 -0
- package/lib/middleware/openapi-serve.js +143 -0
- package/lib/middleware/require-aal.js +2 -2
- package/lib/middleware/require-step-up.js +186 -0
- package/lib/middleware/trace-propagate.js +1 -1
- package/lib/mtls-ca.js +23 -29
- package/lib/mtls-engine-default.js +21 -1
- package/lib/network-tls.js +21 -6
- package/lib/object-store/sigv4-bucket-ops.js +41 -0
- package/lib/observability-otlp-exporter.js +35 -2
- package/lib/openapi-paths-builder.js +248 -0
- package/lib/openapi-schema-walk.js +192 -0
- package/lib/openapi-security.js +169 -0
- package/lib/openapi-yaml.js +154 -0
- package/lib/openapi.js +443 -0
- package/lib/outbox.js +3 -3
- package/lib/permissions.js +10 -1
- package/lib/pqc-agent.js +22 -1
- package/lib/pqc-software.js +195 -0
- package/lib/pubsub.js +8 -4
- package/lib/redact.js +26 -1
- package/lib/retention.js +26 -0
- package/lib/router.js +1 -0
- package/lib/scheduler.js +57 -1
- package/lib/session.js +3 -3
- package/lib/ssrf-guard.js +19 -4
- package/lib/static.js +12 -0
- package/lib/totp.js +16 -0
- package/lib/vault/index.js +3 -0
- package/lib/vault-aad.js +259 -0
- package/lib/vendor/MANIFEST.json +29 -0
- package/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/ws-client.js +978 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,47 @@ Pre-1.0 the surface is intentionally evolving — every release may
|
|
|
6
6
|
change something operators depend on. Read each entry before
|
|
7
7
|
upgrading across more than a few patches at a time.
|
|
8
8
|
|
|
9
|
-
## v0.
|
|
9
|
+
## v0.8.x
|
|
10
|
+
|
|
11
|
+
- **0.8.4** (2026-05-06) — Supply-chain scanner findings + outbound HTTP posture + npm-publish unblock. **Outbound network surface** — `b.observability.otlpExporter` no longer defaults to `globalThis.fetch`; the default transport is now `b.httpClient` (`node:https` through the framework's PQC-hybrid agent + cert-pinning + SSRF guard). The prior default leaked an outbound network surface that supply-chain scanners flagged because it sat outside the framework's TLS posture; operators on fetch-only edge runtimes still override via `opts.fetchImpl`. **Dev-mode child-process isolation** — `b.dev.create()` now lazy-requires `child_process` (was top-level — flagged on every install regardless of whether `b.dev` was used) and refuses to construct when `NODE_ENV=production` unless the operator passes `opts.allowProduction:true` with an audited reason. A misconfigured production deploy that accidentally wires the dev-mode restart loop now crashes loudly at boot rather than spawning shells on every save. **Outbound posture additions** — `b.httpClient.request` adds `responseMode: "always-resolve"` (every response resolves with `{statusCode, headers, body}` regardless of HTTP status) plus `onRedirect({from, to, hop, headersStripped, statusCode, method})` hook (operators can throw to abort or rewrite the redirect chain). `b.wsClient.connect` adds `urlFor(attempt)` and `tlsOptsFor(attempt)` per-dial overrides for between-reconnect URL / TLS rotation; the new URL is re-validated through `ssrfGuard` so a hostile upstream can't redirect a reconnecting client at a private address. `b.wsClient` swallows post-`close()` `ECONNRESET` / `EPIPE` so a clean shutdown doesn't surface a noisy unhandled-error event. `b.pqcAgent.create({ ecdhCurve })` accepts a caller-supplied stricter list — operators can drop a group from the framework default but cannot widen with non-PQ groups (the prior hardcoded value blocked legitimate per-deployment narrowing). **Crypto helpers** — `b.crypto.hashCertFingerprint(pem|der)` returns `{ hex, colon }` SHA3-512 digests and `b.crypto.isCertRevoked(pemOrDer, denyList)` does a constant-time match. **Scheduler surface** — `b.scheduler.register(name, intervalMs, fn)` shorthand for the every-N-ms registration shape; `b.scheduler.getStatus()` returns an aggregate health surface for probes / dashboards (started flag, isLeader, per-task list, totals). **npm-publish unblock** — `test/layer-0-primitives/sd-jwt-vc.test.js` was asserting `DEFAULT_ALG === "ES256"` after v0.8.1 flipped the default to `ML-DSA-87`; the assertion now matches the lib (and `DEFAULT_HASH_ALG` for `sha3-512`). v0.8.1 / v0.8.2 / v0.8.3 all failed the npm-publish gate on this single test; this release re-enables the publish workflow.
|
|
12
|
+
|
|
13
|
+
- **0.8.3** (2026-05-06) — Release-gate fixes + post-v0.8.2 hardening. Wiki primitive-section validator gate flagged `b.middleware.csrfProtect` opts-key drift — the `requireOrigin` opt added in v0.8.1 wasn't documented in the wiki seeder; now listed alongside `checkOrigin` / `allowedOrigins`. Gitleaks secret-scan gate flagged `{ privateKey, cipherText }` KEM-envelope shapes in `lib/crypto.js` error messages + `CHANGELOG.md` v0.8.0 entry as generic-api-key false positives — `.gitleaks.toml` adds an explicit regex allowlist for the parameter-name shape and pins the v0.8.0 commit fingerprints. Functional additions: `b.httpClient.request` adds `responseMode: "always-resolve"` opt — every request resolves with `{ statusCode, headers, body }` regardless of HTTP status (operators using the framework as an inbound-proxy upstream no longer have to wrap each call in a try/catch to recover the body of a 4xx/5xx). `b.wsClient` swallows post-close `ECONNRESET` / `EPIPE` errors so a clean `close()` doesn't surface a noisy unhandled-error event when the kernel races the FIN with an in-flight write. `SECURITY.md` documents the `allowInternal: true` test-pattern (legitimate same-host integration tests opt in explicitly with audited reason — never as a production default).
|
|
14
|
+
|
|
15
|
+
- **0.8.2** (2026-05-06) — eslint fixes for v0.8.1 npm-publish gate. `lib/guard-csv.js` bidi-prefix regex now uses explicit `\uXXXX` escapes (was tripping `no-irregular-whitespace` + `no-misleading-character-class` on the literal-codepoint form). `lib/redact.js` URL-bearer-query detector drops a redundant `\-` escape inside a character class. Functional behaviour unchanged from 0.8.1.
|
|
16
|
+
|
|
17
|
+
- **0.8.1** (2026-05-06) — Hardening sweep across audit emission, crypto defaults, auth bypass closure, storage / SQLi, HTTP/network surface, supply-chain pin, and observability gaps. Defense-in-depth fixes — no new operator-facing primitives.
|
|
18
|
+
|
|
19
|
+
**Audit emission** — `audit.safeEmit` now normalises non-canonical outcomes (`ok` / `fail` / `warning` / `duplicate` / `skip` → `success` / `failure`) and replaces hyphens in action-name segments with underscores. The strict regex enforced by `audit.record` was silently dropping events from `b.flag` / `b.outbox` / `b.inbox` / `b.session` (idle / absolute / fingerprint-drift) / `b.db` (integrity-check) / `b.compliance.aiAct` (every Annex III biometric-id log on the operator-facing kinds with hyphens) / `b.config-drift` (low-severity drift) / `b.log-stream` (sink-failure) / `b.pubsub` (publish + publish-failed — also fixes a positional-signature bug at the call site). Two new `codebase-patterns` detectors (`audit-action-with-hyphen`, `non-canonical-audit-outcome`) catch new sites at gate time. Chain-write integrity failures now emit `system.audit.chain_write_dropped` to observability so operators alerting on rate-drop see something even when audit itself is the broken sink.
|
|
20
|
+
|
|
21
|
+
**Crypto defaults** — `b.mtlsCa` `caKeySealedMode` default flipped from `"auto"` to `"required"`; the legacy `"auto"` mode (load whichever exists, fall back to plaintext when no sealed file pre-exists) is removed. Operators wanting plaintext explicitly opt in via `caKeySealedMode: "disabled"` with audited reason. `b.network.tls` default key-share preference list now leads with `SecP384r1MLKEM1024` (the highest-PQ hybrid registered in `TLS_GROUP_PREFERENCE`) and drops `secp256r1` (P-256 was forbidden as a default per the framework's PQC-first hard rule). `b.auth.sdJwtVc` defaults to `ML-DSA-87` + `sha3-512`; the previous `ES256` + `sha-256` defaults shipped classical P-256 + SHA-256 credentials operators would have to re-issue at the EU eIDAS deadline. `b.crypto.encrypt` now emits `system.crypto.hybrid_disabled` audit when called with only an ML-KEM public key (silent fallback to KEM-only used to mask the missing P-384 leg). `b.auth.totp` emits `auth.totp.algorithm_downgraded` audit on every SHA-256 enrolment / verification.
|
|
22
|
+
|
|
23
|
+
**Auth bypass closure** — `b.breakGlass` `policy.requireScope` is now actually enforced at `grant()` time (was accepted, persisted, and surfaced via `policyGet` but never consulted). `b.permissions` `requireMfa: true` defaults to a 15-minute `mfaWindowMs` floor when neither route nor role supplies one (a stolen long-lived cookie with stale `mfaAt` no longer walks past the gate). `b.middleware.attachUser` now threads `req` through `session.verify` so the documented fingerprint-drift / IP-UA pin / anomaly-score defenses actually fire on the standard middleware path. `b.middleware.bearerAuth` returns 401 with `WWW-Authenticate: Bearer error="invalid_request"` when an `Authorization` header is present but doesn't parse against the configured scheme (was falling through to cookie-session); `realm` is CRLF-validated at create-time. `b.bearerAuth` sets `req._bearerAuthHandled` after success so `b.middleware.attachUser` skips re-reading the same header. `b.breakGlass.unsealRow` SELECTs the target row before incrementing `rowsConsumed` so a typo'd row id no longer exhausts a `maxRowsPerGrant: 1` grant. `b.auth.password` HIBP path fail-closes when more than half the response lines are unparseable (poisoned-mirror defense). `b.auth.jwt.sign` auto-mints a `jti` when `expiresInSec` is set and the operator didn't supply one; the silent-replay-window where the verifier was configured for replay-defense but the token shipped without a jti is closed. `b.auth.oauth.exchangeCode` requires `nonce` on OIDC flows when `authorizationUrl()` produced one (silent skip on operator-forgot-to-thread is closed; explicit `skipNonceCheck: true` for legacy IdPs). `b.auth.lockout` cache-error signal now also rides the audit chain (was observability-only — a deployment with no observability + a degraded cache had no signal that brute-force protection was disabled). `b.middleware.csrfProtect` cookie regex tightened from `{2,}` to `{64}` hex chars (matches `forms.generateCsrfToken` output length so a sibling-subdomain XSS can't plant `csrf=ab` and submit matching `X-CSRF-Token`); new `requireOrigin: true` opt for browser-only routes; `csrf.bad_cookie_value` audit on planted-short-cookie refusals.
|
|
24
|
+
|
|
25
|
+
**Storage / SQLi** — `b.retention` calls `safeSql.validateIdentifier` on every operator-supplied table name, age field, soft-delete field, legal-hold field, and cascade FK before reaching SQL string concatenation (operator-config-fed name like `users"; DROP TABLE audit_log;--` no longer breaks out of the quoted identifier). `b.db` at-rest `db.enc` envelope binds `(data dir, node identity)` AAD so two deployments sharing the operator passphrase can't swap `db.enc` files; old envelopes still decrypt via a one-release backwards-compat fallback. `b.externalDb` `SET LOCAL` GUC values capped at 4 KiB (prevents log bloat / parser pressure from operator-controlled tenant-id payloads). `b.cache` set path swapped from `JSON.stringify` to `safeJson.stringify` (refuses Buffer / circular / Date round-trip ambiguity). `b.objectStore.setObjectRetention` does a `getObjectRetention` pre-check and refuses client-side when the existing retention mode is `COMPLIANCE` and the operator (or attacker with `s3:PutObjectRetention`) tries to shorten it or pass `bypassGovernance: true`. `b.inbox` rejects NUL + C0 controls in `messageId` / `source` (closes the dedupe-collision attack on truncating drivers).
|
|
26
|
+
|
|
27
|
+
**HTTP / network** — `b.wsClient.connect` now wires `b.ssrfGuard` symmetric to `b.httpClient` (cloud-metadata / private / loopback / link-local / reserved IPs are hard-deny by default; `allowInternal: true` opts in for legitimate internal targets). `b.wsClient` outbound `tls.connect` explicitly pins `minVersion: "TLSv1.3"` matching every other outbound TLS site. `b.app` HTTP/2 server pins `maxOutstandingPings: 10` (CVE-2019-9512 ping-flood class). `b.middleware.cors` always appends `Vary: Origin` when the request carried an Origin (closes the cache-poisoning surface where unmatched-origin responses could be served from a previously-matched cache entry). `b.staticServe` adds `maxRangeBytes` cap (default 64 MiB) — refuses single-range requests larger than the cap with 416 (slowloris-range defense). `b.middleware.bodyParser` per-part header bytes now count toward `totalSize` (closes the 120 × 16 KiB amplification surface for many-parts uploads). `b.ssrfGuard` `_ipv4ToInt` strict octet validation refuses non-numeric segments instead of silently coercing to 0 (closes the CIDR-typo collapse-to-0.0.0.0 footgun). `b.cluster` heartbeat picks up ±20% per-tick jitter on followers (closes the thundering-herd lease-acquire race on lease expiry); `MIN_LEASE_TTL` bumped from 5s to 10s.
|
|
28
|
+
|
|
29
|
+
**Supply-chain** — `scripts/vendor-update.sh` now auto-runs `scripts/refresh-vendor-manifest.js` so MANIFEST.json sha256 hashes track the on-disk bundle without a separate operator step.
|
|
30
|
+
|
|
31
|
+
**Content-safety** — `b.guardHtml._extractScheme` + `b.guardSvg._extractScheme` now decode HTML5 named entities (`	` / `
` / `:` / `/` / etc.) before scheme-allowlist matching (closes the `java	script:` bypass class). `b.guardCsv` strips ZWSP / RTLO / LRM / RLM / BOM at cell-start before the formula-prefix scan (closes the bidi-prefix formula-injection bypass). `b.guardAll._verifyParity` now walks `STANDALONE_GUARDS` too (filename / domain / uuid / cidr / time / mime / jwt / oauth / graphql / shell / regex / jsonpath / template / image / pdf / auth) so missing PROFILES / COMPLIANCE_POSTURES on a standalone guard surfaces at boot rather than at `b.guardAll.allGuards()` lookup. `b.fileUpload._checkAllowedFileType` cross-checks the operator-supplied claimed MIME against magic-byte detection and refuses on family mismatch. `b.mail` attachment validation now wires `b.guardFilename.validate({ profile: "strict" })` + magic-byte / claimed-MIME cross-check (defense-in-depth on outbound mail attachments).
|
|
32
|
+
|
|
33
|
+
**Observability** — `b.redact` SENSITIVE_FIELDS now covers `x-api-key` / `x-apikey` / `x_api_key` / `api-key` (substring-match via lowercased field-name was missing the hyphen + underscore variants) plus DPoP / OAuth 2.1 fields (`jwk`, `dpop`, `proof`, `assertion`, `client_assertion`, `id_token_hint`, `code_verifier`, `client_secret`, `refresh_token`, `access_token`); new value-shape detector redacts query-string `?token=` / `?access_token=` / `?api_key=` patterns inside URL fields. `b.logStream` syslog sink strips CR / LF from MSG content per RFC 5424 §6.4 (closes the SIEM-record-injection surface where an operator-controlled `record.message` could spawn a fake separate-priority record). `b.compliance.set` rejection emits `compliance.posture.set_rejected` audit on `unknown-posture` and `already-set` paths.
|
|
34
|
+
|
|
35
|
+
- **0.8.0** (2026-05-06) — Minor release. New: **`b.mail.arc.sign({ rfc822, instance, authservId, domain, selector, privateKey, algorithm, cv, authResults, headersToSign, timestamp })`** ships the relay-side RFC 8617 ARC chain construction (companion to `b.mail.arc.verify`); produces AAR + AMS + AS headers, prepends them in RFC-recommended order; enforces cv= rules (`i=1` requires `cv=none`, `i>=2` requires `cv=pass`/`cv=fail`); chain-gap detection (`i=N` requires N-1 prior hops); rsa-sha256 + ed25519-sha256 algorithms; CRLF-injection refused on operator-supplied authResults; audit emission `dkim.arc.signed`. **`b.inbox.create({ externalDb, table, retentionDays, audit })`** transactional dedupe-on-receive (companion to `b.outbox`); guarantees exactly-once handling by recording every (source, messageId) pair in the same transaction as the business state change — duplicate delivery short-circuits via the (source, message_id) PRIMARY KEY constraint; high-level `handle(opts, fn)` API wraps externalDb.transaction; low-level `recordReceive` / `markProcessed` for operators managing transactions directly; `declareSchema` / `sweep(retentionDays)` / `getStats` / `isFresh`; postgres + sqlite dialect support; audit emissions `inbox.received` / `handled` / `handle_failed` / `swept`. **`b.openapi.parse(jsonStringOrObject)`** + **`b.asyncapi.parse(jsonStringOrObject)`** validate external specs (currently only the build path existed); returns `{ doc, errors[], valid }`; covers version, info, paths/channels/operations, response.description, path-parameter required:true, dangling security references. **`b.crypto.decryptMlkem768X25519(ciphertext, { privateKey, x25519PrivateKey })`** symmetric named helper alongside the existing `encryptMlkem768X25519`; rejects ciphertexts under any other KEM ID at the head with a clear error rather than the generic dispatch path. **`b.wsClient`** hardening + extensions: `handshakeGuid` opt mirrors the server (operators with non-RFC-6455 GUIDs); decompression-bomb defense via `zlib.inflateRawSync` `maxOutputLength` cap (small compressed frame can no longer expand to GBs); fatal UTF-8 validation on text frames + close-frame reasons (RFC 6455 §5.6); RFC 6455 §5.5 control-frame ≤125-byte cap + FIN=1 enforcement; RSV1-on-continuation rejection (RFC 7692 §6.1); permanent-error classifier so 4xx handshake responses / accept-mismatch / bad-subprotocol / bad-upgrade / bad-status-line / message-too-big skip reconnect (no auth-failure hammering); `close(code, reason)` truncates >123-byte UTF-8 reasons at codepoint boundaries; permessage-deflate `server_max_window_bits` parsing with [8, 15] range enforcement; audit metadata enrichment — `bytesSent` / `bytesReceived` / `attempt` / `peerCertFingerprint` / `serverWindowBits` / `tls` / `permanent` flag. **`client.cancelReconnect()`** new operator API stops in-flight reconnect timers — `close()` mid-reconnect now also cancels the pending timer rather than returning early on the closed state; CRLF validation on the operator-supplied `Origin:` header matches the existing custom-header validation. **`b.vault.aad`** + **`b.wsClient`** + integration test from v0.7.114 are also part of this minor since v0.7.114 was the patch immediately preceding the version bump.
|
|
36
|
+
|
|
37
|
+
- **0.7.114** (2026-05-06) — `b.vault.aad` AAD-bound sealed columns + `b.wsClient` outbound WebSocket client. **`b.vault.aad.seal(plaintext, aadParts)`** / **`b.vault.aad.unseal(value, aadParts)`** binds the seal to an AAD tuple `(table, rowId, column, schemaVersion)` so the AEAD tag fails on any decrypt where the AAD differs — copy-paste between rows, replay across schema-version bumps, and table-mismatch attacks all surface as a refused decrypt. Symmetric key derived per-row via SHAKE256 over `("vault.aad/v1/" || vault-root || canonical-AAD)` with the AAD threaded into XChaCha20-Poly1305's tag. `buildColumnAad` / `buildContextAad` helpers produce canonical (sorted-keys, length-prefixed) AAD bytes; `reseal(value, fromAad, toAad)` re-binds a value to a new context after authenticating the source. Audit emissions: `vault.aad.sealed` / `vault.aad.unseal_failed`. **`b.wsClient.connect(url, opts)`** ships the outbound RFC 6455 WebSocket client — companion to `b.websocket` (server-side). HTTP/1.1 Upgrade with Sec-WebSocket-Key generation + Sec-WebSocket-Accept verification (rejects on hash mismatch); subprotocol + permessage-deflate (RFC 7692) negotiation; client-side frame masking (RFC 6455 §5.3); TLS via `b.network.tls.pqc` (X25519MLKEM768 hybrid handshake, security-defaults-on); heartbeat ping/pong with pongDeadline tracking; auto-reconnect with exponential-backoff + full jitter; CRLF-injection defense on operator-supplied headers; configurable `maxMessageBytes` / `maxFrameBytes` / `pingMs` / `pongMs` / `handshakeTimeoutMs` / `reconnect: { maxAttempts, baseMs, maxMs }`. EventEmitter API: `open` / `message` / `close` / `error` / `reconnecting`. Reuses `FrameParser` and `serializeFrame` from `lib/websocket.js` so the wire layer is identical to the server. **Integration test** `test/integration/ws-client-roundtrip.test.js` boots a real `http.Server` driven by `b.websocket` primitives and dials it with `b.wsClient`, exercising plain ws:// handshake + subprotocol negotiation + text/binary echo + ping/pong heartbeat + close round-trip + permessage-deflate compress/inflate end-to-end. Audit emissions: `wsclient.connected` / `wsclient.closed` / `wsclient.error`.
|
|
38
|
+
|
|
39
|
+
- **0.7.113** (2026-05-06) — `@noble/post-quantum` attribution sweep. NOTICE picks up the v0.6.1 vendored bundle with full copyright + Used-for + thank-you line per the framework's attribution convention. README's "Vendored dependencies" dependency table picks up the noble-post-quantum row alongside noble-ciphers. The wiki home page's "Special thanks" list cites Paul Miller for both noble-ciphers and noble-post-quantum. The wiki `welcome` page's "What's in the box" table picks up `@noble/post-quantum` ↔ `b.pqcSoftware` with the security-first defaults (ML-KEM-1024, ML-DSA-87, SLH-DSA-SHAKE-256f). Pure docs / attribution sweep — no behaviour changes.
|
|
40
|
+
|
|
41
|
+
- **0.7.112** (2026-05-06) — `b.asyncapi` AsyncAPI 3.0 schema-document builder + `b.pqcSoftware` pure-JS PQC primitive wrapper around vendored `@noble/post-quantum`. **`b.asyncapi.create({ info, servers, defaultContentType, security, externalDocs, tags, id })`** returns an event-driven sibling to `b.openapi` — operators describe pubsub / websocket / kafka / mqtt / amqp surfaces as a single document the framework can serve at `/asyncapi.json` (or YAML). **`builder.channel(channelId, opts)`** registers channels with messages / parameters / per-channel bindings; **`builder.operation(operationId, opts)`** registers `send` / `receive` operations against declared channels (dangling-channel references throw at registration). **`builder.schema / message / parameter / correlationId / requestBody`** register reusable components. **`builder.security.add / require`** with same scheme builders shared with `b.openapi.security` (bearer / basic / apiKey / oauth2 / openIdConnect / mtls / dpop). **`builder.toJson() / toJsonString() / toYaml()`** — final dangling-security-reference checks; YAML emitter shared with `b.openapi`. **`b.asyncapi.bindings.{websockets,kafka,amqp,mqtt,http}`** typed binding builders for the four protocols framework primitives speak; rejection on bad shape (websockets method ∈ {GET, POST}, kafka partitions/replicas > 0, amqp `is` ∈ {queue, routingKey}, mqtt qos ∈ {0, 1, 2}). **`b.asyncapi.traits.{operation, message, applyOperation, applyMessage}`** AsyncAPI trait composition with shallow-merge semantics — operators define a trait once (e.g. "every kafka publish carries tracing-header envelope") and apply it across operations / messages. **`b.middleware.asyncapiServe({ document, pathJson, pathYaml, accessControl, cacheControl, pretty, audit })`** mirrors `openapiServe`: GET / HEAD only, SHA3-512 ETag for conditional 304s, CORS gating. **`b.pqcSoftware`** ships pure-JS FIPS 203 / 204 / 205 PQC algorithms via `lib/vendor/noble-post-quantum.cjs` (Paul Miller's `@noble/post-quantum` v0.6.1, MIT, vendored under `lib/vendor/MANIFEST.json` with SHA-256 pin). Usable server-side and client-side; ciphertexts FIPS 203 conformant in both directions with Node's built-in WebCrypto ML-KEM. Defaults pin to the highest cat-5 level: `DEFAULT_KEM` = ML-KEM-1024, `DEFAULT_LATTICE_SIG` = ML-DSA-87, `DEFAULT_HASH_SIG` = SLH-DSA-SHAKE-256f. Surface: `ml_kem_512` / `ml_kem_768` / `ml_kem_1024` (FIPS 203 KEM), `ml_dsa_44` / `ml_dsa_65` / `ml_dsa_87` (FIPS 204 lattice signatures), `slh_dsa_sha2_*f` / `slh_dsa_shake_*f` (FIPS 205 hash signatures), `isAvailable()` + `listAlgorithms()` + getter-style algorithm accessors. Audit emissions: `asyncapi.document.built` / `asyncapi.document.served`. 200+ test cases.
|
|
42
|
+
|
|
43
|
+
- **0.7.111** (2026-05-06) — `b.flag` + `b.middleware.flagContext` — OpenFeature-aligned feature-flag client. **`b.flag.create({ provider, providers, defaultEvaluationContext, audit, errorHandler, hooks })`** returns a flag client with `getValue / getBoolean / getString / getNumber / getObject / getDetails / getValues / getDetailsAll / addProvider / removeProvider / list / middleware`. Hot-path drop-silent — operator-supplied defaults returned on flag-not-found / provider-error; an `errorHandler` callback + audit emission on `flag.evaluation.error` surface the issue without taking down the request. **`b.flag.providers`** ships three first-party providers: `memory({ flags })` for in-process / test flag sets, `localFile({ path, watch })` for JSON-file-backed flags with optional watch-on-change reload (hot-path-tolerant — bad JSON during reload is drop-silent), and `environmentVariable({ prefix, flags })` for `FLAG_*` env-var overrides. Flag specs validate at registration time per the no-MVP rule: `default` must reference a registered variant, every rule's `variant` must reference a registered variant, every rollout entry's percentage must be in [0, 100] with the sum bounded at 100. **`b.flag.targeting`** evaluates rules with 14 operators (eq / neq / in / nin / gt / gte / lt / lte / starts_with / ends_with / contains / regex / exists / not_exists / between); regex patterns capped at 200 chars (DoS defense); rules support nested-attribute paths (`user.profile.tier`); conjunction across `conditions[]`. **`b.flag.context`** ships evaluation-context builders: `create`, `merge`, `fromRequest({ userKey, extra })` (extracts targeting key from `req.user.id` or anonymous fallback hashed from clientIp + userAgent), `bucketOf(targetingKey, flagKey)` deterministic SHA3-512 percentage bucket helper. **`b.flag.cache(downstream, { ttlMs, maxEntries })`** wraps a downstream provider with a per-`(targetingKey, flagKey)` TTL cache backed by an insertion-ordered Map (oldest-first eviction at maxEntries cap; flag-not-found never cached so operators can add flags later); `cached.bust()` clears all entries; `cached.stats()` returns `{ size, hits, misses, evictions, hitRatio, ttlMs, maxEntries }`. **`b.middleware.flagContext({ userKey, userKeyHeader, extractAttributes, tenantKeyHeader })`** request-time middleware that extracts an evaluation context onto `req.flagCtx` for downstream handlers and multi-client flag setups (separate from the per-client `flag.middleware()` accessor that attaches `req.flag.{getBoolean,getString,...}` directly). **OpenFeature hooks** — `before / after / error / finally` callbacks fire around every evaluation; throwing hooks are drop-silent (observability, not blocking). Audit emissions: `flag.evaluated` / `flag.evaluation.error` / `flag.cache.bust`. 130+ test cases.
|
|
44
|
+
|
|
45
|
+
- **0.7.110** (2026-05-06) — `b.openapi` + `b.middleware.openapiServe` — OpenAPI 3.1 schema-document builder. **`b.openapi.create({ info, servers, externalDocs, tags, security })`** returns an immutable-on-`toJson` builder for hand-authored API contracts. **`builder.path(method, urlPattern, opts)`** registers operations with full RFC-9110-method validation (get / put / post / delete / options / head / patch / trace), path-template `{name}` placeholders cross-checked against declared `parameters: [{ in: "path", required: true }]` (build-time throw on mismatch), required `responses` map per OpenAPI 3.1 §4.8.5 with required `description` on every response, body-content map under `requestBody.content[mediaType].schema`. **`builder.schema(name, schemaSpec)`** / `response(...)` / `parameter(...)` / `requestBody(...)` / `header(...)` / `example(...)` register reusable `components`. Schema specs accept three forms: a `b.safeSchema` object (walked into JSON Schema 2020-12 by `lib/openapi-schema-walk.js`), a hand-shaped JSON Schema object (passes through with shape validation), or a primitive type-name string. **`builder.security.add(name, scheme)`** + `security.require(requirement)` register security schemes; the builder surfaces `b.openapi.security.{bearer,basic,apiKey,oauth2,openIdConnect,mtls,dpop}` typed builders that validate every IANA-registered scheme shape (apiKey `in` ∈ {header, query, cookie}; oauth2 `flows` with per-flow `authorizationUrl` / `tokenUrl` / `scopes` validation; mTLS uses the OpenAPI 3.1 `mutualTLS` type). **`builder.toJson()`** runs final dangling-reference checks (every `security` requirement key MUST resolve to a registered scheme). **`builder.toJsonString(indent)`** + **`builder.toYaml()`** emit the document; the YAML emitter (`lib/openapi-yaml.js`) handles spec-quoted strings (numbers / booleans / dates / yaml-special tokens), nested arrays / objects, empty `[]` / `{}`. **`builder.middleware({ pretty, cacheControl })`** is the single-builder embed; **`b.middleware.openapiServe({ document, pathJson, pathYaml, accessControl, cacheControl, pretty, audit })`** is the framework-style mounted middleware that responds at `/openapi.json` + `/openapi.yaml` (configurable), emits SHA3-512-derived ETag for conditional-GET 304s, gates by `accessControl: "public" | "same-origin"` for the `Access-Control-Allow-Origin: *` header, falls through on non-GET / non-HEAD methods. Audit emissions: `openapi.document.built` / `openapi.document.served`. 100+ test cases.
|
|
46
|
+
|
|
47
|
+
- **0.7.109** (2026-05-06) — `b.compliance.aiAct` + `b.middleware.aiActDisclosure` — full EU AI Act (Regulation (EU) 2024/1689) compliance primitive with deadline-aware classification, Article 5 prohibited-practices catalog, Article 6 + Annex III high-risk classifier, Article 12 logging helpers (with biometric-system-specific minimum-fields enforcement per Art. 12(3)), Article 50 transparency banner / watermark / JSON-LD / metaTag builders, Article 53/55 GPAI obligations including FLOP-threshold systemic-risk classification, Annex IV technical-documentation scaffold, and operator-actionable deployer checklist. **`b.compliance.aiAct.classify({ purpose, deployContext, deployerType, ... })`** returns `{ tier, prohibitedHits, annexIIIHits, obligations, action, legalReference }` — tier is `prohibited` / `high-risk` / `limited-risk` / `general-purpose` / `minimal-risk`. **`b.compliance.aiAct.prohibited`** ships the eight Art. 5 prohibited practices catalog (subliminal manipulation, vulnerable-group exploitation, social scoring, profiling-only predictive policing, untargeted facial scraping, workplace/education emotion inference, sensitive-attribute biometric categorisation, real-time RBI in public spaces) with conservative classifier and exemption handling. **`b.compliance.aiAct.risk`** ships the eight Annex III rows with per-row Article 9-15 obligation lists (Article 27 FRIA added for law-enforcement / migration). **`b.compliance.aiAct.transparency`** ships banner / htmlBanner / watermark / jsonLdDisclosure / metaTags builders for the four Art. 50 obligations. **`b.compliance.aiAct.logging`** ships buildEvent / emit / logEvent / loggerFor / retentionFloorMs (per Art. 19 — 180-day default, 365-day floor for financial / employment / law-enforcement). Biometric systems require the Art. 12(3) minimum fields (periodStart, periodEnd, referenceDatabase, matchedInputRef, verifiers); the builder throws when they're missing. **`b.compliance.aiAct.gpai`** classifies general-purpose AI models with the Art. 51(2) FLOP threshold (10^25 cumulative training compute → presumption of systemic risk per Art. 55). **`b.compliance.aiAct.annexIVScaffold(...)`** returns the eight Annex IV documentation sections (general description / detailed description / monitoring + functioning / risk-management / changes / harmonised standards / EU declaration of conformity / post-market monitoring). **`b.compliance.aiAct.deployerChecklist(assessment)`** returns operator-actionable next-steps with article references for the assessment's tier (prohibited → do-not-deploy; high-risk → conformity assessment + technical doc + risk mgmt + data governance + logging + human oversight + Art. 71 EU database + Art. 72 post-market monitoring; limited-risk → transparency middleware mount; GPAI → technical doc + downstream info + copyright policy + training summary + Art. 55 systemic-risk obligations when applicable). **`b.compliance.aiAct.DEADLINES`** carries the Art. 113 phase-in calendar (2026-02-02 prohibited / 2026-08-02 GPAI + transparency / 2027-08-02 high-risk). **`b.middleware.aiActDisclosure({ kind, deployerName, policyUri, mode })`** auto-injects `AI-Act-Notice` / `AI-Act-Article` / `AI-Act-Policy` response headers on every 2xx response, plus an `<div role="status">` banner injection on 2xx HTML responses when mode=`html`; honors `X-Skip-AI-Act` request header and `res.locals.aiActSkip` opt-out. Audit emissions: `compliance.aiact.classified` / `aiact.disclosed` / `aiact.<kind>`. 130+ test cases covering catalog completeness, classifier exemptions, GPAI FLOP threshold, banner / watermark / metaTag round-trips, logging biometric-field enforcement, retention floors, annexIVScaffold sections, deployer checklist for all five tiers.
|
|
48
|
+
|
|
49
|
+
- **0.7.108** (2026-05-06) — `b.auth.stepUp` + `b.middleware.requireStepUp` — RFC 9470 OAuth 2.0 Step-Up Authentication Challenge with elevation-grant short-circuit, RFC 9396 authorization-details parsing, and RFC 8176 AMR phishing-resistance evaluation. Routes that need a stronger or fresher authentication ceremony now have a complete primitive; resource servers emit the `WWW-Authenticate: Bearer error="insufficient_user_authentication", acr_values="...", max_age="..."` challenge per the registered IANA error code, and clients re-prompt the IdP with `acr_values` / `max_age` propagated. **`b.auth.acr`** ships an operator-extendable ACR-vocabulary registry (NIST 800-63-4 AAL1/2/3, OIDC `0`/`1`/`2`, ISO 29115 `loa1`–`loa4`, InCommon Bronze/Silver/Gold, RFC 9470 `phr`/`phrh`) with rank-based `meets(presented, required)` / `meetsAny(presented, required[])` comparison and `register({ value, rank })` for private vocabularies. **`b.auth.authTime`** ships `auth_time` enforcement: `ageSec(claims)` / `freshEnough(claims, maxAge)` / `buildClaims({ method, prevAt })` (refresh preserves prior auth_time per OIDC Core 1.0 §5.4) / `recommendMaxAge` clamping helper. **`b.auth.stepUp.evaluate({ claims, requirement })`** runs the full RFC 9470 evaluation: ACR rank check, ACR-values list (any-satisfies), `max_age` freshness, RFC 8176 AMR `requiredAmr: ["hwk", "pop"]` requirement, and `phishingResistant: true` boolean check. Hot-path drop-silent (returns structured `{ ok: false, error, reason }`); operator typos in the requirement (unregistered acr, bad maxAge type) bubble up to surface boot-time. **`b.auth.stepUp.buildChallenge({ requirement, realm, errorDescription })`** assembles the RFC 7235-quoted `WWW-Authenticate` value with control-character / quote-injection rejection. **`b.auth.stepUp.parseChallenge(headerValue)`** is the operator-side roundtrip helper. **`b.auth.stepUp.parseAuthorizationDetails(jsonString)`** parses the RFC 9396 fine-grained authorization-details parameter and validates each entry has the required `type` field. **`b.auth.stepUp.grant.{create,verify,revoke,isRevoked,list,setSigningKey}`** issues short-lived HMAC-SHA3-512 signed elevation grants binding `(subject, scope, acr, amr, audience, evidence, iat, exp, jti)`; verify enforces audience / scope / subject / expiry / revocation. **`b.middleware.requireStepUp({ requirement, realm, acceptGrant, grantHeader, grantScope, getClaims, audit })`** mounts the RFC 9470 challenge in front of routes; checks an optional `X-Step-Up-Grant` header first (multi-step flows skip re-prompting), falls back to claims-based evaluation. Audit emissions on every state transition: `auth.stepup.required` / `satisfied` / `denied` / `grant.issued` / `consumed` / `revoked` / `rejected`. 60+ test cases cover ACR rank evaluation, registration, AMR phishing-resistance, auth_time freshness with skew, RFC 7235 quote-injection rejection, RAR JSON shape checks, grant happy-path / tamper / audience / scope / expiry / revoke, and middleware happy-path / 401-with-WWW-Authenticate / grant short-circuit / scope-mismatch fallback / config-time typo throws.
|
|
10
50
|
|
|
11
51
|
- **0.7.107** (2026-05-06) — `b.auth.sdJwtVc` — Selective Disclosure JWT for Verifiable Credentials per [draft-ietf-oauth-sd-jwt-vc](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/), aligned with the EU Digital Identity Wallet (EUDI) roll-out and EU AI Act Art. 50 disclosure requirements. **`b.auth.sdJwtVc.issue({ issuer, vct, claims, selectivelyDisclosed, issuerKey, ... })`** mints an SD-JWT VC: per-claim disclosures (base64url-encoded `[salt, name, value]` JSON arrays) with their SHA-256 (or SHA3-256 / SHA-512 / SHA3-512) digests pinned in the issuer-signed `_sd` array; selectively-disclosed claims hidden from the issuer payload, plain claims passthrough; optional `cnf` claim pins the holder's public JWK for key-binding. Supported algorithms: ES256 (default per spec), ES384, EdDSA, ML-DSA-87 (PQC, framework's default), ML-DSA-65. **`present({ sdJwt, disclosedClaimNames, audience, nonce, holderKey })`** builds a holder presentation by selecting which disclosures to reveal; signs an optional Key Binding JWT (typ=`kb+jwt`) carrying the audience, nonce, iat, and an sd_hash binding it to the specific presentation. **`verify(presentation, { issuerKeyResolver, audience, nonce, expectedVct, requireKeyBinding })`** runs the full verification pipeline: issuer JWT signature, iat / exp / vct, per-disclosure digest match against the issuer's `_sd` array, optional KB-JWT signature + audience + nonce + sd_hash check. **`b.auth.sdJwtVc.issuer.create({ issuerUrl, keys, activeKid })`** — operator-side issuer factory with key-management + kid stamping + per-issuance audit emission + `rotateKey` support. **`b.auth.sdJwtVc.holder.create({ storage, holderKey })`** — wallet helper with operator-pluggable storage (production wires `b.db` / `b.objectStore`; built-in `memoryStorage()` for dev / tests); `store` / `present` / `list` / `get` / `delete` covers the full wallet lifecycle. **`b.auth.sdJwtVc.disclosure.encode / decode`** — pure helpers for the SD-JWT disclosure format. Audit emissions on every state transition (`auth.sdJwtVc.issued` / `auth.sdJwtVc.holder.stored` / `presented` / `deleted` / `auth.sdJwtVc.key_rotated`). 32 test cases covering disclosure round-trip, issue/verify happy path, signature tamper, expiration, vct mismatch, disclosure tamper, present subset, key binding (audience + nonce + sd_hash + replay defense), issuer factory + key rotation, holder factory + storage round-trip, hash-algorithm switching, plain-only credential.
|
|
12
52
|
|
package/NOTICE
CHANGED
|
@@ -22,7 +22,23 @@ Source: https://github.com/paulmillr/noble-ciphers
|
|
|
22
22
|
License: MIT
|
|
23
23
|
Copyright: Copyright (c) 2023 Paul Miller (https://paulmillr.com)
|
|
24
24
|
Used for: XChaCha20-Poly1305 authenticated encryption (lib/crypto.js,
|
|
25
|
-
lib/vault-wrap.js, via lib/vendor/noble-ciphers.cjs)
|
|
25
|
+
lib/vault-wrap.js, via lib/vendor/noble-ciphers.cjs).
|
|
26
|
+
Thank you to Paul Miller for the audited noble-ciphers suite.
|
|
27
|
+
--------------------------------------------------------------------------------
|
|
28
|
+
Component: @noble/post-quantum
|
|
29
|
+
Version: 0.6.1
|
|
30
|
+
Source: https://github.com/paulmillr/noble-post-quantum
|
|
31
|
+
License: MIT
|
|
32
|
+
Copyright: Copyright (c) 2024 Paul Miller (https://paulmillr.com)
|
|
33
|
+
Used for: FIPS 203 ML-KEM (ml_kem_512 / ml_kem_768 / ml_kem_1024),
|
|
34
|
+
FIPS 204 ML-DSA (ml_dsa_44 / ml_dsa_65 / ml_dsa_87),
|
|
35
|
+
FIPS 205 SLH-DSA (slh_dsa_sha2_*f / slh_dsa_shake_*f),
|
|
36
|
+
via lib/vendor/noble-post-quantum.cjs and the b.pqcSoftware
|
|
37
|
+
framework wrapper (lib/pqc-software.js). Server-side and
|
|
38
|
+
client-side; ciphertexts FIPS 203 conformant in both
|
|
39
|
+
directions with Node's built-in WebCrypto ML-KEM. Thank
|
|
40
|
+
you to Paul Miller for the auditable, dependency-free
|
|
41
|
+
reference implementation.
|
|
26
42
|
--------------------------------------------------------------------------------
|
|
27
43
|
Component: @simplewebauthn/server
|
|
28
44
|
Version: 13.3.0
|
package/README.md
CHANGED
|
@@ -43,15 +43,15 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
43
43
|
|
|
44
44
|
- **Data layer** — SQLite with sealed-by-default columns (`b.db`), migrations, seeders, atomic-file writes; bring-your-own external Postgres / MySQL / etc. with pool tuning + role-aware connect + read-replica routing (`b.externalDb`); declarative role-narrowed views and Postgres row-level-security migrations (`b.db.declareView`, `b.db.declareRowPolicy`); S3 / R2 / B2 / GCS / Azure object store with multipart upload + SSE + bucket-ops (create / delete / list / lifecycle / CORS) across all three clouds, plus S3 Object Lock + per-object retention + legal hold for write-once-read-many compliance workloads (`b.storage`, `b.objectStore`); durable queue with priority + cron + flows on the local SQLite backend, a shared Redis backend, OR AWS SQS via SigV4 + AWSJsonProtocol_1.0 for fully-managed multi-replica deploys (`b.queue`, `b.jobs`); cluster-shared cache (`b.cache`).
|
|
45
45
|
- **Identity & access** — passwords (Argon2id) + policy primitive (NIST 800-63B / PCI-DSS 4.0 / HIPAA-AAL2 profiles, HaveIBeenPwned k-anonymity breach check, length / context / dictionary / complexity rules, rotation + history) (`b.auth.password`); passkeys (WebAuthn), TOTP, JWT (PQ-default), OAuth, sessions with optional IP / UA fingerprint drift detection + anomaly scoring, brute-force lockout (`b.auth.*`, `b.session`); RBAC + optional per-role DB binding + role-spec `requireMfa` + per-route MFA freshness window + ABAC predicate registry (`b.permissions`); API keys with rotation (`b.apiKey`); break-glass column gates with second-factor + audit (`b.breakGlass`); two-person-rule approval workflow with m-of-n quorum + cooling-off lock + approver-role gate + cancellation (`b.dualControl`).
|
|
46
|
-
- **Crypto** — envelope-versioned PQC at rest (ML-KEM-1024 + P-384 hybrid, XChaCha20-Poly1305, SHAKE256), vault sealing, field-level crypto + cryptographic erasure (`b.cryptoField.eraseRow`), signed webhooks (SLH-DSA-SHAKE-256f), ECIES API encryption (`b.crypto`, `b.vault`, `b.webhook`); pure-JS mTLS CA that issues clientAuth / serverAuth / dual-EKU certs with SAN entries and auto-detects the highest-PQC signature algorithm the vendored x509 library accepts (today: ECDSA-P384-SHA384 bridge; self-upgrades to SLH-DSA / ML-DSA when the X.509 ecosystem catches up), PQC TLS gates inbound + outbound (`b.mtlsCa`, `b.pqcGate`, `b.pqcAgent`).
|
|
46
|
+
- **Crypto** — envelope-versioned PQC at rest (ML-KEM-1024 + P-384 hybrid, XChaCha20-Poly1305, SHAKE256), vault sealing, field-level crypto + cryptographic erasure (`b.cryptoField.eraseRow`), AAD-bound sealed columns whose AEAD tag is tied to a `(table, rowId, column, schemaVersion)` tuple so a copy-paste between rows or a schema-version replay surfaces as a refused decrypt (`b.vault.aad`), signed webhooks (SLH-DSA-SHAKE-256f), ECIES API encryption (`b.crypto`, `b.vault`, `b.webhook`); pure-JS mTLS CA that issues clientAuth / serverAuth / dual-EKU certs with SAN entries and auto-detects the highest-PQC signature algorithm the vendored x509 library accepts (today: ECDSA-P384-SHA384 bridge; self-upgrades to SLH-DSA / ML-DSA when the X.509 ecosystem catches up), PQC TLS gates inbound + outbound (`b.mtlsCa`, `b.pqcGate`, `b.pqcAgent`).
|
|
47
47
|
- **HTTP** — router with schema-validated routes + OpenAPI publication; full middleware stack (CSRF, CORS, rate-limit, security headers, CSP nonce, body parser, compression, SSE, request log, request-time DB role binding via `b.middleware.dbRoleFor`, in-process CIDR fence via `b.middleware.networkAllowlist`) wired by `createApp`; HTTP/1.1 + HTTP/2 outbound client with SSRF gate (cloud-metadata IPs hard-denied unconditionally; private / loopback / link-local overridable per call), scheme + userinfo + per-host (wildcard / per-method) destination allowlist, redirects, multipart, interceptors, progress, encrypted cookie jar (`b.httpClient`, `b.ssrfGuard`, `b.safeUrl`); operator-tunable network configurability — env-driven NTP / NTS (RFC 8915 authenticated time), IPv4-or-IPv6 NTP servers, DNS with IPv6 / DoH / DoT (private-CA trust pinning via `opts.ca`) / cache / lookup timeout, outbound HTTP proxy (`HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`), runtime DPI trust-store CA additions, application-level heartbeats, TCP socket defaults (`b.network`).
|
|
48
48
|
- **Defensive parsers** — `b.safeJson`, `b.safeBuffer`, `b.safeSql`, `b.safeSchema`, `b.parsers` (XML / TOML / YAML / .env), `b.config` (schema-validated env), `b.fileType` magic-byte content classification with deny-on-upload categories (image / document / archive / executable / etc.).
|
|
49
49
|
- **Content-safety gates** — `b.gateContract` uniform composition contract (mode posture / hooks / forensic snapshot / decision cache / runtime cap). Family members: `b.guardCsv` (formula injection ASCII + full-width prefixes, dangerous-function denylist, bidi / homoglyph / control / null / BOM / zero-width detection, dialect ambiguity, CSV-bombs, numeric precision, schema-bound serializer); `b.guardHtml` (XSS / mXSS / DOM-clobbering / dangerous-tag / event-handler-family / dangerous-URL-scheme with entity-decode / CSS-injection in style attribute / IE conditional comments + token-level sanitize + always-correct `escapeText` / `escapeAttr` entity encoders); `b.guardSvg` (script / foreignObject / animation-element href hijack / DOCTYPE billion-laughs / XXE / SVGZ / cross-origin `<use>` SSRF / event-handlers + token-level sanitize). `b.guardArchive` (zip-slip / symlink + hardlink escape / decompression-ratio bombs (per-entry + aggregate) / nested-archive depth / duplicate-entry / case-insensitive collision / encryption-claim mismatch / format-claim mismatch via magic-byte detection — composes `b.guardFilename` for per-entry-name validation); `b.guardJson` (source-level prototype-pollution detection, duplicate-key, NaN/Infinity, JSON5 syntax, BOM, bidi, numeric-precision-loss, top-level-key allowlist, depth/breadth/array/string/node-count caps); `b.guardYaml` (deserialization-tag RCE via language-specific tag prefixes, billion-laughs alias recursion, Norway-problem implicit booleans, leading-zero octals, multi-document streams, duplicate keys, merge-key chains, depth+anchor+node caps); `b.guardXml` (XXE / billion-laughs / external-entity / parameter-entity / XInclude / xsi:schemaLocation / processing-instruction / CDATA / XML-signature-wrapping detection — DOCTYPE refused at all profile levels); `b.guardMarkdown` (source-level scan run BEFORE any markdown renderer sees the input — dangerous URL schemes in inline links + images + autolinks + reference-link definitions with HTML-entity decode bypass; whitespace-tolerant dangerous-tag matching per CVE-2026-30838; front-matter; HTML comments; code-fence language injection; catastrophic emphasis-run ReDoS per CVE-2025-6493 class; inline DOCTYPE; depth + link + image + autolink + ref-def caps); `b.guardEmail` (single-address + full RFC 822/5322 message validation — SMTP smuggling per CVE-2023-51764 / 51765 / 51766 / CVE-2026-32178 class via bare-CR + bare-LF + smuggled SMTP verbs; CRLF header injection; IDN homograph mixed-script domains with operator-opt-in `allowedScripts`; Punycode flag; display-name spoofing; IP-literal addresses; RFC 5322 comment syntax; multi-@; RFC 5321 length caps + RFC 5322 line cap; BOM injection). Filename safety: `b.guardFilename` (path traversal raw + percent-encoded + overlong-UTF-8 + null-byte truncation + Windows reserved names + NTFS ADS + RTLO bidi spoofing + shell-exec / double-extension detection — standalone, wires into `b.fileUpload` via `filenameSafety`). All members ship strict / balanced / permissive profiles plus hipaa / pci-dss / gdpr / soc2 compliance postures. `b.guardAll` is the registry + aggregator: every shipped guard ON by default; opt-out per guard with audited reason via `exceptFor: { name: { reason } }`. **As of v0.7.12, `b.fileUpload` and `b.staticServe` wire `b.guardAll.byExtension({ profile: "strict" })` automatically + `b.fileUpload` also wires `b.guardFilename.gate({ profile: "strict" })` as `filenameSafety`** — defense-in-depth applied without any explicit operator wiring. Operators opt out per host-primitive via `contentSafety: null` / `filenameSafety: null` (audited at create() with operator-supplied reason).
|
|
50
|
-
- **Communication** — WebSockets with channel/room fan-out across cluster replicas (`b.websocket`, `b.websocketChannels`); generic distributed pub/sub with cluster-table / Redis PUB/SUB / custom backends (`b.pubsub`); mail with multipart + attachments + DKIM + calendar invites + bounce intake (`b.mail`, `b.mailBounce`); generic notification dispatcher with operator-supplied transports (`b.notify`); chunked file uploads with per-chunk SHA3-512 verification + atomic finalize + tombstone cleanup (`b.fileUpload`).
|
|
50
|
+
- **Communication** — WebSockets with channel/room fan-out across cluster replicas (`b.websocket`, `b.websocketChannels`); outbound WebSocket client (RFC 6455) with PQC-TLS handshake, permessage-deflate negotiation with decompression-bomb cap, fatal UTF-8 validation, control-frame size + FIN enforcement, permanent-error classifier that skips reconnect on 4xx handshake / accept mismatch / bad-subprotocol, exponential-backoff with full jitter (`b.wsClient`); generic distributed pub/sub with cluster-table / Redis PUB/SUB / custom backends (`b.pubsub`); mail with multipart + attachments + DKIM + calendar invites + bounce intake (`b.mail`, `b.mailBounce`); inbound mail authentication — SPF / DMARC / ARC verify + ARC chain signing for relays (`b.mail.spf`, `b.mail.dmarc`, `b.mail.arc`); generic notification dispatcher with operator-supplied transports (`b.notify`); chunked file uploads with per-chunk SHA3-512 verification + atomic finalize + tombstone cleanup (`b.fileUpload`).
|
|
51
51
|
- **Observability** — tamper-evident audit chain with SLH-DSA-signed checkpoints, metrics, tracing (OTel pass-through when wired), PII redaction, log-stream sinks (local file rotation, generic webhook, OTLP/HTTP-JSON OR OTLP/gRPC to an OTel collector, AWS CloudWatch Logs via SigV4 with optional autoCreate, RFC 5424 syslog over UDP/TCP/TLS), OTLP/HTTP-JSON exporter for traces + metrics (`b.audit`, `b.metrics`, `b.tracing`, `b.redact`, `b.logStream`, `b.otelExport`); operator-callable boot-time security policy assertions (`b.security.assertProduction`) and tamper-evident config-baseline drift detection signed with the audit-signing key (`b.configDrift`).
|
|
52
52
|
- **i18n** — CLDR plural rules, Accept-Language negotiation, Intl formatters, RTL (`b.i18n`).
|
|
53
53
|
- **Format helpers** — RFC 4180 CSV with Excel formula-injection prevention (`b.csv`), RFC 9562 UUID v4 + v7 (`b.uuid`), URL-safe slugs (`b.slug`), TZ-aware datetime (`b.time`), ZIP creation (`b.archive`), HMAC-signed cursor pagination (`b.pagination`), HTML form rendering + validation + CSRF (`b.forms`).
|
|
54
|
-
- **Production** — cluster leader election with fenced leases over Postgres/SQLite (`b.cluster`); cron + interval scheduler that runs exactly-once globally (`b.scheduler`); retry with full-jitter backoff + circuit breaker (`b.retry`); graceful shutdown (`b.appShutdown`); NTP boot check (`b.ntpCheck`); end-to-end-encrypted backup bundles with pre-flush fail-closed mode (`b.backup`); restore with pulled-bundle footprint preflight (`b.restore`); GDPR / PCI / HIPAA-shaped retention rules with multi-stage warn → archive → erase, legal-hold exemptions, dry-run preview, cross-table cascade (`b.retention`).
|
|
54
|
+
- **Production** — cluster leader election with fenced leases over Postgres/SQLite (`b.cluster`); cron + interval scheduler that runs exactly-once globally (`b.scheduler`); retry with full-jitter backoff + circuit breaker (`b.retry`); graceful shutdown (`b.appShutdown`); NTP boot check (`b.ntpCheck`); transactional outbox + dedupe-on-receive inbox so the business-state change and the outbound publish (or the inbound mark-handled) live in the same DB transaction — exactly-once semantics across Postgres / SQLite (`b.outbox`, `b.inbox`); end-to-end-encrypted backup bundles with pre-flush fail-closed mode (`b.backup`); restore with pulled-bundle footprint preflight (`b.restore`); GDPR / PCI / HIPAA-shaped retention rules with multi-stage warn → archive → erase, legal-hold exemptions, dry-run preview, cross-table cascade (`b.retention`).
|
|
55
55
|
|
|
56
56
|
## Documentation
|
|
57
57
|
|
|
@@ -113,6 +113,7 @@ All runtime dependencies are committed to the repo — no transitive npm install
|
|
|
113
113
|
| Package | Version | Author | Purpose |
|
|
114
114
|
|---|---|---|---|
|
|
115
115
|
| [`@noble/ciphers`](https://github.com/paulmillr/noble-ciphers) | 2.2.0 | [Paul Miller](https://github.com/paulmillr) | XChaCha20-Poly1305 AEAD |
|
|
116
|
+
| [`@noble/post-quantum`](https://github.com/paulmillr/noble-post-quantum) | 0.6.1 | [Paul Miller](https://github.com/paulmillr) | Pure-JS FIPS 203 ML-KEM (`ml_kem_512` / `ml_kem_768` / `ml_kem_1024`), FIPS 204 ML-DSA (`ml_dsa_44/65/87`), FIPS 205 SLH-DSA (`slh_dsa_*`). First-class on both server-side and client-side via `b.pqcSoftware` — security-first defaults pin to the highest cat-5 levels (ML-KEM-1024, ML-DSA-87, SLH-DSA-SHAKE-256f); interoperable with Node's built-in WebCrypto ML-KEM that `b.crypto.encrypt` / `b.middleware.apiEncrypt` use. |
|
|
116
117
|
| [`@simplewebauthn/server`](https://github.com/MasterKale/SimpleWebAuthn) | 13.3.0 | [Matthew Miller](https://github.com/MasterKale) | WebAuthn / passkey verification |
|
|
117
118
|
| [`@peculiar/x509`](https://github.com/PeculiarVentures/x509) + [`pkijs`](https://github.com/PeculiarVentures/PKI.js) | 2.0.0 + 3.4.0 | [Peculiar Ventures](https://github.com/PeculiarVentures) | Pure-JS mTLS CA — ECDSA P-384 cert signing, PKCS#12 packaging (no openssl CLI) |
|
|
118
119
|
| [`SecLists` 10k-most-common.txt](https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt) | master snapshot | [Daniel Miessler / SecLists contributors](https://github.com/danielmiessler/SecLists) (CC-BY-3.0) | Top-10000 common-password dictionary read by `b.auth.password.policy()` for the NIST 800-63B §5.1.1.2 "previously breached" check |
|
package/index.js
CHANGED
|
@@ -141,6 +141,9 @@ var auth = {
|
|
|
141
141
|
aal: require("./lib/auth/aal"),
|
|
142
142
|
statusList: require("./lib/auth/status-list"),
|
|
143
143
|
sdJwtVc: require("./lib/auth/sd-jwt-vc"),
|
|
144
|
+
stepUp: require("./lib/auth/step-up"),
|
|
145
|
+
acr: require("./lib/auth/acr-vocabulary"),
|
|
146
|
+
authTime: require("./lib/auth/auth-time-tracker"),
|
|
144
147
|
};
|
|
145
148
|
var template = require("./lib/template");
|
|
146
149
|
var render = require("./lib/render");
|
|
@@ -172,6 +175,7 @@ var dev = require("./lib/dev");
|
|
|
172
175
|
var bundler = require("./lib/bundler");
|
|
173
176
|
var pqcGate = require("./lib/pqc-gate");
|
|
174
177
|
var pqcAgent = require("./lib/pqc-agent");
|
|
178
|
+
var pqcSoftware = require("./lib/pqc-software");
|
|
175
179
|
var vaultRotate = require("./lib/vault/rotate");
|
|
176
180
|
var vaultPassphraseOps = require("./lib/vault/passphrase-ops");
|
|
177
181
|
var mtlsCa = require("./lib/mtls-ca");
|
|
@@ -185,6 +189,10 @@ var restoreRollback = require("./lib/restore-rollback");
|
|
|
185
189
|
var restore = require("./lib/restore");
|
|
186
190
|
var deprecate = require("./lib/deprecate");
|
|
187
191
|
var apiSnapshot = require("./lib/api-snapshot");
|
|
192
|
+
var openapi = require("./lib/openapi");
|
|
193
|
+
var asyncapi = require("./lib/asyncapi");
|
|
194
|
+
var wsClient = require("./lib/ws-client");
|
|
195
|
+
var flag = require("./lib/flag");
|
|
188
196
|
var auditTools = require("./lib/audit-tools");
|
|
189
197
|
var events = require("./lib/events");
|
|
190
198
|
var safeSchema = require("./lib/safe-schema");
|
|
@@ -216,6 +224,7 @@ var network = require("./lib/network");
|
|
|
216
224
|
var cloudEvents = require("./lib/cloud-events");
|
|
217
225
|
var dsr = require("./lib/dsr");
|
|
218
226
|
var outbox = require("./lib/outbox");
|
|
227
|
+
var inbox = require("./lib/inbox");
|
|
219
228
|
|
|
220
229
|
module.exports = {
|
|
221
230
|
crypto: crypto,
|
|
@@ -322,6 +331,7 @@ module.exports = {
|
|
|
322
331
|
bundler: bundler,
|
|
323
332
|
pqcGate: pqcGate,
|
|
324
333
|
pqcAgent: pqcAgent,
|
|
334
|
+
pqcSoftware: pqcSoftware,
|
|
325
335
|
vaultRotate: vaultRotate,
|
|
326
336
|
vaultPassphraseOps: vaultPassphraseOps,
|
|
327
337
|
mtlsCa: mtlsCa,
|
|
@@ -335,6 +345,10 @@ module.exports = {
|
|
|
335
345
|
restore: restore,
|
|
336
346
|
deprecate: deprecate,
|
|
337
347
|
apiSnapshot: apiSnapshot,
|
|
348
|
+
openapi: openapi,
|
|
349
|
+
asyncapi: asyncapi,
|
|
350
|
+
wsClient: wsClient,
|
|
351
|
+
flag: flag,
|
|
338
352
|
safeJson: safeJson,
|
|
339
353
|
safeSchema: safeSchema,
|
|
340
354
|
pagination: pagination,
|
|
@@ -365,6 +379,7 @@ module.exports = {
|
|
|
365
379
|
cloudEvents: cloudEvents,
|
|
366
380
|
dsr: dsr,
|
|
367
381
|
outbox: outbox,
|
|
382
|
+
inbox: inbox,
|
|
368
383
|
ntpCheck: ntpCheck,
|
|
369
384
|
version: constants.version,
|
|
370
385
|
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AsyncAPI 3.0 protocol bindings.
|
|
4
|
+
*
|
|
5
|
+
* AsyncAPI describes asynchronous APIs (pubsub, websockets, kafka,
|
|
6
|
+
* mqtt, amqp, redis-streams, ...) and bindings carry per-protocol
|
|
7
|
+
* configuration that the spec body does not describe in a protocol-
|
|
8
|
+
* neutral way.
|
|
9
|
+
*
|
|
10
|
+
* The framework ships first-class binding builders for the four
|
|
11
|
+
* protocols its primitives speak:
|
|
12
|
+
*
|
|
13
|
+
* .websockets({ method, query, headers }) — RFC 6455 / RFC 7692
|
|
14
|
+
* .kafka({ topic, partitions, replicas, ... })
|
|
15
|
+
* .amqp({ exchange, queue, ... })
|
|
16
|
+
* .mqtt({ qos, retain, ... })
|
|
17
|
+
*
|
|
18
|
+
* Operators with other protocols (NATS, Redis Streams, AWS SNS, ...)
|
|
19
|
+
* pass plain JSON binding objects; the AsyncAPI builder accepts them
|
|
20
|
+
* unchanged.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
var validateOpts = require("./validate-opts");
|
|
24
|
+
var { defineClass } = require("./framework-error");
|
|
25
|
+
var AsyncApiError = defineClass("AsyncApiError", { alwaysPermanent: true });
|
|
26
|
+
|
|
27
|
+
function websockets(opts) {
|
|
28
|
+
opts = opts || {};
|
|
29
|
+
validateOpts(opts, [
|
|
30
|
+
"method", "query", "headers", "bindingVersion",
|
|
31
|
+
], "asyncapi.bindings.websockets");
|
|
32
|
+
if (opts.method != null) {
|
|
33
|
+
var validMethods = ["GET", "POST"];
|
|
34
|
+
if (validMethods.indexOf(opts.method) === -1) {
|
|
35
|
+
throw new AsyncApiError("asyncapi/bad-binding",
|
|
36
|
+
"websockets: method must be GET or POST - got " + JSON.stringify(opts.method));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
var out = {};
|
|
40
|
+
if (opts.method) out.method = opts.method;
|
|
41
|
+
if (opts.query) out.query = opts.query;
|
|
42
|
+
if (opts.headers) out.headers = opts.headers;
|
|
43
|
+
out.bindingVersion = opts.bindingVersion || "0.1.0";
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function kafka(opts) {
|
|
48
|
+
opts = opts || {};
|
|
49
|
+
validateOpts(opts, [
|
|
50
|
+
"topic", "partitions", "replicas", "topicConfiguration",
|
|
51
|
+
"bindingVersion", "groupId", "clientId", "schemaRegistryUrl",
|
|
52
|
+
"schemaRegistryVendor", "schemaIdLocation", "schemaIdPayloadEncoding",
|
|
53
|
+
"schemaLookupStrategy", "key",
|
|
54
|
+
], "asyncapi.bindings.kafka");
|
|
55
|
+
if (opts.topic != null) {
|
|
56
|
+
validateOpts.requireNonEmptyString(opts.topic,
|
|
57
|
+
"kafka: topic", AsyncApiError, "asyncapi/bad-binding");
|
|
58
|
+
}
|
|
59
|
+
if (opts.partitions != null &&
|
|
60
|
+
(typeof opts.partitions !== "number" || opts.partitions <= 0)) {
|
|
61
|
+
throw new AsyncApiError("asyncapi/bad-binding",
|
|
62
|
+
"kafka: partitions must be a positive number");
|
|
63
|
+
}
|
|
64
|
+
if (opts.replicas != null &&
|
|
65
|
+
(typeof opts.replicas !== "number" || opts.replicas <= 0)) {
|
|
66
|
+
throw new AsyncApiError("asyncapi/bad-binding",
|
|
67
|
+
"kafka: replicas must be a positive number");
|
|
68
|
+
}
|
|
69
|
+
var out = {};
|
|
70
|
+
if (opts.topic) out.topic = opts.topic;
|
|
71
|
+
if (opts.partitions) out.partitions = opts.partitions;
|
|
72
|
+
if (opts.replicas) out.replicas = opts.replicas;
|
|
73
|
+
if (opts.topicConfiguration) out.topicConfiguration = opts.topicConfiguration;
|
|
74
|
+
if (opts.groupId) out.groupId = opts.groupId;
|
|
75
|
+
if (opts.clientId) out.clientId = opts.clientId;
|
|
76
|
+
if (opts.schemaRegistryUrl) out.schemaRegistryUrl = opts.schemaRegistryUrl;
|
|
77
|
+
if (opts.schemaRegistryVendor) out.schemaRegistryVendor = opts.schemaRegistryVendor;
|
|
78
|
+
if (opts.schemaIdLocation) out.schemaIdLocation = opts.schemaIdLocation;
|
|
79
|
+
if (opts.schemaIdPayloadEncoding) out.schemaIdPayloadEncoding = opts.schemaIdPayloadEncoding;
|
|
80
|
+
if (opts.schemaLookupStrategy) out.schemaLookupStrategy = opts.schemaLookupStrategy;
|
|
81
|
+
if (opts.key) out.key = opts.key;
|
|
82
|
+
out.bindingVersion = opts.bindingVersion || "0.5.0";
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function amqp(opts) {
|
|
87
|
+
opts = opts || {};
|
|
88
|
+
validateOpts(opts, [
|
|
89
|
+
"is", "exchange", "queue", "deliveryMode",
|
|
90
|
+
"mandatory", "bcc", "replyTo", "timestamp", "ack", "bindingVersion",
|
|
91
|
+
], "asyncapi.bindings.amqp");
|
|
92
|
+
if (opts.is != null && opts.is !== "queue" && opts.is !== "routingKey") {
|
|
93
|
+
throw new AsyncApiError("asyncapi/bad-binding",
|
|
94
|
+
"amqp: is must be 'queue' or 'routingKey' - got " + JSON.stringify(opts.is));
|
|
95
|
+
}
|
|
96
|
+
var out = {};
|
|
97
|
+
if (opts.is) out.is = opts.is;
|
|
98
|
+
if (opts.exchange) out.exchange = opts.exchange;
|
|
99
|
+
if (opts.queue) out.queue = opts.queue;
|
|
100
|
+
if (opts.deliveryMode != null) out.deliveryMode = opts.deliveryMode;
|
|
101
|
+
if (opts.mandatory === true) out.mandatory = true;
|
|
102
|
+
if (Array.isArray(opts.bcc)) out.bcc = opts.bcc.slice();
|
|
103
|
+
if (opts.replyTo) out.replyTo = opts.replyTo;
|
|
104
|
+
if (opts.timestamp === true) out.timestamp = true;
|
|
105
|
+
if (opts.ack === true) out.ack = true;
|
|
106
|
+
out.bindingVersion = opts.bindingVersion || "0.3.0";
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function mqtt(opts) {
|
|
111
|
+
opts = opts || {};
|
|
112
|
+
validateOpts(opts, [
|
|
113
|
+
"qos", "retain", "messageExpiryInterval", "topic",
|
|
114
|
+
"bindingVersion",
|
|
115
|
+
], "asyncapi.bindings.mqtt");
|
|
116
|
+
if (opts.qos != null) {
|
|
117
|
+
if (typeof opts.qos !== "number" || opts.qos < 0 || opts.qos > 2 ||
|
|
118
|
+
Math.floor(opts.qos) !== opts.qos) {
|
|
119
|
+
throw new AsyncApiError("asyncapi/bad-binding",
|
|
120
|
+
"mqtt: qos must be 0, 1, or 2 - got " + JSON.stringify(opts.qos));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
var out = {};
|
|
124
|
+
if (typeof opts.qos === "number") out.qos = opts.qos;
|
|
125
|
+
if (opts.retain === true) out.retain = true;
|
|
126
|
+
if (typeof opts.messageExpiryInterval === "number") {
|
|
127
|
+
out.messageExpiryInterval = opts.messageExpiryInterval;
|
|
128
|
+
}
|
|
129
|
+
if (opts.topic) out.topic = opts.topic;
|
|
130
|
+
out.bindingVersion = opts.bindingVersion || "0.2.0";
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function http(opts) {
|
|
135
|
+
opts = opts || {};
|
|
136
|
+
validateOpts(opts, [
|
|
137
|
+
"type", "method", "query", "statusCode", "headers", "bindingVersion",
|
|
138
|
+
], "asyncapi.bindings.http");
|
|
139
|
+
if (opts.type != null && opts.type !== "request" && opts.type !== "response") {
|
|
140
|
+
throw new AsyncApiError("asyncapi/bad-binding",
|
|
141
|
+
"http: type must be 'request' or 'response' - got " + JSON.stringify(opts.type));
|
|
142
|
+
}
|
|
143
|
+
var out = {};
|
|
144
|
+
if (opts.type) out.type = opts.type;
|
|
145
|
+
if (opts.method) out.method = opts.method;
|
|
146
|
+
if (opts.query) out.query = opts.query;
|
|
147
|
+
if (opts.statusCode != null) out.statusCode = opts.statusCode;
|
|
148
|
+
if (opts.headers) out.headers = opts.headers;
|
|
149
|
+
out.bindingVersion = opts.bindingVersion || "0.3.0";
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
websockets: websockets,
|
|
155
|
+
kafka: kafka,
|
|
156
|
+
amqp: amqp,
|
|
157
|
+
mqtt: mqtt,
|
|
158
|
+
http: http,
|
|
159
|
+
AsyncApiError: AsyncApiError,
|
|
160
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AsyncAPI 3.0 traits — reusable fragments that operations and
|
|
4
|
+
* messages can inherit via the `traits` array. Operators define a
|
|
5
|
+
* trait once (e.g. "every kafka publish carries a tracing-header
|
|
6
|
+
* envelope") and apply it to every relevant operation.
|
|
7
|
+
*
|
|
8
|
+
* AsyncAPI's trait-merge semantics: each trait is merged into the
|
|
9
|
+
* parent in declaration order, with later traits overriding earlier
|
|
10
|
+
* ones; the parent (operation / message) overrides any trait. This
|
|
11
|
+
* module ships a `applyTraits(parent, traits)` helper that performs
|
|
12
|
+
* the merge in-process so operators can compose traits without
|
|
13
|
+
* relying on consumer tooling to do it.
|
|
14
|
+
*
|
|
15
|
+
* var docTrait = b.asyncapi.traits.operation({
|
|
16
|
+
* bindings: { kafka: { groupId: "consumers-prod" } },
|
|
17
|
+
* tags: [{ name: "kafka" }],
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* var op = b.asyncapi.traits.applyOperation(
|
|
21
|
+
* { action: "send", channel: "events" },
|
|
22
|
+
* [docTrait],
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* The framework's primary use of this module is as a building block —
|
|
26
|
+
* operators can also pass `traits: [...]` arrays directly into
|
|
27
|
+
* `builder.operation(...)` / `builder.message(...)` without using
|
|
28
|
+
* these helpers, but applyTraits gives a side-channel for reuse.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
var validateOpts = require("./validate-opts");
|
|
32
|
+
var { defineClass } = require("./framework-error");
|
|
33
|
+
var AsyncApiError = defineClass("AsyncApiError", { alwaysPermanent: true });
|
|
34
|
+
|
|
35
|
+
var OPERATION_TRAIT_KEYS = [
|
|
36
|
+
"title", "summary", "description", "security", "tags",
|
|
37
|
+
"bindings", "externalDocs",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
var MESSAGE_TRAIT_KEYS = [
|
|
41
|
+
"headers", "correlationId", "schemaFormat", "contentType",
|
|
42
|
+
"name", "title", "summary", "description", "tags",
|
|
43
|
+
"bindings", "externalDocs", "examples", "traits",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function operation(spec) {
|
|
47
|
+
if (!spec || typeof spec !== "object") {
|
|
48
|
+
throw new AsyncApiError("asyncapi/bad-trait",
|
|
49
|
+
"traits.operation: spec must be an object");
|
|
50
|
+
}
|
|
51
|
+
validateOpts(spec, OPERATION_TRAIT_KEYS, "traits.operation");
|
|
52
|
+
return Object.freeze(_clone(spec));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function message(spec) {
|
|
56
|
+
if (!spec || typeof spec !== "object") {
|
|
57
|
+
throw new AsyncApiError("asyncapi/bad-trait",
|
|
58
|
+
"traits.message: spec must be an object");
|
|
59
|
+
}
|
|
60
|
+
validateOpts(spec, MESSAGE_TRAIT_KEYS, "traits.message");
|
|
61
|
+
return Object.freeze(_clone(spec));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function applyOperation(parent, traits) {
|
|
65
|
+
return _apply(parent, traits, OPERATION_TRAIT_KEYS, "traits.applyOperation");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function applyMessage(parent, traits) {
|
|
69
|
+
return _apply(parent, traits, MESSAGE_TRAIT_KEYS, "traits.applyMessage");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function _apply(parent, traits, keys, label) {
|
|
73
|
+
if (!parent || typeof parent !== "object") {
|
|
74
|
+
throw new AsyncApiError("asyncapi/bad-apply",
|
|
75
|
+
label + ": parent must be an object");
|
|
76
|
+
}
|
|
77
|
+
if (traits == null) return _clone(parent);
|
|
78
|
+
if (!Array.isArray(traits)) {
|
|
79
|
+
throw new AsyncApiError("asyncapi/bad-apply",
|
|
80
|
+
label + ": traits must be an array");
|
|
81
|
+
}
|
|
82
|
+
var merged = {};
|
|
83
|
+
for (var i = 0; i < traits.length; i += 1) {
|
|
84
|
+
var trait = traits[i];
|
|
85
|
+
if (!trait || typeof trait !== "object") continue;
|
|
86
|
+
for (var k = 0; k < keys.length; k += 1) {
|
|
87
|
+
var key = keys[k];
|
|
88
|
+
if (Object.prototype.hasOwnProperty.call(trait, key)) {
|
|
89
|
+
merged[key] = _mergeKey(key, merged[key], trait[key]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Parent overrides traits.
|
|
94
|
+
for (var pk in parent) {
|
|
95
|
+
if (!Object.prototype.hasOwnProperty.call(parent, pk)) continue;
|
|
96
|
+
merged[pk] = _mergeKey(pk, merged[pk], parent[pk]);
|
|
97
|
+
}
|
|
98
|
+
return merged;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _mergeKey(key, base, overlay) {
|
|
102
|
+
if (overlay == null) return base;
|
|
103
|
+
if (base == null) return _clone(overlay);
|
|
104
|
+
// Arrays merge by concatenation (`tags`, `examples`).
|
|
105
|
+
if (Array.isArray(base) && Array.isArray(overlay)) {
|
|
106
|
+
return base.concat(overlay);
|
|
107
|
+
}
|
|
108
|
+
// Objects merge shallow (`bindings`, `headers`).
|
|
109
|
+
if (typeof base === "object" && typeof overlay === "object" &&
|
|
110
|
+
!Array.isArray(base) && !Array.isArray(overlay)) {
|
|
111
|
+
var out = {};
|
|
112
|
+
for (var k1 in base) {
|
|
113
|
+
if (Object.prototype.hasOwnProperty.call(base, k1)) out[k1] = base[k1];
|
|
114
|
+
}
|
|
115
|
+
for (var k2 in overlay) {
|
|
116
|
+
if (Object.prototype.hasOwnProperty.call(overlay, k2)) out[k2] = overlay[k2];
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
return overlay; // scalar overrides
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function _clone(value) {
|
|
124
|
+
if (value == null || typeof value !== "object") return value;
|
|
125
|
+
if (Array.isArray(value)) return value.map(_clone);
|
|
126
|
+
var out = {};
|
|
127
|
+
for (var k in value) {
|
|
128
|
+
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
|
129
|
+
out[k] = _clone(value[k]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
operation: operation,
|
|
137
|
+
message: message,
|
|
138
|
+
applyOperation: applyOperation,
|
|
139
|
+
applyMessage: applyMessage,
|
|
140
|
+
OPERATION_TRAIT_KEYS: OPERATION_TRAIT_KEYS,
|
|
141
|
+
MESSAGE_TRAIT_KEYS: MESSAGE_TRAIT_KEYS,
|
|
142
|
+
AsyncApiError: AsyncApiError,
|
|
143
|
+
};
|