@blamejs/core 0.14.11 → 0.14.12

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/lib/vault-aad.js CHANGED
@@ -147,13 +147,17 @@ function buildContextAad(parts) {
147
147
  // 32 bytes). Constant-domain prefix prevents key collision with other
148
148
  // uses of the vault root.
149
149
 
150
- function _deriveKey(aadBytes) {
151
- var keysJson = vault().getKeysJson();
152
- // The vault keys JSON includes the active keypair PEMs. We hash the
153
- // whole serialized form to get a stable per-vault root secret
154
- // this is a deterministic derivation; rotating vault keys produces
155
- // a different root and breaks all prior AAD-sealed values (operator
156
- // intent: rotation = re-seal).
150
+ function _deriveKey(aadBytes, rootKeysJson) {
151
+ // rootKeysJson lets the vault-key rotation pipeline derive the per-row
152
+ // key under a SPECIFIC vault root (old or new keypair) within one
153
+ // process; when omitted it uses the live singleton. The keys JSON
154
+ // includes the active keypair PEMs hashing the whole serialized form
155
+ // gives a stable per-vault root secret. Rotating vault keys produces a
156
+ // different root, so prior AAD-sealed values must be re-sealed (the
157
+ // rotation pipeline walks them via sealRoot/unsealRoot/resealRoot).
158
+ var keysJson = (typeof rootKeysJson === "string" && rootKeysJson.length > 0)
159
+ ? rootKeysJson
160
+ : vault().getKeysJson();
157
161
  var rootHash = bCrypto().sha3Hash(keysJson);
158
162
  var prefix = Buffer.from("vault.aad/v1/", "utf8");
159
163
  var rootBuf = Buffer.from(rootHash, "hex");
@@ -161,7 +165,7 @@ function _deriveKey(aadBytes) {
161
165
  return bCrypto().kdf(input, C.BYTES.bytes(32));
162
166
  }
163
167
 
164
- function seal(plaintext, aadParts) {
168
+ function _seal(plaintext, aadParts, rootKeysJson, suppressAudit) {
165
169
  if (plaintext == null) {
166
170
  throw new VaultAadError("vault-aad/bad-input",
167
171
  "seal: plaintext is required (use null/undefined-stripping at the call site)");
@@ -176,26 +180,32 @@ function seal(plaintext, aadParts) {
176
180
  "seal: value is already AAD-sealed (refuses to double-seal)");
177
181
  }
178
182
  var aadBytes = _canonicalize(aadParts);
179
- var key = _deriveKey(aadBytes);
183
+ var key = _deriveKey(aadBytes, rootKeysJson);
180
184
  var ptBuf = Buffer.from(plaintext, "utf8");
181
185
  var packed = bCrypto().encryptPacked(ptBuf, key, aadBytes);
182
186
 
183
- try {
184
- audit().safeEmit({
185
- action: "vault.aad.sealed",
186
- outcome: "success",
187
- actor: null,
188
- metadata: {
189
- aadKeys: Object.keys(aadParts).sort(), // allow:bare-canonicalize-walk — audit-emit metadata, not for signing
190
- bytes: ptBuf.length,
191
- },
192
- });
193
- } catch (_e) { /* drop-silent */ }
187
+ if (!suppressAudit) {
188
+ try {
189
+ audit().safeEmit({
190
+ action: "vault.aad.sealed",
191
+ outcome: "success",
192
+ actor: null,
193
+ metadata: {
194
+ aadKeys: Object.keys(aadParts).sort(), // allow:bare-canonicalize-walk — audit-emit metadata, not for signing
195
+ bytes: ptBuf.length,
196
+ },
197
+ });
198
+ } catch (_e) { /* drop-silent */ }
199
+ }
194
200
 
195
201
  return AAD_PREFIX + packed.toString("base64");
196
202
  }
197
203
 
198
- function unseal(value, aadParts) {
204
+ function seal(plaintext, aadParts) {
205
+ return _seal(plaintext, aadParts, undefined, false);
206
+ }
207
+
208
+ function _unseal(value, aadParts, rootKeysJson, suppressAudit) {
199
209
  if (value == null || typeof value !== "string") {
200
210
  throw new VaultAadError("vault-aad/bad-input",
201
211
  "unseal: value must be a non-empty string");
@@ -205,7 +215,7 @@ function unseal(value, aadParts) {
205
215
  "unseal: value is not AAD-sealed (missing " + JSON.stringify(AAD_PREFIX) + " prefix)");
206
216
  }
207
217
  var aadBytes = _canonicalize(aadParts);
208
- var key = _deriveKey(aadBytes);
218
+ var key = _deriveKey(aadBytes, rootKeysJson);
209
219
  var packed;
210
220
  try { packed = Buffer.from(value.slice(AAD_PREFIX.length), "base64"); }
211
221
  catch (e) {
@@ -215,17 +225,19 @@ function unseal(value, aadParts) {
215
225
  var pt;
216
226
  try { pt = bCrypto().decryptPacked(packed, key, aadBytes); }
217
227
  catch (e) {
218
- try {
219
- audit().safeEmit({
220
- action: "vault.aad.unseal_failed",
221
- outcome: "denied",
222
- actor: null,
223
- metadata: {
224
- aadKeys: Object.keys(aadParts).sort(), // allow:bare-canonicalize-walk — audit-emit metadata, not for signing
225
- reason: e.message,
226
- },
227
- });
228
- } catch (_e) { /* drop-silent */ }
228
+ if (!suppressAudit) {
229
+ try {
230
+ audit().safeEmit({
231
+ action: "vault.aad.unseal_failed",
232
+ outcome: "denied",
233
+ actor: null,
234
+ metadata: {
235
+ aadKeys: Object.keys(aadParts).sort(), // allow:bare-canonicalize-walk — audit-emit metadata, not for signing
236
+ reason: e.message,
237
+ },
238
+ });
239
+ } catch (_e) { /* drop-silent */ }
240
+ }
229
241
  throw new VaultAadError("vault-aad/aead-mismatch",
230
242
  "unseal: AEAD authentication failed — value may have been tampered, " +
231
243
  "copied from a different row, or sealed under different AAD");
@@ -233,6 +245,10 @@ function unseal(value, aadParts) {
233
245
  return pt.toString("utf8");
234
246
  }
235
247
 
248
+ function unseal(value, aadParts) {
249
+ return _unseal(value, aadParts, undefined, false);
250
+ }
251
+
236
252
  function isAadSealed(value) {
237
253
  return typeof value === "string" && value.indexOf(AAD_PREFIX) === 0;
238
254
  }
@@ -246,10 +262,45 @@ function reseal(value, fromAad, toAad) {
246
262
  return seal(plaintext, toAad);
247
263
  }
248
264
 
265
+ // ---- explicit-root variants (vault-key rotation pipeline) ----
266
+ //
267
+ // The rotation pipeline must decrypt a cell under the OLD vault root and
268
+ // re-encrypt it under the NEW root within one process — the live-singleton
269
+ // _deriveKey cannot straddle two keypairs. These take the serialized vault
270
+ // keys JSON (b.vault.getKeysJson() output) for a specific root; the AAD
271
+ // tuple is unchanged, only the root differs. Per-cell audit is suppressed
272
+ // (the rotation pipeline has its own progress + verify reporting).
273
+
274
+ function sealRoot(plaintext, aadParts, rootKeysJson) {
275
+ if (typeof rootKeysJson !== "string" || rootKeysJson.length === 0) {
276
+ throw new VaultAadError("vault-aad/bad-root", "sealRoot: rootKeysJson (vault keys JSON) is required");
277
+ }
278
+ return _seal(plaintext, aadParts, rootKeysJson, true);
279
+ }
280
+
281
+ function unsealRoot(value, aadParts, rootKeysJson) {
282
+ if (typeof rootKeysJson !== "string" || rootKeysJson.length === 0) {
283
+ throw new VaultAadError("vault-aad/bad-root", "unsealRoot: rootKeysJson (vault keys JSON) is required");
284
+ }
285
+ return _unseal(value, aadParts, rootKeysJson, true);
286
+ }
287
+
288
+ // Re-seal a value from the old root to the new root under the SAME AAD
289
+ // tuple: authenticate under the old root (throws aead-mismatch if the
290
+ // value was not sealed under oldRootJson + aadParts), then re-encrypt
291
+ // under the new root. The rotation pipeline composes this per cell.
292
+ function resealRoot(value, aadParts, oldRootJson, newRootJson) {
293
+ var plaintext = unsealRoot(value, aadParts, oldRootJson);
294
+ return sealRoot(plaintext, aadParts, newRootJson);
295
+ }
296
+
249
297
  module.exports = {
250
298
  seal: seal,
251
299
  unseal: unseal,
252
300
  reseal: reseal,
301
+ sealRoot: sealRoot,
302
+ unsealRoot: unsealRoot,
303
+ resealRoot: resealRoot,
253
304
  isAadSealed: isAadSealed,
254
305
  buildColumnAad: buildColumnAad,
255
306
  buildContextAad: buildContextAad,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.14.11",
3
+ "version": "0.14.12",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
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.5",
5
- "serialNumber": "urn:uuid:51d93d1b-aac7-4b5a-b94b-b60595c0eba0",
5
+ "serialNumber": "urn:uuid:f81dd931-ce62-498a-ac6f-d9ac5b0be399",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-31T14:46:52.063Z",
8
+ "timestamp": "2026-05-31T18:05:40.945Z",
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.14.11",
22
+ "bom-ref": "@blamejs/core@0.14.12",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.14.11",
25
+ "version": "0.14.12",
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.14.11",
29
+ "purl": "pkg:npm/%40blamejs/core@0.14.12",
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.14.11",
57
+ "ref": "@blamejs/core@0.14.12",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]