fix(broker/cli): e2e get_file owner sealedKey bug
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

broker: owner also fetches sealedKey from mesh.file_key (not skipped),
  only non-owners are blocked when key is missing
cli: explicit error when encrypted file has no sealedKey (no silent raw download)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-07 12:56:36 +01:00
parent 5f696f47ea
commit e70f0ed1ff
2 changed files with 10 additions and 10 deletions

View File

@@ -977,20 +977,19 @@ function handleConnection(ws: WebSocket): void {
break; break;
} }
} }
// E2E: for encrypted files, fetch the sealed key for this peer // E2E: for encrypted files, fetch the sealed key for this peer.
// Owners are not blocked if their key is missing (edge case), but
// they still get it returned so the CLI can decrypt normally.
let sealedKey: string | null = null; let sealedKey: string | null = null;
if (file.encrypted) { if (file.encrypted) {
const peerPubkey = conn.sessionPubkey ?? conn.memberPubkey; const peerPubkey = conn.sessionPubkey ?? conn.memberPubkey;
const isOwner = file.ownerPubkey && peerPubkey === file.ownerPubkey; const isOwner = !!(file.ownerPubkey && peerPubkey === file.ownerPubkey);
if (!isOwner) {
sealedKey = peerPubkey ? await getFileKey(gf.fileId, peerPubkey) : null; sealedKey = peerPubkey ? await getFileKey(gf.fileId, peerPubkey) : null;
if (!sealedKey) { if (!sealedKey && !isOwner) {
sendError(conn.ws, "forbidden", "no decryption key for this file"); sendError(conn.ws, "forbidden", "no decryption key for this file");
break; break;
} }
} }
// Owner gets sealedKey = null (they already have Kf from upload)
}
// Generate presigned URL (60s expiry) // Generate presigned URL (60s expiry)
const bucket = meshBucketName(conn.meshId); const bucket = meshBucketName(conn.meshId);
const presignedUrl = await minioClient.presignedGetObject( const presignedUrl = await minioClient.presignedGetObject(

View File

@@ -527,7 +527,8 @@ Your message mode is "${messageMode}".
const result = await client.getFile(id); const result = await client.getFile(id);
if (!result) return text(`get_file: file ${id} not found`, true); if (!result) return text(`get_file: file ${id} not found`, true);
if (result.encrypted && result.sealedKey) { if (result.encrypted) {
if (!result.sealedKey) return text("get_file: encrypted file — no decryption key available for your session", true);
const { openSealedKey, decryptFile } = await import("../crypto/file-crypto"); const { openSealedKey, decryptFile } = await import("../crypto/file-crypto");
const { ensureSodium } = await import("../crypto/keypair"); const { ensureSodium } = await import("../crypto/keypair");
const myPubkey = client.getSessionPubkey(); const myPubkey = client.getSessionPubkey();