From 192071e4a7642e83284213c499e20373a53d1dc6 Mon Sep 17 00:00:00 2001 From: shuaiplus <2327005759@qq.com> Date: Sat, 30 May 2026 02:43:09 +0800 Subject: [PATCH] fix: enhance cipher key handling and compatibility for secure notes --- src/handlers/ciphers.ts | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/handlers/ciphers.ts b/src/handlers/ciphers.ts index 2b66270..ad3a0bb 100644 --- a/src/handlers/ciphers.ts +++ b/src/handlers/ciphers.ts @@ -130,6 +130,16 @@ function optionalEncString(value: unknown): string | null { return isValidEncString(value) ? value.trim() : null; } +function shouldAcceptCipherKey(value: unknown): boolean { + if (LIMITS.compatibility.cipherKeyEncryptionFeatureEnabled) return true; + return optionalEncString(value) === null; +} + +function normalizeCipherKeyForStorage(value: unknown): string | null { + if (!LIMITS.compatibility.cipherKeyEncryptionFeatureEnabled) return null; + return optionalEncString(value); +} + function sanitizeEncryptedObject>( source: T | null | undefined, encryptedKeys: readonly string[] @@ -293,6 +303,14 @@ export function normalizeCipherSshKeyForCompatibility(sshKey: any): any { }; } +function normalizeCipherSecureNoteForCompatibility(secureNote: any): CipherSecureNote | null { + if (!secureNote || typeof secureNote !== 'object') return null; + const type = Number(secureNote?.type ?? secureNote?.Type ?? 0); + return { + type: Number.isFinite(type) ? type : 0, + }; +} + // Format attachments for API response export function formatAttachments(attachments: Attachment[]): any[] | null { if (attachments.length === 0) return null; @@ -570,6 +588,9 @@ export function cipherToResponse( 'licenseNumber', ]); const normalizedSshKey = normalizeCipherSshKeyForCompatibility((passthrough as any).sshKey ?? null); + const normalizedSecureNote = Number(cipher.type) === 2 + ? normalizeCipherSecureNoteForCompatibility((passthrough as any).secureNote ?? null) ?? { type: 0 } + : null; const responseAttachments = applyCipherEmbeddedAttachmentMetadata(cipher, attachments); return { @@ -598,6 +619,7 @@ export function cipherToResponse( login: normalizedLogin, card: normalizedCard, identity: normalizedIdentity, + secureNote: normalizedSecureNote, fields: normalizeCipherFieldsForCompatibility((passthrough as any).fields), passwordHistory: normalizePasswordHistoryForCompatibility((passthrough as any).passwordHistory), sshKey: normalizedSshKey, @@ -694,6 +716,10 @@ export async function handleCreateCipher(request: Request, env: Env, userId: str const createSshKey = readCipherProp(cipherData, ['sshKey', 'SshKey']); const createPasswordHistory = readCipherProp(cipherData, ['passwordHistory', 'PasswordHistory']); + if (createKey.present && !shouldAcceptCipherKey(createKey.value)) { + return errorResponse('Cipher key encryption is not supported by this server. Resync the client and try again.', 400); + } + const now = new Date().toISOString(); // Opaque passthrough: spread ALL client fields to preserve unknown/future ones, // then override only server-controlled fields. @@ -711,7 +737,7 @@ export async function handleCreateCipher(request: Request, env: Env, userId: str deletedAt: null, }; cipher.folderId = createFolderId.present ? normalizeOptionalId(createFolderId.value) : normalizeOptionalId(cipher.folderId); - cipher.key = createKey.present ? (createKey.value ?? null) : (cipher.key ?? null); + cipher.key = normalizeCipherKeyForStorage(createKey.present ? createKey.value : cipher.key); cipher.login = createLogin.present ? (createLogin.value ?? null) : (cipher.login ?? null); cipher.card = createCard.present ? (createCard.value ?? null) : (cipher.card ?? null); cipher.identity = createIdentity.present ? (createIdentity.value ?? null) : (cipher.identity ?? null); @@ -772,6 +798,10 @@ export async function handleUpdateCipher(request: Request, env: Env, userId: str const incomingRevisionDate = readCipherRevisionDate(cipherData); const hasAttachmentMigrationMetadata = hasIncomingAttachmentMetadata(cipherData); + if (incomingKey.present && !shouldAcceptCipherKey(incomingKey.value)) { + return errorResponse('Cipher key encryption is not supported by this server. Resync the client and try again.', 400); + } + if (!hasAttachmentMigrationMetadata && isStaleCipherUpdate(existingCipher.updatedAt, incomingRevisionDate)) { return errorResponse('The client copy of this cipher is out of date. Resync the client and try again.', 400); } @@ -797,9 +827,7 @@ export async function handleUpdateCipher(request: Request, env: Env, userId: str if (incomingFolderId.present) { cipher.folderId = normalizeOptionalId(incomingFolderId.value); } - if (incomingKey.present) { - cipher.key = incomingKey.value ?? null; - } + cipher.key = normalizeCipherKeyForStorage(incomingKey.present ? incomingKey.value : existingCipher.key); cipher.login = nextType === 1 ? (incomingLogin.present ? (incomingLogin.value ?? null) : (existingCipher.login ?? null)) : null; cipher.secureNote = nextType === 2 ? (incomingSecureNote.present ? (incomingSecureNote.value ?? null) : (existingCipher.secureNote ?? null)) : null; cipher.card = nextType === 3 ? (incomingCard.present ? (incomingCard.value ?? null) : (existingCipher.card ?? null)) : null;