@blamejs/core 0.11.1 → 0.11.2
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 +2 -0
- package/README.md +1 -1
- package/lib/backup/index.js +37 -13
- package/lib/cli.js +4 -4
- package/lib/restore.js +2 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.11.x
|
|
10
10
|
|
|
11
|
+
- v0.11.2 (2026-05-19) — **Node 26 floor-bump preparation.** Today's `engines.node` floor is `>=24.14.1` and the framework runs cleanly on Node 26 (which satisfies the floor). This release ships the **prep** scaffolding so the future floor-bump slice (when Node 26 promotes to Active LTS and `>=26.x` becomes the floor) is mechanical. **`b.backup.diskStorage(opts)`** is the new canonical name for the local-filesystem backup storage backend; `b.backup.localStorage(opts)` continues to work and emits a one-time deprecation warning via `b.deprecate.alias`, with removal scheduled for the next major. The rename avoids the Node 26 platform-level `localStorage` global naming collision; the deprecation path follows the framework's stable upgrade policy (one minor with deprecation warnings before removal). **New codebase-patterns detector `map-get-or-insert-pre-node-26`** flags the `if (!m.has(k)) m.set(k, factory()); m.get(k)` shape that Node 26's `Map.prototype.getOrInsertComputed(key, factory)` replaces in a single call. The detector lands as an allowlist marker — every existing call site in `lib/` is allowlisted with the spec file as the migration target; new code post-this-patch trips the gate. When the floor bumps the allowlist is walked + the detector flips to enforce. **`test/integration/pqc-pkcs8-forward-compat.test.js`** captures the ML-KEM-1024 / ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f / Ed25519 PKCS8 export-byte shape on the current Node, asserts the sign+verify / encap+decap roundtrip via a re-imported KeyObject, and embeds a Node-26-shape fixture that re-imports every run — so the forward-compat contract is testable today and the reverse-direction (Node-26-exported → Node-24-imported) test follows the floor-bump. **SECURITY.md gains a "Node 26 compatibility" section** documenting the `localStorage` global naming collision (bare references in operator handler code now resolve to a Node global rather than throwing `ReferenceError`) and the ML-KEM / ML-DSA seed-only PKCS8 export shape (Node-24-sealed material re-imports cleanly on Node 26; new material from Node 26 is seed-only — parallel Node 24 readers of the same sealed disk need a one-time migration when the writer moves). README "Requirements" line gains the matching Node 26 note. **References:** [Node.js v26 release notes](https://nodejs.org/en/blog/release/v26.0.0) · [TC39 Map.getOrInsertComputed](https://github.com/tc39/proposal-upsert) · [RFC 8032 §5.1 Ed25519 context parameter](https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1).
|
|
12
|
+
|
|
11
13
|
- v0.11.1 (2026-05-19) — **Integration suite hardening + live coverage for the v0.11.0 surface.** **`b.httpClient.request`** now skips the local SSRF DNS lookup when a proxy is configured AND the operator passes `allowInternal: true`. The proxy resolves the destination hostname in its own network context, so requiring local resolution refused legitimate intranet / docker-service-name targets routed through the proxy. The SSRF gate still runs when `allowInternal` is false / array-form (the proxy's freedom to reach internal IPs is not a blanket license; the explicit opt-in is still required). **`b.mtlsCa`** integration tests now compose with the `caKeySealedMode: "disabled"` opt for fixture purposes; production deployments continue to wire `opts.vault` for sealed-at-rest CA-key storage. **`b.mail.crypto.smime.verify`** return shape gains a `chainVerified: boolean` field reflecting whether `opts.trustAnchorCertsPem` was supplied and the leaf-to-root chain walk completed. **Integration coverage added for v0.11.0 primitives:** new `test/integration/mail-crypto-smime.test.js` round-trips S/MIME sign + verify with a real X.509 chain issued by `b.mtlsCa` (CA → leaf cert → ML-DSA-65 signer), exercises tamper / wrong-key / untrusted-anchor refusal paths, and validates the `chainVerified` return field. `test/integration/federation-auth.test.js` extends to cover SAML SLO (`buildLogoutRequest` against Keycloak's `/protocol/saml` SLO endpoint with the wire-format-parse assertion) and RFC 7592 Dynamic Client Registration Management (`registerClient` / `readClient` / `updateClient` / `deleteClient` against Keycloak's DCR endpoint).
|
|
12
14
|
|
|
13
15
|
- v0.11.0 (2026-05-19) — **Mail-crypto sign/verify + encrypt/decrypt, SAML Single Logout (Redirect / POST / SOAP) + EncryptedAssertion + Holder-of-Key, browser identity (FedCM / DBSC / VAPID), CSP3 builder, hypermedia + observability formats, OAuth DCR management + OIDC Native SSO, sectoral compliance posture growth.** **`b.cms.parseSignedData(buf)`** walks an inbound RFC 5652 SignedData ContentInfo and returns `{ digestAlgs, encapContent, certificates, signerInfos }` so consumers verify signatures without re-implementing the SignedData walker. **`b.mail.crypto.smime.sign(opts)` + `.verify(opts)`** are now live on the `b.cms` substrate: `sign` emits an RFC 8551 `multipart/signed; protocol="application/pkcs7-signature"; micalg=sha3-{256,512}` envelope; `verify` recomputes the message digest, compares it against the signed-attrs `messageDigest` attribute, refuses tamper with `mail-crypto/smime/message-digest-mismatch`, and PQC-verifies the signature against the operator-supplied signer public key. Supports ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f signers; SHA-2 family refused at `cms/bad-digest`. **`b.mail.crypto.pgp.experimental.encrypt(opts)` + `.decrypt(opts)` + `.wkd.computeUrl(email)`** ship under the `experimental` namespace because the relevant IANA codepoint registrations are still in draft: ML-KEM-1024 KEM + ChaCha20-Poly1305 AEAD multi-recipient envelope; WKD URL computer per [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/) (SHAKE256-hash localpart + zbase32 encoding; operators supply their own HTTPS fetcher). **`b.auth.saml.sp.buildLogoutRequest({...})` + `parseLogoutRequest(b64, opts)` + `buildLogoutResponse({...})`** implement SAML 2.0 Single Logout on the HTTP-Redirect binding per SAML Bindings §3.4.4.1 with PQC-signed canonical query (ML-DSA-65 / ML-DSA-87 / Ed25519); SP metadata now emits `<md:SingleLogoutService>` bindings when `singleLogoutServiceUrl` is set; tamper / wrong-key / missing-signature each refuse as typed errors. **`b.webPush.generateVapidKeypair()` + `.buildVapidAuthHeader(opts)`** sign the RFC 8292 VAPID JWT inline (ECDSA-P256 per the spec; the framework's PQC-default JWT signer refuses ES256 by design, so `b.webPush` owns the signing rather than relaxing the broader policy). **`b.fedcm`** ships the W3C FedCM 2024 IdP-side response builders: `wellKnown({ provider_urls })` / `config({ accounts_endpoint, ... })` / `accountsResponse({ accounts })` / `idAssertionResponse({ token })`. **`b.dbsc`** implements IETF Device-Bound Session Credentials: `challenge({ secretKey })` mints an HMAC-SHA3-512-signed token requiring no server-side storage; `verifyBindingAssertion(jwt, opts)` refuses HS256 / none as algorithm-confusion, validates ES256/RS256 against the embedded JWK, and returns the RFC 7638 JWK thumbprint so operators pin the binding key to a session. **`b.importmapIntegrity.build({ modules })`** emits a WICG Import Maps + SRI integrity map (SHA-384 default) so browsers refuse module bytes that don't match. **OpenMetrics 1.0 exposition** — `b.metrics` registry `exposition({ format: "openmetrics" })` emits the counter `_total` suffix, `# UNIT` lines, exemplar trace IDs on histograms, and `# EOF` terminator per the openmetrics.io 1.0 wire format. **`b.standardWebhooks.sign + verify`** implement the standardwebhooks.com consortium spec (Stripe / Svix / Okta wire format): HMAC-SHA256, multi-version signature header, 5-minute default skew tolerance. **`b.lro`** implements AIP-151 Long-Running Operations (`create({ store }) → { submit, status, list, cancel }`) with operator-supplied storage and AbortSignal-aware cancellation. **`b.jsonApi.dataResponse(data, opts) + .errorResponse(errors)`** wraps domain payloads in the JSON:API v1.1 top-level shape; refuses missing Resource Object `type`. **`b.hal.resource(payload, { links, embedded, templates })`** builds HAL responses (draft-kelly-json-hal) with an RFC 8288 link-object normaliser. **Lib-side codebase-patterns detector `number-coerce-or-zero-on-json-source`** refuses `Number(<var>['<kebab-cased-key>']) || 0` shapes — that coercion silently accepts `Infinity`, NaN, negative values, and arbitrary strings on operator-untrusted JSON-source input. **(a) `b.cms.parseSignedData(buf)`** — RFC 5652 §5.1 SignedData walker that surfaces `digestAlgs` / `encapContent` / `certificates` / `signerInfos` as structured arrays so downstream verifiers can check signatures without re-implementing the walker. **(b) `b.mail.crypto.smime.sign(opts)` + `b.mail.crypto.smime.verify(opts)` — LIVE on the b.cms substrate.** `sign` composes `b.cms.encodeSignedData` and wraps in an RFC 8551 `multipart/signed; protocol="application/pkcs7-signature"; micalg=sha3-{256,512}` envelope. `verify` parses the CMS SignedData payload, walks signed-attributes to extract the `messageDigest` attribute, recomputes the message digest, refuses tamper with `mail-crypto/smime/message-digest-mismatch`, and PQC-verifies the signature against the operator-supplied signer public key. Supports ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f signers; SHA-2 family refused at `cms/bad-digest`. **(c) `b.mail.crypto.pgp.experimental.encrypt(opts)` + `decrypt(opts)` + `wkd.computeUrl(email)`** — PQC PGP encrypt/decrypt under `experimental` namespace (RFC 9580bis PKESK ML-KEM codepoints haven't IANA-registered yet; framework-private envelope similar to v0.10.10 `b.jose.jwe.experimental`). ML-KEM-1024 KEM + ChaCha20-Poly1305 AEAD + per-recipient KEK derived via SHAKE256 bound to the literal label `pgp/experimental/chacha20-poly1305`. Multi-recipient envelopes; tamper / wrong-key refusal as typed errors. WKD URL computer per [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/) — SHAKE256-hash localpart + zbase32 encoding; returns `{ direct, advanced }` URLs (operators supply their own HTTPS fetcher). **(d) `b.auth.saml.sp.buildLogoutRequest({...})` + `parseLogoutRequest(b64, opts)` + `buildLogoutResponse({...})`** — SAML 2.0 Single Logout on the HTTP-Redirect binding per SAML Bindings §3.4.4.1 with PQC-signed canonical query string (ML-DSA-65 / ML-DSA-87 / Ed25519). `parseLogoutRequest` inflates the operator-supplied SAMLRequest, optionally verifies the redirect-binding signature against an IdP public key, and surfaces NameID / SessionIndex / Issuer. Tamper / wrong-key / missing-signature each refuse as typed errors. Closes the largest item from the v0.10.15 SAML SLO + identity-residual queued plan. **(e) Codex P2 detector `number-coerce-or-zero-on-json-source`** (lib-side) — flags `Number(<var>['<kebab-cased-key>']) || 0` shapes that silently accept Infinity / NaN / negative on operator-untrusted JSON-source numeric input. The v0.10.15 TLS-RPT fix was a one-off; this detector forces the discipline. **Additional residual closure:** `b.auth.saml.sp.buildLogoutRequestPost` / `parseLogoutRequestPost` / `buildLogoutRequestSoap` / `parseLogoutResponseSoap` add SAML SLO HTTP-POST + SOAP synchronous back-channel bindings with embedded XMLDSig-Enveloped signatures. **SignatureMethod surface** spans the W3C XMLDSig Core 1.1 + RFC 9231 vocabulary so the framework interops with deployed IdPs out of the box: `rsa-sha256` / `rsa-sha384` / `rsa-sha512` (W3C XMLDSig Core 1.1), `ecdsa-sha256` / `ecdsa-sha384` / `ecdsa-sha512` (W3C XMLDSig Core 1.1), `ed25519` (RFC 9231). Classical keys are PEM strings or `node:crypto` KeyObject instances; PQC keys remain `Uint8Array` from `b.pqcSoftware.ml_dsa_*.keygen()`. Post-quantum `ml-dsa-65` / `ml-dsa-87` are also accepted on the same surface under clearly framework-private URIs (`urn:blamejs:experimental:saml-sig-alg:ml-dsa-65` / `:ml-dsa-87`) — no IETF/W3C XMLDSig registration exists for ML-DSA yet (LAMPS WG drafts in flight). Verification refuses `'unsupported-c14n'` (only exclusive c14n; inclusive-c14n IdPs must upgrade), alg-confusion (SignatureMethod URI must match the operator-declared `idpVerifyAlg`), signature-wrapping (Reference URI must match root ID via timing-safe digest compare), and SHA-1 digest methods (CVE-2017-7525-class). **EncryptedAssertion (SAML 2.0 §2.5)** decrypts AES-128-GCM / AES-256-GCM (W3C XMLEnc 1.1 §5.2.4) content + RSA-OAEP-MGF1P / xmlenc11 RSA-OAEP key transport (SHA-256/384/512 only — SHA-1 OAEP refused as CVE-2023-49141-class). AES-CBC content encryption is **refused** under both `xmlenc#aes128-cbc` and `xmlenc#aes256-cbc` — CVE-2011-1473 padding-oracle research makes CBC mode under XMLEnc unsuitable without a per-content MAC; operators integrating with IdPs that default to CBC (older ADFS / Azure AD / Okta / Keycloak / OneLogin) switch the IdP's content-encryption setting to AES-128-GCM or AES-256-GCM. Framework-experimental URIs `urn:blamejs:experimental:xmlenc:ml-kem-1024` (key transport) and `urn:blamejs:experimental:xmlenc:xchacha20-poly1305` (content) are accepted alongside the W3C URIs. `b.mail.crypto.smime.verify({ trustAnchorCertsPem })` now walks the SignerInfo cert chain leaf-to-root via `node:crypto.X509Certificate` with notBefore/notAfter checks and refuses `mail-crypto/smime/untrusted-chain` / `cert-expired` / `cert-not-yet-valid`; revocation is operator-wired via `b.network.tls.ocsp` when freshness is required. `b.auth.saml.sp.verifyResponse({ holderOfKey: { presentedCertPem } })` honors `urn:oasis:names:tc:SAML:2.0:cm:holder-of-key` SubjectConfirmation: SHA3-512 fingerprint of the embedded KeyInfo/X509Data certificate is compared against the operator-supplied presented mTLS / possession-proof cert via timingSafeEqual; HoK and Bearer confirmations coexist. `b.auth.oauth.readClient(uri, token)` / `updateClient(uri, token, metadata)` / `deleteClient(uri, token)` implement RFC 7592 Dynamic Client Registration Management Protocol (GET/PUT/DELETE bound to the registration_access_token returned by `registerClient`); updateClient enforces the same redirect_uris-array refusal as registerClient. `b.auth.oauth.nativeSsoExchange({ deviceSecret, idToken, audience })` convenience-wraps `exchangeToken` for OpenID Connect Native SSO 1.0 §6 with `urn:openid:params:token-type:device-secret` added to the RFC 8693 §3 token-type allowlist. **`b.csp.build(directives, opts?)` + `b.csp.nonce(byteLen?)` + `b.csp.hash(scriptBody, alg?)`** ship the CSP Level 3 builder surface: refuses `'unsafe-*'` / catch-all `*` / `https:` / `data:` in non-image directives without an explicit acknowledgement opt; auto-appends `require-trusted-types-for 'script'` plus the operator-supplied `trusted-types` policy list when any script-* directive is set; emits ≥128-bit nonces by default; computes sha256/sha384/sha512 hash sources for inline scripts. `b.middleware.securityHeaders` `coep` opt now documents the W3C CR 2024-12 `credentialless` value alongside `require-corp`. **`b.compliance` posture catalog gains 17 sectoral / cybersecurity / AI-governance regimes:** `42-cfr-part-2`, `hti-1`, `uscdi-v4`, `irs-1075`, `nist-800-172-r3`, `tlp-2.0`, `soci-au`, `nis2`, `cra`, `ffiec-cat-2`, `cri-profile-v2.0`, `m-22-09`, `m-22-18`, `nist-800-53-r5-privacy`, `nist-ai-600-1-genai`, `nist-csf-2.0`, `sb-53`, `nyc-ll144-2024`. Each posture cascade pins the regime's normative floor (backupEncryptionRequired / auditChainSignedRequired / tlsMinVersion / requireVacuumAfterErase). **References:** [RFC 5652 CMS](https://www.rfc-editor.org/rfc/rfc5652.html) · [RFC 8551 S/MIME 4.0](https://www.rfc-editor.org/rfc/rfc8551.html) · [RFC 9580 OpenPGP](https://www.rfc-editor.org/rfc/rfc9580.html) · [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/) · [SAML Bindings §3.4 HTTP-Redirect + §3.5 HTTP-POST + §3.2 SOAP](https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf) · [SAML 2.0 §2.5 EncryptedAssertion](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf) · [W3C XML-Encryption 1.1](https://www.w3.org/TR/xmlenc-core1/) · [RFC 7592 Dynamic Client Management](https://www.rfc-editor.org/rfc/rfc7592.html) · [OpenID Connect Native SSO 1.0](https://openid.net/specs/openid-connect-native-sso-1_0.html) · [W3C CSP Level 3](https://www.w3.org/TR/CSP3/) · [W3C Trusted Types](https://www.w3.org/TR/trusted-types/).
|
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ var b = require("@blamejs/core");
|
|
|
52
52
|
})();
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
**Requirements:** Node.js 24.14+ (current active LTS, fixes CVE-2026-21713 non-constant-time HMAC compare).
|
|
55
|
+
**Requirements:** Node.js 24.14+ (current active LTS, fixes CVE-2026-21713 non-constant-time HMAC compare). Node 26 satisfies the floor and the framework test suite runs cleanly on it today; the floor itself will bump to `>=26.x` when Node 26 promotes to Active LTS. Two Node 26 platform changes operators integrating with blamejs should know about: the new `localStorage` global (the framework's storage backend was renamed from `b.backup.localStorage` to `b.backup.diskStorage` in v0.11.2 to avoid the ambiguity; the legacy name still works with a deprecation warning), and the seed-only ML-KEM / ML-DSA PKCS8 export shape (sealed material from Node 24 re-imports cleanly on Node 26; new material from Node 26 in the seed-only shape). See [SECURITY.md](SECURITY.md#node-26-compatibility) for the details.
|
|
56
56
|
|
|
57
57
|
## What ships in the box
|
|
58
58
|
|
package/lib/backup/index.js
CHANGED
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* The namespace wires `b.backupBundle.create` (encrypt + emit a bundle
|
|
14
14
|
* directory) to a pluggable storage backend, plus retention policy +
|
|
15
15
|
* audit emission. Ships with a local-filesystem backend
|
|
16
|
-
* (`b.backup.
|
|
17
|
-
* the same interface.
|
|
16
|
+
* (`b.backup.diskStorage`); S3 or any custom backend drops in through
|
|
17
|
+
* the same interface. The legacy alias `b.backup.localStorage` still
|
|
18
|
+
* works and routes through `b.deprecate.alias` (warns once per
|
|
19
|
+
* process; removal scheduled for the next major). The rename avoids
|
|
20
|
+
* the Node 26 `localStorage` global naming collision.
|
|
18
21
|
*
|
|
19
22
|
* Storage backend contract:
|
|
20
23
|
*
|
|
@@ -58,6 +61,7 @@ var backupManifest = require("./manifest");
|
|
|
58
61
|
var lazyRequire = require("../lazy-require");
|
|
59
62
|
var validateOpts = require("../validate-opts");
|
|
60
63
|
var numericBounds = require("../numeric-bounds");
|
|
64
|
+
var deprecate = require("../deprecate");
|
|
61
65
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
62
66
|
var compliance = lazyRequire(function () { return require("../compliance"); });
|
|
63
67
|
// lazyRequire ../db so backup stays a leaf module operators can use
|
|
@@ -110,9 +114,9 @@ function _dirSize(p) {
|
|
|
110
114
|
// ---- Local filesystem storage backend (the default) ----
|
|
111
115
|
|
|
112
116
|
/**
|
|
113
|
-
* @primitive b.backup.
|
|
114
|
-
* @signature b.backup.
|
|
115
|
-
* @since 0.
|
|
117
|
+
* @primitive b.backup.diskStorage
|
|
118
|
+
* @signature b.backup.diskStorage(opts)
|
|
119
|
+
* @since 0.11.2
|
|
116
120
|
* @status stable
|
|
117
121
|
* @related b.backup.create
|
|
118
122
|
*
|
|
@@ -126,6 +130,12 @@ function _dirSize(p) {
|
|
|
126
130
|
* custom backend matching the same shape; the engine never touches the
|
|
127
131
|
* filesystem directly.
|
|
128
132
|
*
|
|
133
|
+
* Renamed from `b.backup.localStorage` to avoid the Node 26 global
|
|
134
|
+
* `localStorage` naming collision (Node 26 adds `localStorage` as a
|
|
135
|
+
* platform-wide global). The legacy `b.backup.localStorage` alias
|
|
136
|
+
* continues to work and emits a one-time deprecation warning per
|
|
137
|
+
* `b.deprecate.alias`; removal is scheduled for the next major.
|
|
138
|
+
*
|
|
129
139
|
* @opts
|
|
130
140
|
* root: string, // required; directory under which bundle dirs land
|
|
131
141
|
*
|
|
@@ -135,14 +145,14 @@ function _dirSize(p) {
|
|
|
135
145
|
* var os = require("node:os");
|
|
136
146
|
* var root = fs.mkdtempSync(path.join(os.tmpdir(), "backup-root-"));
|
|
137
147
|
*
|
|
138
|
-
* var storage = b.backup.
|
|
148
|
+
* var storage = b.backup.diskStorage({ root: root });
|
|
139
149
|
* storage.name; // → "local"
|
|
140
150
|
* typeof storage.writeBundle; // → "function"
|
|
141
151
|
* typeof storage.listBundles; // → "function"
|
|
142
152
|
*/
|
|
143
|
-
function
|
|
153
|
+
function diskStorage(opts) {
|
|
144
154
|
opts = opts || {};
|
|
145
|
-
validateOpts.requireNonEmptyString(opts.root, "
|
|
155
|
+
validateOpts.requireNonEmptyString(opts.root, "diskStorage: opts.root", BackupError, "backup/no-storage-root");
|
|
146
156
|
var root = opts.root;
|
|
147
157
|
|
|
148
158
|
function _bundlePath(bundleId) {
|
|
@@ -215,7 +225,7 @@ function localStorage(opts) {
|
|
|
215
225
|
function _validateStorage(storage) {
|
|
216
226
|
if (!storage || typeof storage !== "object") {
|
|
217
227
|
throw new BackupError("backup/bad-storage",
|
|
218
|
-
"storage backend is required (use b.backup.
|
|
228
|
+
"storage backend is required (use b.backup.diskStorage or pass a custom one)");
|
|
219
229
|
}
|
|
220
230
|
var required = ["writeBundle", "readBundle", "listBundles", "deleteBundle", "hasBundle"];
|
|
221
231
|
for (var i = 0; i < required.length; i++) {
|
|
@@ -247,7 +257,7 @@ async function _resolveVaultKeyJson(vaultKeyJsonOpt) {
|
|
|
247
257
|
* @since 0.4.0
|
|
248
258
|
* @status stable
|
|
249
259
|
* @compliance hipaa, pci-dss, gdpr, soc2, dora
|
|
250
|
-
* @related b.backup.
|
|
260
|
+
* @related b.backup.diskStorage, b.backup.recommendedFiles, b.backup.verifyManifestSignature, b.backupBundle.create
|
|
251
261
|
*
|
|
252
262
|
* Build a backup engine bound to a data directory, a storage backend,
|
|
253
263
|
* the operator's passphrase, and an include list. Returns an object
|
|
@@ -266,7 +276,7 @@ async function _resolveVaultKeyJson(vaultKeyJsonOpt) {
|
|
|
266
276
|
*
|
|
267
277
|
* @opts
|
|
268
278
|
* dataDir: string, // required; must exist on disk
|
|
269
|
-
* storage: StorageBackend, // required;
|
|
279
|
+
* storage: StorageBackend, // required; diskStorage() or custom
|
|
270
280
|
* passphrase: Buffer | string, // required; KEK for per-file Argon2id wrap
|
|
271
281
|
* files: Array<{ relativePath, kind, required }>,
|
|
272
282
|
* vaultKeyJson: string | () => string | Promise<string>,
|
|
@@ -292,7 +302,7 @@ async function _resolveVaultKeyJson(vaultKeyJsonOpt) {
|
|
|
292
302
|
*
|
|
293
303
|
* var engine = b.backup.create({
|
|
294
304
|
* dataDir: dataDir,
|
|
295
|
-
* storage: b.backup.
|
|
305
|
+
* storage: b.backup.diskStorage({ root: root }),
|
|
296
306
|
* passphrase: Buffer.from("operator backup passphrase"),
|
|
297
307
|
* files: [
|
|
298
308
|
* { relativePath: "db.enc", kind: "raw", required: true },
|
|
@@ -991,7 +1001,7 @@ function runInWorker(opts) {
|
|
|
991
1001
|
|
|
992
1002
|
module.exports = {
|
|
993
1003
|
create: create,
|
|
994
|
-
|
|
1004
|
+
diskStorage: diskStorage,
|
|
995
1005
|
recommendedFiles: recommendedFiles,
|
|
996
1006
|
runInWorker: runInWorker,
|
|
997
1007
|
verifyManifestSignature: verifyManifestSignature,
|
|
@@ -999,3 +1009,17 @@ module.exports = {
|
|
|
999
1009
|
BackupError: BackupError,
|
|
1000
1010
|
BUNDLE_ID_RE: BUNDLE_ID_RE,
|
|
1001
1011
|
};
|
|
1012
|
+
|
|
1013
|
+
// Legacy alias — `b.backup.localStorage(...)`. The Node 26 `localStorage`
|
|
1014
|
+
// global doesn't clash with this property-access shape inside the
|
|
1015
|
+
// framework, but the rename keeps operator-facing surface unambiguous
|
|
1016
|
+
// and avoids any future drift on the bare identifier. Removed in the
|
|
1017
|
+
// next major (engines.node bump to >=26 — Node 24 LTS sunset window).
|
|
1018
|
+
deprecate.alias(module.exports, "localStorage", "diskStorage", {
|
|
1019
|
+
since: "0.11.2",
|
|
1020
|
+
removeIn: "0.12.0",
|
|
1021
|
+
message: "b.backup.localStorage was renamed to b.backup.diskStorage — " +
|
|
1022
|
+
"the Node 26 `localStorage` global doesn't clash today, but the " +
|
|
1023
|
+
"rename keeps the operator-facing surface unambiguous. " +
|
|
1024
|
+
"Update the call site; removal lands in the next major.",
|
|
1025
|
+
});
|
package/lib/cli.js
CHANGED
|
@@ -742,7 +742,7 @@ async function _runAudit(args, ctx) {
|
|
|
742
742
|
// inspect a specific one, do a live in-place restore (with rollback
|
|
743
743
|
// preservation), and roll back to a previous restore point. Wraps the
|
|
744
744
|
// restore primitive's run / inspect / list / rollback / list-rollbacks
|
|
745
|
-
// surface; uses b.backup.
|
|
745
|
+
// surface; uses b.backup.diskStorage as the storage adapter (the same
|
|
746
746
|
// adapter that wrote the bundles).
|
|
747
747
|
//
|
|
748
748
|
// Two ways to identify a bundle for inspect / apply:
|
|
@@ -846,7 +846,7 @@ async function _runRestore(args, ctx) {
|
|
|
846
846
|
var sel = _resolveRestoreBundleSelector(args, ctx, report, false);
|
|
847
847
|
if (!sel) return 2;
|
|
848
848
|
try {
|
|
849
|
-
var storage = backup.
|
|
849
|
+
var storage = backup.diskStorage({ root: sel.storageRoot });
|
|
850
850
|
var bundles = await storage.listBundles();
|
|
851
851
|
if (bundles.length === 0) {
|
|
852
852
|
report.write("no bundles in " + sel.storageRoot);
|
|
@@ -869,7 +869,7 @@ async function _runRestore(args, ctx) {
|
|
|
869
869
|
var selI = _resolveRestoreBundleSelector(args, ctx, report, true);
|
|
870
870
|
if (!selI) return 2;
|
|
871
871
|
try {
|
|
872
|
-
var storageI = backup.
|
|
872
|
+
var storageI = backup.diskStorage({ root: selI.storageRoot });
|
|
873
873
|
// restore.create needs a passphrase + dataDir even for inspect because
|
|
874
874
|
// its closure captures them; pass placeholders since inspect doesn't
|
|
875
875
|
// touch them.
|
|
@@ -918,7 +918,7 @@ async function _runRestore(args, ctx) {
|
|
|
918
918
|
return report.error("--max-pulled-files must be a positive number", 2);
|
|
919
919
|
}
|
|
920
920
|
try {
|
|
921
|
-
var storageA = backup.
|
|
921
|
+
var storageA = backup.diskStorage({ root: selA.storageRoot });
|
|
922
922
|
var rA = restore.create({
|
|
923
923
|
dataDir: dd,
|
|
924
924
|
storage: storageA,
|
package/lib/restore.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* var restore = b.restore.create({
|
|
13
13
|
* dataDir: "./data",
|
|
14
|
-
* storage: b.backup.
|
|
14
|
+
* storage: b.backup.diskStorage({ root: "./backups" }),
|
|
15
15
|
* passphrase: Buffer.from("operator backup passphrase"),
|
|
16
16
|
* rollbackRoot: "./data.rollbacks", // optional; default <dataDir>.rollbacks
|
|
17
17
|
* audit: true,
|
|
@@ -76,7 +76,7 @@ class RestoreError extends FrameworkError {
|
|
|
76
76
|
function _validateStorage(storage) {
|
|
77
77
|
if (!storage || typeof storage !== "object") {
|
|
78
78
|
throw new RestoreError("restore/bad-storage",
|
|
79
|
-
"storage backend is required (use b.backup.
|
|
79
|
+
"storage backend is required (use b.backup.diskStorage or pass a custom one)");
|
|
80
80
|
}
|
|
81
81
|
var required = ["readBundle", "listBundles", "hasBundle"];
|
|
82
82
|
for (var i = 0; i < required.length; i++) {
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.6",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:6e5e359f-2406-47e3-ac83-d8f1cf7b7bcb",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-19T15:
|
|
8
|
+
"timestamp": "2026-05-19T15:27:24.507Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.11.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.11.2",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.11.
|
|
25
|
+
"version": "0.11.2",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.11.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.11.2",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.11.
|
|
57
|
+
"ref": "@blamejs/core@0.11.2",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|