@bivy/bivy 0.18.5-staging.1 → 0.18.5-staging.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.
@@ -0,0 +1,22 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ /** Validate keys against the vault before caching them or sharing them with peers.
4
+ * A stale local key must not hide a fresh peer wrap; an unusable wrap must never
5
+ * be persisted as though key recovery succeeded. No credential data is changed.
6
+ */
7
+ export function recoverModelAuthKey(options) {
8
+ if (options.localKey) {
9
+ try {
10
+ return { key: options.localKey, value: options.decrypt(options.localKey) };
11
+ }
12
+ catch { /* Try the peer's replacement before requesting another wrap. */ }
13
+ }
14
+ if (options.unwrap) {
15
+ try {
16
+ const key = options.unwrap();
17
+ return { key, value: options.decrypt(key) };
18
+ }
19
+ catch { /* The caller must reject this exact wrap and request a new one. */ }
20
+ }
21
+ return undefined;
22
+ }
package/dist/server.js CHANGED
@@ -149,6 +149,7 @@ import { redactSecrets } from "./redact.js";
149
149
  import { getSttConfig, isSttProvider, removeSttKey, setSttKey, setSttProvider, transcribeAudio, MAX_AUDIO_BYTES, } from "./stt.js";
150
150
  import { synthesizeOpenAiSpeech } from "./tts.js";
151
151
  import { seal, open } from "./e2e.js";
152
+ import { recoverModelAuthKey } from "./model-auth-key-recovery.js";
152
153
  import { RunDelegationService, parseDelegationSource } from "./run-tools.js";
153
154
  import { ControlPlaneTaskPoller, resolveControlPlaneTaskConfig, } from "./control-plane-tasks.js";
154
155
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -3365,20 +3366,31 @@ async function syncModelAuthFromControlPlane() {
3365
3366
  }
3366
3367
  const targetVault = hostedCustody ? hostedData.hostedVault : data.vault;
3367
3368
  let vaultKeyB64 = hostedCustody ? readHostedModelAuthVaultKey() : readLocalModelAuthVaultKey();
3368
- if (!hostedCustody && !vaultKeyB64 && data.wrappedKey?.wrappedKey) {
3369
- vaultKeyB64 = pairingStore.unwrapFromNodePublicKey(data.wrappedKey.wrappedByPublicKey, data.wrappedKey.wrappedKey);
3370
- writeLocalModelAuthVaultKey(vaultKeyB64);
3371
- }
3372
3369
  // Hosted runners may decrypt ONLY the separately encrypted snapshot of
3373
3370
  // records that the user explicitly granted to unattended execution.
3374
3371
  if (hostedCustody && !vaultKeyB64 && hostedData.hostedKey && hostedData.hostedVault && Buffer.from(hostedData.hostedKey, "base64").length === 32) {
3375
3372
  vaultKeyB64 = hostedData.hostedKey;
3376
3373
  writeHostedModelAuthVaultKey(vaultKeyB64);
3377
3374
  }
3378
- if (targetVault?.ciphertext && vaultKeyB64) {
3375
+ if (targetVault?.ciphertext && (vaultKeyB64 || (!hostedCustody && data.wrappedKey))) {
3379
3376
  let decrypted;
3380
3377
  try {
3381
- decrypted = decryptModelAuthEnvelope(targetVault.ciphertext, vaultKeyB64);
3378
+ if (hostedCustody) {
3379
+ decrypted = decryptModelAuthEnvelope(targetVault.ciphertext, vaultKeyB64);
3380
+ }
3381
+ else {
3382
+ const wrapped = data.wrappedKey;
3383
+ const recovered = recoverModelAuthKey({
3384
+ localKey: vaultKeyB64,
3385
+ unwrap: wrapped ? () => pairingStore.unwrapFromNodePublicKey(wrapped.wrappedByPublicKey, wrapped.wrappedKey) : undefined,
3386
+ decrypt: (key) => decryptModelAuthEnvelope(targetVault.ciphertext, key),
3387
+ });
3388
+ if (!recovered)
3389
+ throw new Error("No available key can decrypt the account vault");
3390
+ vaultKeyB64 = recovered.key;
3391
+ decrypted = recovered.value;
3392
+ writeLocalModelAuthVaultKey(vaultKeyB64);
3393
+ }
3382
3394
  }
3383
3395
  catch (error) {
3384
3396
  // Most commonly this node cached the previous generation while another
@@ -3394,7 +3406,10 @@ async function syncModelAuthFromControlPlane() {
3394
3406
  else {
3395
3407
  forgetLocalModelAuthVaultKey();
3396
3408
  lastPushedModelAuthCiphertext = "";
3397
- await modelAuthFetch("/node/model-auth-key/request", { method: "POST", body: JSON.stringify({ publicKey: pairingStore.nodePublicKeyB64() }) });
3409
+ await modelAuthFetch("/node/model-auth-key/request", {
3410
+ method: "POST",
3411
+ body: JSON.stringify({ publicKey: pairingStore.nodePublicKeyB64(), rejectedWrappedKey: data.wrappedKey?.wrappedKey }),
3412
+ });
3398
3413
  ensureModelAuthColdStart();
3399
3414
  }
3400
3415
  console.warn("[auth-sync] cached vault key is stale; requested the rotated key:", error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.18.5-staging.1",
3
+ "version": "0.18.5-staging.2",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",