squidcloudctl 3.0.5 → 3.0.6

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.
@@ -310,20 +310,37 @@ export async function downloadFile(target, destPath, onProgress) {
310
310
  const { detectChunkVersion, decryptAny, decryptV3, deriveRawKey } = await import('./crypto/res54.js');
311
311
  const master = deriveRawKey(defaultKey || '');
312
312
  const fileKeyRaw = noble.hkdf(sha2.sha256, master, new Uint8Array(0), enc.encode('v3-file-master' + target.id), 32);
313
+ let activeMaster = master;
313
314
  for (let i = 0; i < chunks.length; i++) {
314
315
  const blob = await clusterDownloadChunk(chunks[i], target.userId || '');
315
316
  const version = detectChunkVersion(blob);
316
317
  let plain;
317
- if (version === 3) {
318
- plain = decryptV3(blob, master, target.userId || '', target.id, i, chunks.length);
319
- }
320
- else if (version === 1 && !encrypted) {
318
+ if (version === 1 && !encrypted && !defaultKey) {
321
319
  plain = blob;
322
320
  }
323
321
  else {
324
- plain = decryptAny(blob, defaultKey || '', {
325
- userId: target.userId, fileId: target.id, chunkIndex: i, totalChunks: chunks.length,
326
- }).plain;
322
+ if (!defaultKey && byokCache === undefined) {
323
+ byokCache = (await askByokKey(target.name)) ?? '';
324
+ }
325
+ const attemptKey = defaultKey || byokCache || '';
326
+ try {
327
+ if (version === 3) {
328
+ const m2 = attemptKey ? deriveRawKey(attemptKey) : master;
329
+ plain = decryptV3(blob, m2, target.userId || '', target.id, i, chunks.length);
330
+ activeMaster = m2;
331
+ }
332
+ else {
333
+ plain = decryptAny(blob, attemptKey, {
334
+ userId: target.userId, fileId: target.id, chunkIndex: i, totalChunks: chunks.length,
335
+ }).plain;
336
+ }
337
+ }
338
+ catch (err) {
339
+ if (!attemptKey)
340
+ throw new Error('BYOK_KEY_REQUIRED: pass --key or run interactively');
341
+ throw err;
342
+ }
343
+ void activeMaster;
327
344
  }
328
345
  void fileKeyRaw;
329
346
  progress.update(i + 1, `chunk ${i + 1}/${chunks.length}`);
@@ -338,11 +355,26 @@ export async function downloadFile(target, destPath, onProgress) {
338
355
  return finalPath2;
339
356
  }
340
357
  catch (e) {
341
- errors.push(`cluster: ${e instanceof Error ? e.message : String(e)}`);
358
+ const msg = e instanceof Error ? e.message : String(e);
359
+ if (msg.startsWith('VAULT_KEY_DRIFT'))
360
+ throw e;
361
+ errors.push(`cluster: ${msg}`);
342
362
  }
343
363
  }
344
364
  throw new Error(errors.join(' · ') || 'No downloadable strategy succeeded');
345
365
  }
366
+ let byokCache;
367
+ async function askByokKey(fileName) {
368
+ if (byokCache !== undefined)
369
+ return byokCache;
370
+ if (!process.stdin.isTTY || process.env.SQUIDCLOUD_NONINTERACTIVE === '1')
371
+ return null;
372
+ const readline = await import('node:readline');
373
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
374
+ const answer = await new Promise((res) => rl.question(`\u25cf decryption key for ${fileName}: `, (v) => { rl.close(); res(v); }));
375
+ byokCache = answer.trim() || null;
376
+ return byokCache;
377
+ }
346
378
  async function resolveKey(fileId, tagsKey) {
347
379
  if (tagsKey && !/^(managed_key|sha256:\S+|byok_encrypted|byok_protected)$/.test(tagsKey))
348
380
  return tagsKey;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squidcloudctl",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "SquidCloud CLI (squidcloudctl) — encrypted storage, shares and secrets from your terminal",
5
5
  "main": "dist/bin/squidcloud.js",
6
6
  "bin": {