@blamejs/core 0.8.60 → 0.8.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/index.js +11 -0
- package/lib/audit.js +1 -0
- package/lib/auth/ciba.js +538 -0
- package/lib/auth/oauth.js +199 -11
- package/lib/auth/oid4vci.js +588 -0
- package/lib/auth/oid4vp.js +514 -0
- package/lib/auth/openid-federation.js +523 -0
- package/lib/auth/saml.js +636 -0
- package/lib/auth/sd-jwt-vc-holder.js +30 -8
- package/lib/auth/sd-jwt-vc.js +61 -7
- package/lib/db-collection.js +402 -105
- package/lib/db-file-lifecycle.js +333 -0
- package/lib/session-stores.js +138 -0
- package/lib/session.js +437 -20
- package/lib/validate-opts.js +41 -0
- package/lib/xml-c14n.js +499 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,12 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.8.x
|
|
10
10
|
|
|
11
|
+
- v0.8.66 (2026-05-10) — `b.session.updateData(token, data, opts?)`. Update the sealed `data` payload on a session WITHOUT rotating the sid. Pre-v0.8.66 the only path to mutate session data was `b.session.rotate(token, { data })` which forces an sid rotation — appropriate for security-boundary transitions (login, MFA, role escalation) but heavyweight for cart-state writes / preference flips / step-up-completion flags. Default semantics: full payload replace, `lastActivity` bumped (idle-timeout reset), reserved `__bj_fingerprint` binding preserved automatically so verify() still surfaces drift correctly. `opts.merge: true` does a one-level deep merge into the existing payload; `opts.touchLastActivity: false` skips the idle-timeout bump. Returns `false` for unknown / expired / pre-v0.8.61-raw-format tokens (no throw). Anonymous-session userIds work the same as named userIds. Leader-only. New Layer 0 tests: 13 checks covering replace / merge / null-clear / fingerprint preservation / unknown-token returns / array refusal.
|
|
12
|
+
- v0.8.65 (2026-05-10) — federated-authentication integration test fixture (Keycloak as OIDC OP + SAML IdP). Adds `quay.io/keycloak/keycloak:26.0` to `docker-compose.test.yml` running with realm-import on port `:18080` (HTTP) + `:18081` (Quarkus health). Realm `blamejs-test` boots with one OIDC client (`blamejs-rp-oidc`, secret `blamejs-test-rp-secret`, frontchannel + backchannel logout enabled), one SAML SP client (entityID `https://sp.blamejs-test.example`, RSA-SHA256 assertion signature), and a test user (`alice` / `blamejs-test-password`). New `test/integration/federation-auth.test.js` exercises end-to-end against the live Keycloak: OIDC discovery, `b.auth.oauth.authorizationUrl` (state + nonce + PKCE), password-grant token retrieval, `verifyIdToken` against the realm JWKS, `fetchUserInfo` with `idTokenSub` cross-check, RP-Initiated Logout URL build, `parseFrontchannelLogoutRequest` iss-mismatch refusal, `verifyBackchannelLogoutToken` JWKS-lookup + signature + typ-check failure paths, SAML `buildAuthnRequest` POST to the IdP's `/protocol/saml` endpoint, SP `metadata()` XML emit, and CIBA `startAuthentication` wire-format check (Keycloak's `backchannel_authentication_endpoint` is at `/protocol/openid-connect/ext/ciba/auth`). `b.auth.ciba._postForm` now sets `responseMode: "always-resolve"` so deterministic OAuth-shape error JSON (`{ error, error_description }`) reaches the AuthError-mapping path instead of being rejected as a generic HTTP error. `b.auth.ciba._postForm` URL validation switched from a non-existent `safeUrl.assertHttpUrl` to `safeUrl.parse({ allowedProtocols })`. `scripts/check-services.js` registers `keycloak` + `keycloak-health` (both v4 + v6); `test/helpers/services.js` exposes `URLS.keycloak`. CLAUDE.md release-workflow §6 documents the federation-auth integration test path. OID4VCI / OID4VP / OpenID Federation deferred — Keycloak's `oid4vc-issuer` SPI is preview-only and there's no entity-statement publisher in the base image.
|
|
13
|
+
- v0.8.64 (2026-05-10) — CI green-up for v0.8.63 (wiki source-comment validator). The v0.8.62 push missed `examples/wiki/test/validate-source-comment-blocks.js` — a wiki-side validator that enforces every `@primitive` block has `@signature` starting with `b.`, matching function arity, an `@opts` declaration when the function takes opts, an `@example` block, and resolvable `@related` cross-refs. 50 findings across the new federation/VC primitives. Fixed: every nested-namespace `@signature` (`b.auth.ciba.client.X`, `b.auth.oid4vci.issuer.X`, `b.auth.oid4vp.verifier.X`, `b.auth.saml.sp.X`) re-qualified with the full `b.*` prefix instead of the bare `client.X` shorthand; arity collapsed to `(opts)` where the function takes a single opts arg; `@example` block added to every primitive; `@opts` block added to ciba.client.startAuthentication / parseNotification + openidFederation.resolveLeaf; `@primitive` block added to xmlC14n.parse; `@related` cross-refs scrubbed of dangling references to undocumented primitives (oauth.create / sdJwtVc.issuer aren't @primitive-documented yet); the parse-as-JS check on oid4vci's @example caught a `{...}` literal with no target — replaced with a concrete object spread. No primitive surface change versus v0.8.63.
|
|
14
|
+
- v0.8.63 (2026-05-10) — CI green-up for v0.8.62. The v0.8.62 npm-publish workflow's lint gate flagged ten ESLint findings the local pre-ship audit missed (eslint not run before commit): unused vars (`openTag` in xml-c14n; `cache` + unused `C` import + `DEFAULT_CHAIN_TTL_MS` in openid-federation; `safeJson` in oid4vp; `sdJwtVcCore` + `SUPPORTED_PROOF_TYPES` in oid4vci), an unnecessary `\-` escape in xml-c14n's name-character regex, and a control-character regex in ciba's binding_message validator (eslint's `no-control-regex` refuses control-char ranges in regex literals regardless of `\u` escaping). Fixed by removing the dead vars + dead escape, and replacing the regex with an explicit codepoint scan in `_validateBindingMessage` that walks `msg.charCodeAt(i)` and refuses C0 / DEL+C1 / zero-width / bidi-mark / bidi-isolate / BOM ranges. No primitive surface change versus v0.8.62.
|
|
15
|
+
- v0.8.62 (2026-05-10) — federation / VC primitive family + standalone DB-file lifecycle + anonymous-session ergonomics. **OpenID Connect Front-Channel + Back-Channel Logout 1.0** on `b.auth.oauth`: `parseFrontchannelLogoutRequest(req)` validates iss + sid query params; `verifyBackchannelLogoutToken(jwt, vopts)` verifies the JWS (typ=logout+jwt), validates the events claim per OIDC §2.6, refuses logout-tokens carrying nonce, requires sub OR sid, and runs an operator-supplied `seen({jti,iss,iat})` callback for jti-replay defense. Discovery surface extended to `check_session_iframe` + `backchannel_authentication_endpoint`. **CIBA Core 1.0** at `b.auth.ciba.client.create({ deliveryMode: "poll"|"ping"|"push", ... })` with `startAuthentication` / `pollToken` / `parseNotification`; supports JWT-bearer / mTLS / shared-secret client auth, binding_message + acr_values + requested_expiry, ping/push notification token (timing-safe compared via sha3 hash), and the urn:openid:params:grant-type:ciba grant. **OpenID4VCI 1.0** at `b.auth.oid4vci.issuer.create({ sdJwtIssuer, supportedCredentials, ... })` — issuer-initiated credential_offer with pre-authorized_code + tx_code, /token grant exchange, /credential endpoint with proof-JWT verification (typ=openid4vci-proof+jwt, iat freshness, nonce-replay defense, holder-key binding via header.jwk + JWS verify), c_nonce rotation per request, /.well-known/openid-credential-issuer metadata. Composes `b.auth.sdJwtVc.issuer` for SD-JWT VC minting. **OpenID4VP 1.0 + DCQL** at `b.auth.oid4vp.verifier.create({ ... })` — `createRequest` builds a vp_token authz request with a DCQL query; `verifyResponse` parses the wallet's vp_token, runs each presentation through `b.auth.sdJwtVc.verify` (audience + nonce + KB-JWT bound, optional key_attestation verifier), then runs the DCQL matcher. DCQL implementation covers credentials[] (id / format / meta vct_values / meta issuer_values / claims with path + values) and credential_sets[] (options + required) per OID4VP §6. **OpenID Federation 1.0** at `b.auth.openidFederation.{parseEntityStatement, verifyEntityStatement, buildTrustChain, applyMetadataPolicy, resolveLeaf}` — fetches entity configs from `<entity>/.well-known/openid-federation`, walks `authority_hints` up to a trust anchor (operator-pinned JWKS), verifies each subordinate-statement JWS, and applies the federation's metadata_policy (value / default / add / one_of / subset_of / superset_of / essential — unknown operators refuse). **SAML 2.0 SP** at `b.auth.saml.sp.create({ entityId, idpEntityId, idpCertPem, ... })` — AuthnRequest builder for HTTP-Redirect/POST bindings, Response parser that verifies XMLDSig (Response-level OR Assertion-level signature), defends against signature-wrapping via `b.xmlC14n.canonicalizeElementById`'s single-match invariant, validates SubjectConfirmation Bearer (NotOnOrAfter / NotBefore / Recipient / InResponseTo) + Conditions audience + Status. Plus `b.auth.saml.fetchMdq({ baseUrl, entityId, trustCertPem? })` for MDQ-style metadata fetch with optional XMLDSig verification. **`b.xmlC14n`** — RFC 3741 Exclusive XML Canonicalization 1.0 (SAML/XMLDSig subset): `canonicalize(input, opts?)` and `canonicalizeElementById(xml, id, opts?)`. The `canonicalizeElementById` single-match invariant is the core defense against XML signature-wrapping attacks (refuses ID collisions + zero-match references). Doctype + ENTITY refused at parse time. **SD-JWT VC `key_attestation` extension** — `b.auth.sdJwtVc.holder.store({..., keyAttestation })` persists a holder-side attestation JWT alongside the credential; `b.auth.sdJwtVc.present({..., keyAttestation })` embeds it in the KB-JWT header; `b.auth.sdJwtVc.verify(presentation, { keyAttestationVerifier, requireKeyAttestation })` surfaces the attestation token to an operator-supplied verifier so trust-anchor decisions (TEE / FIDO MDS3 / App Attest / Play Integrity) stay operator-side. **`b.db.fileLifecycle({ dataDir, vault, ... })`** — standalone encrypted-DB-file lifecycle for consumers that own their own SQLite handle (own schema, own migrations, own connection). Decrypts `<dataDir>/db.enc` to a tmpfs path (`/dev/shm` on Linux), exposes `dbPath` for the operator to open, runs a periodic re-encrypt flush via `startFlushTimer(db)`, returns an in-memory snapshot via `snapshot(db)`, and runs a graceful flush + cleanup via `flushAndCleanup(db, opts)`. Same envelope shape as `b.db`; no schema / audit-chain coupling. **`b.session.create({ anonymous: true })`** auto-mints `userId = "anon:" + crypto.randomUUID()` so operators running pre-login flows keep the full sealed-cookie + sealed-userId + sidHash + idle/absolute-timeout posture without rolling their own opaque-id pattern. `b.session.isAnonymous(userId)` helper for post-auth gates. `destroyAllForUser` refuses anon-prefix ids (per-session, not portable). **`validateOpts.makeNamespacedEmitters(prefix, { audit, observability })`** — collapses the recurring per-primitive `_emitAudit / _emitMetric` boilerplate into one call; new federation/VC primitives consume it. Wiki e2e regex updated for the v0.8.61 sealed-cookie format (`vault:<base64>` instead of pre-v0.8.61 hex).
|
|
16
|
+
- v0.8.61 (2026-05-10) — `b.db.collection` schemaless-document opts, `b.session` PQC-sealed cookie default, `/24`+`/64` IP-prefix fingerprint binding, pluggable session store. **Operator-visible breaking change**: `b.session.create(...).token` now returns a vault-sealed envelope (`vault:` prefix, ML-KEM-1024 + P-384 hybrid + XChaCha20-Poly1305) instead of the plaintext sid. Pre-v0.8.61 raw-sid cookies fail to unseal at `b.session.verify` and the affected user re-authenticates. Pre-v1.0 ships no compat shim — every existing session force-logs-out on upgrade. Storage path unchanged: the DB still keys on `sha3('bj-session:' || sid)`, sealing only changes the wire format. **db.collection schemaless extensions**: pass `{ overflow: "data" }` to fold every insert/update field outside the table's column list into a JSON-text column; `find` / `findOne` parse it and merge keys back onto the row. `WHERE` on an unknown field rewrites to `JSON_EXTRACT(<overflow>, '$.field')` for `$eq` / `$ne` / `$in` (range / `$like` require a real column with an index). Pass `{ jsonColumns: ["roles", "metadata"] }` to auto-`JSON.stringify` listed columns on write and parse them via `b.safeJson` on read; unknown columns refuse at first use. Pass `{ sealedFields: { email: "emailHash" } }` to co-locate the sealed-column / derived-hash declaration with the collection — registers via `b.cryptoField.registerTable` so the existing query-builder sealed-field rewrite (`where({ email: "x" })` → `where({ emailHash: <hash> })`) picks up automatically. **Session fingerprint subnet binding**: `fingerprintFields: ["clientIpPrefix"]` hashes the client IP at `/24` for IPv4 and `/64` for IPv6 (per RFC 4291 §2.5.4 customer-LAN allocation), so roaming carriers' per-request IP flips no longer log out healthy mobile users. IPv4-mapped IPv6 (`::ffff:1.2.3.4`) buckets as v4 `/24`. **Pluggable session store**: `b.session.useStore(store)` swaps the `_blamejs_sessions` storage backend; first-party adapter `b.session.stores.localDbThin({ file })` wraps `b.localDb.thin` (typically pointed at tmpfs) so heavy session churn doesn't fight the main DB's WAL fsync + at-rest re-encryption cycle. `audit.FRAMEWORK_NAMESPACES` extended with `localdb` so `b.localDb.thin` open / close events stop dropping. New per-primitive Layer 0 tests: `db-collection-extensions.test.js` (29 checks), `session-extensions.test.js` (24 checks).
|
|
11
17
|
- v0.8.60 (2026-05-09) — CI green-up for v0.8.59. macOS smoke runner failed `watcher.test.js: surface onChange for non-ignored file` again under SMOKE_PARALLEL=64 + GitHub-Actions-runner contention; the v0.8.56 bump from 300ms→1500ms wasn't enough on a contended Darwin runner. Replaced the fixed-budget wait with two structural fixes: (a) a 200ms priming wait BEFORE the test writes any files, so the FSEvents watcher is fully established before file-system activity races its startup; (b) a poll-until-event loop after writes that flushes + checks the change set every 100ms up to a 5s deadline, exiting early when the target event lands. Linux/Windows still finish in <100ms; the wider budget only matters on contended macOS. No primitive surface change versus v0.8.59.
|
|
12
18
|
- v0.8.59 (2026-05-09) — comment cleanup. Two stale references to an external consumer name inside `lib/db-collection.js` and `lib/template.js` comments stripped — names of downstream consumers don't belong in framework source. No primitive surface change versus v0.8.58.
|
|
13
19
|
- v0.8.58 (2026-05-09) — `b.db` query-builder + facade extensions, db.init opt-outs, snapshot helper, mtls path fix. **Query atoms** on `b.db.from(table)`: `.increment(column, delta)` (atomic `UPDATE col = COALESCE(col, 0) + ?`, refuses unconditional, returns rows-changed), `.whereGroup(qb => ...)` (closure-form OR composition via new `WhereBuilder` class with `.eq` / `.neq` / `.gt` / `.gte` / `.lt` / `.lte` / `.in` / `.like` AND vs `.orEq` / `.orNeq` / ... OR + `.raw`), `.orWhere(...)` (top-level OR; accepts object map / `(field, value)` / `(field, op, value)` / closure), `.search(fields, term, opts?)` (chainable LIKE-OR with `match: "substring"|"prefix"|"exact"`, `~` ESCAPE char to safely handle user-supplied `%`/`_`), `.paginate(opts)` (returns `{ items, total, limit, offset, page, totalPages }`; default limit 25, cap 1000). **Mongo facade** at `b.db.collection(name)` returning `{ insert, insertMany, find, findOne, update, updateMany, remove, count, paginate }` — maps Mongo-shape calls onto `b.db.from(name)`. Update operators: `$set` / `$inc` (composes `Query.increment`) / `$unset`. Query operators: `$eq` / `$ne` / `$gt` / `$gte` / `$lt` / `$lte` / `$in` / `$like`. Unknown operators throw at config-time. **db.init opt-outs**: `frameworkTables: false` skips provisioning `audit_log` / `consent_log` (operators with their own audit chain reuse the framework's `b.db` / `b.vault` / `b.cryptoField` primitives without the bundled chain tables — append-only triggers, chain verifies, WORM assertions, audit-signing bootstrap, checkpoint verifies, and the `audit_log` / `consent_log` reserved-name refusal all become no-ops). `auditSigning: false` (finer-grained — keep framework tables but skip the audit-signing-key bootstrap when the host manages its own signing key). **db.init path overrides**: `encryptedDbPath` (fully-qualified path to `db.enc`), `encryptedDbName` (basename under `dataDir`, default `"db.enc"`), `dbKeyPath` (encryption-key file outside `dataDir`, e.g. KMS-fronted volume). **`b.db.snapshot()`** — in-memory encrypted Buffer (same envelope `flushToDisk` writes, just held in memory; WAL checkpoint forced first so committed state captures cleanly). Plain mode returns the raw plaintext SQLite file. **`b.mtlsCa.create({ paths })` absolute-path fix** — `_resolvePaths` no longer joins absolute path entries under `dataDir`. The pre-v0.8.58 shape silently rewrote `MTLS_CA_KEY=/etc/ssl/ca.key` → `<dataDir>/etc/ssl/ca.key`, breaking operators with externally-mounted CA files. Standard `path.join` semantics already preserve absolute arguments — the always-join was an oversight. Relative entries still join under `dataDir` (back-compat). **CLAUDE.md release workflow §5** rewritten — host smoke + host wiki e2e run BEFORE container smoke + container wiki e2e, sequentially, never in parallel. Both runners write to `.test-output/smoke.log` and `.test-output/wiki-e2e.log`; parallel runs clobber each other so the log of the actually-blocking failure may be overwritten by whichever leg finishes second. The container leg writes to `smoke-container.log` / `wiki-e2e-container.log` so diagnose-after-failure is one file lookup. New per-primitive Layer 0 tests: `db-query-extensions.test.js` (27 checks), `db-collection.test.js` (23 checks), `db-init-extensions.test.js` (13 checks), `mtls-ca-paths.test.js` (7 checks). **Deferred**: configurable framework-schema column names — the original proposal was incomplete; re-open with the full spec. Configurable framework-table names (rename `audit_log` / `consent_log`) — `frameworkTables: false` covers the audit-conflict use case; the full rename touches dozens of hardcoded SQL literals across the audit + consent modules and is its own patch with a refactor proper.
|
package/README.md
CHANGED
|
@@ -41,8 +41,8 @@ var b = require("@blamejs/core");
|
|
|
41
41
|
|
|
42
42
|
The framework bundles the surface a typical Node app reaches for. Every primitive listed is callable today; nothing is a stub.
|
|
43
43
|
|
|
44
|
-
- **Data layer** — SQLite with sealed-by-default columns (`b.db`), migrations, seeders, atomic-file writes; chainable query builder with atomic `.increment(col, delta)`, closure-form `.whereGroup` / top-level `.orWhere` OR composition, `.search(fields, term)` LIKE-OR with safe `%`/`_` ESCAPE handling, `.paginate(opts)` returning `{ items, total, page, totalPages }`; Mongo-style document-store facade `b.db.collection(name)`
|
|
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,
|
|
44
|
+
- **Data layer** — SQLite with sealed-by-default columns (`b.db`), migrations, seeders, atomic-file writes; chainable query builder with atomic `.increment(col, delta)`, closure-form `.whereGroup` / top-level `.orWhere` OR composition, `.search(fields, term)` LIKE-OR with safe `%`/`_` ESCAPE handling, `.paginate(opts)` returning `{ items, total, page, totalPages }`; Mongo-style document-store facade `b.db.collection(name, opts?)` (`$set` / `$inc` / `$unset` / `$eq` / `$ne` / `$gt` / `$gte` / `$lt` / `$lte` / `$in` / `$like`) with schemaless-document opts — `overflow: "<col>"` folds unknown insert/update fields into a JSON-text column (and rewrites `WHERE` on virtual fields to `JSON_EXTRACT`), `jsonColumns: [...]` auto-stringifies on write + parses via `b.safeJson` on read, `sealedFields: { email: "emailHash" }` co-locates a `b.cryptoField` sealed-column / derived-hash declaration with the collection so plaintext lookups auto-rewrite to hash-column lookups; in-memory encrypted snapshot via `b.db.snapshot()`; standalone encrypted-DB-file lifecycle for consumers that own their own SQLite handle (`b.db.fileLifecycle({ dataDir, vault })` — decrypt-to-tmpfs, periodic re-encrypt flush, snapshot, graceful shutdown — same envelope as `b.db`, no schema/audit-chain coupling); `db.init` opt-outs for hosts that own their audit chain (`frameworkTables: false` / `auditSigning: false`) and configurable encrypted-file paths (`encryptedDbPath` / `encryptedDbName` / `dbKeyPath`); 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
|
+
- **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 + OIDC RP-Initiated / Front-Channel / Back-Channel Logout 1.0 (`b.auth.oauth.parseFrontchannelLogoutRequest` + `verifyBackchannelLogoutToken` with jti-replay defense), CIBA Core 1.0 decoupled-auth client (`b.auth.ciba`, poll/ping/push delivery, JWT-bearer / mTLS / shared-secret client auth), OpenID Federation 1.0 trust chain (`b.auth.openidFederation`, entity statement parse + verify, metadata_policy resolution), SAML 2.0 SP with XMLDSig signature-wrapping defense via single-match-by-ID invariant + RFC 9525 server-identity (`b.auth.saml`), OpenID4VCI 1.0 issuer (`b.auth.oid4vci` — credential_offer / pre-authorized_code / /credential with proof-JWT verification), OpenID4VP 1.0 verifier with DCQL query language (`b.auth.oid4vp`), SD-JWT VC with optional `key_attestation` extension for TEE / FIDO MDS3 / App Attest holder-key provenance (`b.auth.sdJwtVc`), sessions with PQC-sealed sid cookie (ML-KEM-1024 + P-384 hybrid + XChaCha20-Poly1305 envelope on the wire), `/24` IPv4 + `/64` IPv6 subnet binding via `fingerprintFields: ["clientIpPrefix"]` (carrier-roaming-safe), pluggable storage backend via `b.session.useStore` + first-party `b.session.stores.localDbThin` (tmpfs-fast session writes that don't fight the main DB's encrypted-at-rest re-flush cycle), opaque-userId anonymous sessions via `b.session.create({ anonymous: true })`, idle / absolute timeouts, optional 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`); FAPI 2.0 Final composite posture (PAR + PKCE-S256 + DPoP-or-mTLS sender-constrained tokens + RFC 9207 issuer-in-callback) for financial / Open Banking deployments (`b.fapi2`); CFPB §1033 / FDX 6.0 consumer-financial-data-sharing wrapper (`b.fdx`); cross-table data-subject coordination (export / rectify / erase / restrict / objection) walking every table tagged with the subject's column without app-side plumbing (`b.subject`, `b.subject.eraseHard`); subject-level legal-hold registry consulted by erase + retention paths (FRCP Rule 26/37(e), GDPR Art 17(3)(e), SEC Rule 17a-4, HIPAA §164.530(j)(2)) (`b.legalHold`); adaptive bot-challenge staircase composing `b.middleware.botGuard` + `b.auth.lockout` + operator challenge / escalation hooks for auth paths (`b.authBotChallenge`); session-to-device-posture binding (UA + Accept-Language + Accept-Encoding + IP /24 prefix + optional WebAuthn-bound key) with fail-closed verify on store error (`b.sessionDeviceBinding`).
|
|
46
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`), per-column data residency tagging + per-row keys (`K_row = HKDF(K_table, rowId)`) so erasing the per-row key makes WAL / replica residuals undecryptable — true crypto-shred discipline (`b.cryptoField.declareColumnResidency`, `b.cryptoField.declarePerRowKey`); 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 default; ML-DSA-65 opt-in for smaller signatures + faster verify), ECIES API encryption (`b.crypto`, `b.vault`, `b.webhook`); RFC 9180 HPKE with ML-KEM-1024 + HKDF-SHA3-512 + ChaCha20-Poly1305 suite (`b.crypto.hpke`); RFC 9421 HTTP Message Signatures with derived components (`@method`, `@target-uri`, `@authority`, `@path`, `@query`) and `created` / `expires` / `nonce` / `keyid` parameters; ed25519 (legacy peers) + ML-DSA-65 (PQC peers) (`b.crypto.httpSig`); RFC 9266 TLS-Exporter channel binding for token-to-session pinning (`b.tlsExporter`); RFC 9162 CT v2 inclusion-proof verification against signed tree heads (`b.network.tls.ct.verifyInclusion`); RFC 8555 ACME client + RFC 9773 ARI (Renewal Information) polling for the CA/Browser Forum 47-day certificate lifetime (`b.acme`); RFC 8470 0-RTT inbound posture (`refuse` default; `replay-cache` opt-in with 10s SHA3-512 dedupe window; fail-closed under `pci-dss` / `fapi2`) (`b.router.create({tls0Rtt})`); RFC 9794 SecP256r1MLKEM768 codepoint added to the preferred-group order (`b.network.tls.preferredGroups`); 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 (`b.openapi`) + AsyncAPI publication for event/streaming endpoints (`b.asyncapi`); full middleware stack (CSRF, CORS, rate-limit, security headers, CSP nonce, body parser, compression, SSE, request log, threat-aware cookie parser via `b.middleware.cookies`, 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`); operator-rendered error pages with no app-frame leakage (`b.errorPage`).
|
|
48
48
|
- **Defensive parsers** — `b.safeJson` (with `maxKeys` cap defending CVE-2026-21717 V8 HashDoS), `b.safeBuffer`, `b.safeSql`, `b.safeSchema`, `b.safeUrl` (IDN mixed-script / homograph refuse), `b.safeJsonPath` (JSON-path validator refusing filter `?(...)`, deep-scan `$..`, script-shape `(@.x)` for safe Postgres JSONB ops), `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.).
|
package/index.js
CHANGED
|
@@ -65,6 +65,11 @@ var vault = require("./lib/vault");
|
|
|
65
65
|
var vaultWrap = require("./lib/vault/wrap");
|
|
66
66
|
var vaultPassphraseSource = require("./lib/vault/passphrase-source");
|
|
67
67
|
var db = require("./lib/db");
|
|
68
|
+
// Standalone encrypted-DB-file lifecycle for consumers that own
|
|
69
|
+
// their own SQLite handle. Attached as b.db.fileLifecycle so it
|
|
70
|
+
// rides alongside the framework's full b.db API.
|
|
71
|
+
db.fileLifecycle = require("./lib/db-file-lifecycle").fileLifecycle;
|
|
72
|
+
var xmlC14n = require("./lib/xml-c14n");
|
|
68
73
|
var cryptoField = require("./lib/crypto-field");
|
|
69
74
|
var audit = require("./lib/audit");
|
|
70
75
|
// Attach the audit-tools dispatcher onto b.audit so operators can
|
|
@@ -184,6 +189,11 @@ var auth = {
|
|
|
184
189
|
authTime: require("./lib/auth/auth-time-tracker"),
|
|
185
190
|
accessLock: require("./lib/auth/access-lock"),
|
|
186
191
|
atoKillSwitch: require("./lib/auth/ato-kill-switch"),
|
|
192
|
+
ciba: require("./lib/auth/ciba"),
|
|
193
|
+
oid4vci: require("./lib/auth/oid4vci"),
|
|
194
|
+
oid4vp: require("./lib/auth/oid4vp"),
|
|
195
|
+
saml: require("./lib/auth/saml"),
|
|
196
|
+
openidFederation: require("./lib/auth/openid-federation"),
|
|
187
197
|
};
|
|
188
198
|
var template = require("./lib/template");
|
|
189
199
|
var render = require("./lib/render");
|
|
@@ -294,6 +304,7 @@ module.exports = {
|
|
|
294
304
|
vaultWrap: vaultWrap,
|
|
295
305
|
vaultPassphraseSource: vaultPassphraseSource,
|
|
296
306
|
db: db,
|
|
307
|
+
xmlC14n: xmlC14n,
|
|
297
308
|
cryptoField: cryptoField,
|
|
298
309
|
audit: audit,
|
|
299
310
|
auditChain: auditChain,
|
package/lib/audit.js
CHANGED
|
@@ -292,6 +292,7 @@ var FRAMEWORK_NAMESPACES = [
|
|
|
292
292
|
"mailmdn", // b.mailMdn (mailmdn.generated / mailmdn.suppressed — RFC 3798/8098 Message Disposition Notification)
|
|
293
293
|
"mailarf", // b.mailArf (mailarf.parsed / mailarf.malformed — RFC 5965 abuse-feedback ingestion)
|
|
294
294
|
"mailbimi", // b.mail.bimi (mail.bimi.vmc.fetched / verified — RFC 9091 VMC chain validation)
|
|
295
|
+
"localdb", // b.localDb.thin (localdb.thin.opened / recovered / closed — desktop-daemon SQLite wrapper)
|
|
295
296
|
];
|
|
296
297
|
var registeredNamespaces = new Set(FRAMEWORK_NAMESPACES);
|
|
297
298
|
|