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

Hi there,

I got a genuine issue from user, thought this might be useful for you. He says the encrypted note can be deleted without entering a password and similarly the title can be modified as well.

Thanks for sharing, this is useful.

Doesn’t leak content, but delete/rename is still important too, will keep this in mind going fwd with project.

Honestly I don't agree with what that user is proposing. For both secure notes and this proposal, the title is not encrypted, so it doesn't matter if you can modify it or not. Though I guess it wouldn't harm to make the title readonly when you open the note in a locked state.

Regarding restricting deleting an encrypted note, I don't agree with the idea that it can't be deleted. What if you forget your password and its permanently unrecoverable? You should be able to delete it then.

That also makes sense,
i was thinking of it more as a ux thing than a security rule. Blocking delete completely would be wrong if note is unrecoverable, but making title readonly while locked still sounds reasonable to me.

I haven’t gone through the full proposal draft yet, but I do have an architectural idea in mind. See if this works for you. Secure Notes already supports per-note encryption, and you mentioned that you plan to use a single password for note encryption. Based on that, here’s a possible approach:

You could introduce a vault-like section called Private Notes:

When a user opens this section for the first time, Joplin prompts them to create a Vault Password. This password can later be changed from Settings. And there should be an option to nuke the Private Vault in case the user forgets the password, this is a destructive operation, it would completely reset the vault by deleting both the vault password and all private notes stored inside it.

With this approach, all private notes remain isolated inside a single encrypted vault and can only be accessed after entering the vault password which prevents normal users from see the private note titles, and they would not be able to delete them without unlocking the vault first.

@cipherswami Implementing a private vault is out if scope for the project, because it would involve a higher level of complexity which there probably is not enough time for. This project just implements encrypting note contents (and attachments) and batch encryption of notes

I don't know, this might look complicated but I think this is a lot easier.

I’m assuming the encryption/decryption part is already handled. The main point I’m unsure about is how encrypted notes are being identified. With the approach mentioned above, the vault is nothing but a special kind of notebook. The notebook ID itself could be used to detect an encrypted note.

Hmm, I just realised this approach kind of makes it a notebook Lock :roll_eyes:

The rest is same for all the other approaches.