{"record":{"id":"0be02c81a71a7a50","repo":"laurent22/joplin","slug":"encrypted-items-cannot-be-modified","errorCode":null,"errorMessage":"Encrypted items cannot be modified","messagePattern":"Encrypted items cannot be modified","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/lib/models/BaseItem.ts","lineNumber":1017,"sourceCode":"\t\t// await this.forceSync(item.id);\n\n\t\treturn true;\n\t}\n\n\tpublic static async forceSync(itemId: string) {\n\t\tawait this.db().exec('UPDATE sync_items SET force_sync = 1 WHERE item_id = ?', [itemId]);\n\t}\n\n\tpublic static async forceSyncAll() {\n\t\tawait this.db().exec('UPDATE sync_items SET force_sync = 1');\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- save() accepts any BaseItemEntity subclass plus disable-readonly options; subclasses override with stricter per-entity types\n\tpublic static async save(o: any, options: SaveOptions = null) {\n\t\tif (!options) options = {};\n\n\t\tif (options.userSideValidation === true) {\n\t\t\tif (o.encryption_applied) throw new Error(_('Encrypted items cannot be modified'));\n\t\t}\n\n\t\tconst isNew = this.isNew(o, options);\n\n\t\tif (needsShareReadOnlyChecks(this.modelType(), options.changeSource, this.syncShareCache, options.disableReadOnlyCheck)) {\n\t\t\tif (!isNew) {\n\t\t\t\tconst previousItem = await this.loadItemByTypeAndId(this.modelType(), o.id, { fields: ['id', 'share_id'] });\n\t\t\t\tcheckIfItemCanBeChanged(this.modelType(), options.changeSource, previousItem, this.syncShareCache);\n\t\t\t}\n\n\t\t\t// If the item has a parent folder (a note or a sub-folder), check\n\t\t\t// that we're not adding the item to a read-only folder.\n\t\t\tif (o.parent_id) {\n\t\t\t\tawait checkIfItemCanBeAddedToFolder(\n\t\t\t\t\tthis.modelType(),\n\t\t\t\t\tthis.getClass('Folder'),\n\t\t\t\t\toptions.changeSource,\n\t\t\t\t\tBaseItem.syncShareCache,","sourceCodeStart":999,"sourceCodeEnd":1035,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/lib/models/BaseItem.ts#L999-L1035","documentation":"Thrown by BaseItem.save() when options.userSideValidation is true and the entity has encryption_applied set. End-to-end encryption makes item fields opaque to the client, so user-initiated edits to an encrypted item are rejected to prevent corrupting ciphertext. The message is localized via _().","triggerScenarios":"Calling save(item, { userSideValidation: true }) on a BaseItem whose encryption_applied is truthy — typically a note/resource that was encrypted during sync and is now being saved from a UI/editor path that sets userSideValidation.","commonSituations":"E2EE is enabled but the master key isn't loaded, so items stay encrypted locally while the user tries to edit them; an item was encrypted on another device and not yet decrypted here; a code path that should decrypt-before-edit skipped decryption; race where encryption was applied after the editor opened.","solutions":["Decrypt the item before editing — ensure the master key is provided and the item is decrypted (encryption_applied = 0) prior to save.","If E2EE isn't fully set up, complete master key setup/config before editing encrypted items.","Re-sync to pull the decrypted plaintext once the key is available.","Audit the calling path to confirm it decrypts before opening the editor."],"exampleFix":"// before\nawait Note.save(note, { userSideValidation: true });\n// after - decrypt first, then save\nif (note.encryption_applied) {\n  await decryptionService.decryptItem(note);\n}\nawait Note.save(note, { userSideValidation: true });","handlingStrategy":"validation","validationCode":"if (item.encryption_applied) {\n  throw new Error('Item is encrypted; decrypt it before saving user-side changes.');\n}\nawait BaseItem.save(item, { userSideValidation: true });","typeGuard":"function isEncryptedItem(o: any): boolean {\n  return !!o && !!o.encryption_applied;\n}","tryCatchPattern":"try {\n  await BaseItem.save(item, { userSideValidation: true });\n} catch (e) {\n  if (/Encrypted items cannot be modified/i.test(e.message)) {\n    await decryptionService.decryptItem(item);\n    await BaseItem.save(item, { userSideValidation: true });\n    return;\n  }\n  throw e;\n}","preventionTips":["Ensure the E2EE master key is loaded before editing.","Decrypt items before opening them in the editor.","Don't call save with userSideValidation on ciphertext; decrypt first.","Re-sync to obtain plaintext after enabling/adding the master key."],"tags":["encryption","e2ee","save","security","validation"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}