GSoC 2026 Proposal Draft - Idea 7: Local Note Encryption - keshav0479

Are you sure it’s actually necessary to have a distinct status for local encryption? I believe encryption_blob_encrypted is used because E2EE decryption is all done in the background. In the case of option gated Resource load, the decryption is done explicitly and should be awaited, so it will be in a ready state at that point.

Also, did you see the update I made yesterday on #87 (added with UPDATE: at the bottom)?

you're right, a separate readyStatus value is probably not needed.

encryption_blob_encrypted/readyStatus('encrypted') is mainly for E2EE background decrypt flow. for local encryption, the gated resource path can await decrypt when vault is unlocked, and when locked we should intentionally render the locked placeholder flow instead of treating it as a download state.

so i'll keep status handling simple and use is_locally_encrypted + placeholder interaction logic for the UX split, rather than adding a new readyStatus enum.

and yes, i saw your #87 UPDATE and will align with that placeholder-to-decrypt path.

FYI I’m not sure how far through you got with implementing the latest changes to the proposal, but in the save section of 3.2, make sure the following sections are removed

Setting resources is not relevant anymore, and deleting revisions will be done on gated load instead (for an encrypted note only), but just deleting unencrypted revisions

updated, thanks for catching that. save path now just does encrypt + beforeChangeItemJson suppression, moved the plaintext revision cleanup into the gated load - on every load of an encrypted note, delete any revisions where the encrypted flag isn't set. cleaner since it handles both initial lock and cross-device sync in one place. first post is updated.

Hi @mrjo118 ,
Just wanted to let you know i've finalized my proposal and will be submitting it on the GSoC portal. Let me know if you have any feedback before the deadline!

GSoC_2026_Keshav_Idea7_Local_Note_Encryption.pdf (760.5 KB)

Just a couple of things to mention:

ResourceService.indexNoteResources guard: the background indexer (ResourceService.ts,
L82-91) parses note.body to populate note_resources. For locally encrypted notes, the
ciphertext body would yield no resource IDs, wiping associations. Core change: skip locally
encrypted notes in the indexer.

You mentioned a resource indexer change in the section about why the solution shouldn’t be plugin only, but did not specify anything about this in section 3.11, which describes the changes for encrypted resources. Also I don’t think the indexer specifically needs to be amended, but the service which deletes orphaned resources needs to be amended to skip deleting resources where is_locally_encrypted is true. An optimal way to do this could be to alter the NoteResources.orphanResources function to join to the resources table and exclude encrypted resources there. It means encrypted resources have to always be manually deleted, but that is a minor caveat.

3.3 Option-Gated Note.save / Note.load

In this section, it should be made clear that useLocalEncryption: true will be applied in all places that local encryption / decryption may need to be applied, not just when the note is actually encrypted. Then note that each of the points about the functionality of the gated save / load only applies when is_locally_encrypted is true, with the exception of the migration logic on load, which applies always when useLocalEncryption: true, and this should happen before the check if is_locally_encrypted is true

3.6 Revision Strategy

It’s worth mentioning here that revisions with is_locally_encrypted = true should trigger the unlock UI

hey, thanks for the final notes. All three are in the updated PDF (attaching v2 here).

orphaned resources - went with your suggestion, NoteResources.orphanResources() gets a JOIN to exclude encrypted resources (3.9, point 7). means encrypted resources need manual deletion but that's a reasonable tradeoff to avoid accidental data loss.

option-gated scope - rewrote 3.3 to make it clear useLocalEncryption: true goes on all code paths where encryption might apply, not just when the note is already encrypted. migration (null -> 1 or 0) runs first before the is_locally_encrypted check.

revision viewer - added to 3.6, NoteRevisionViewer checks the flag on each revision and triggers the unlock prompt for encrypted ones.

submitted on the GSoC portal. updated PDF attached.

GSoC_2026_Keshav_Idea7_Local_Note_Encryption_v2.pdf (768.4 KB)

Not sure if it can be amended in the portal now, but if you can, one clarification could be made here

Within the gated path, migration logic (null → 1 or 0) executes first as a prerequisite…

This section does not make it clear the migration only happens on load

yeah you're right, that should say migration only runs on load. on save you're setting the flag explicitly so there's no migration needed there, still have time before the 31st so i'll update the PDF on the portal with this fix. edit: also updated first post in thread
GSoC_2026_Keshav_Idea7_Local_Note_Encryption_v2.1.pdf (768.5 KB)

All good!

awesome, thanks for all the guidance throughout. looking forward to getting started on this if selected.

Will it work on android or ios?

@testhh No need to ask on every proposal. The majority if not all the proposals for this project include mobile support as well