Skip to content

Security: Shockwave3301/encrypt-selection

SECURITY.md

Security policy

Encrypt Selection encrypts selected text inside an Obsidian note. This document states precisely what that does and does not protect, describes the construction so it can be reviewed, and explains how to report a vulnerability privately.

Read the "What this does not protect" section before you rely on this plugin for anything that matters. Several of the items in it are the difference between "the text is safe" and "the text is sitting in plaintext in a different file in the same vault".

Threat model

What this protects against: someone who obtains your vault files and reads them. A stolen or lost laptop, a synced folder on a machine that has been compromised, a backup or a disk image someone else can read, a repository you pushed somewhere public by accident.

Against that, text you have encrypted with this plugin is protected by the strength of your password. There is no key escrow, no recovery mechanism and no backdoor.

What this does not protect against: anything running as you on an unlocked machine. Malware, a malicious Obsidian plugin, a keylogger, or a person sitting at your unlocked computer can read your password as you type it or read the plaintext after you decrypt it. Obsidian plugins share one JavaScript context and are not sandboxed from each other. If your machine is compromised while you are using it, this plugin does not help.

It also does not hide the fact that something is encrypted. Tokens are visibly tokens.

What this does not protect

Encrypting text is not the whole job. Obsidian and its surroundings keep copies of your notes from before you encrypted them, and encrypting today does nothing about yesterday's copy. All of the following are outside this plugin's control:

  • File recovery. A core Obsidian plugin, on by default, which stores periodic plaintext snapshots of every note in a local database inside your vault. Anyone with your vault files can read them. This is the single most likely way encrypted text leaks. Disable File recovery, or clear its history, for any vault where this plugin is protecting something real. The settings tab carries a standing warning about this.

  • Obsidian Sync version history. Sync retains pre-encryption versions in cloud version history. Encrypting a paragraph does not remove the earlier version from that history. The same settings-tab warning covers this.

    Both warnings are shown unconditionally rather than probing whether those core plugins are actually enabled. Detecting them requires an undocumented API, and a guarded lookup that quietly failed would quietly drop a security warning — the wrong failure mode here.

  • Undo history. After encrypting, the plaintext remains in the editor's undo stack for the rest of the session, and one Ctrl+Z brings it back.

  • The search index. May contain pre-encryption content until it is rebuilt.

  • Backups and external copies. Time Machine, File History, OneDrive, Dropbox, Syncthing, git. Each holds the pre-encryption file. If the note lives in a git repository, the plaintext is in its history permanently, and encrypting it later does not remove it.

  • Note titles, filenames, links, tags and every unencrypted part of the note. Only the text you selected is encrypted. A note called Bank logins.md reveals a great deal regardless of its body.

  • Memory. JavaScript cannot reliably zero memory. Your password lives in RAM until Obsidian closes or you run the lock command, and even then the runtime may hold copies that cannot be reached or overwritten. Swap files, hibernation images and crash dumps can contain them.

  • Your password. Someone holding the file can attack it offline, at whatever rate their hardware allows. 600,000 PBKDF2 iterations raises the cost per guess; it does not rescue a short, common or reused password. This is the weakest part of the system by a wide margin.

There is no password recovery. If you forget the password, the text is unrecoverable. That is a design property, not a defect.

Construction

  • Cipher: AES-256-GCM, via the Web Crypto API (crypto.subtle). Authenticated encryption, so a wrong password and a tampered payload are both detected and reported rather than yielding plausible-looking garbage.

  • Key derivation: PBKDF2-HMAC-SHA256, 600,000 iterations by default, per current OWASP guidance. Configurable, and the iteration count used is recorded in every payload, so raising the default never renders existing text undecryptable.

  • Salt: a fresh 16 bytes from crypto.getRandomValues for every encryption. Never reused.

  • IV: a fresh 12 bytes from crypto.getRandomValues for every encryption. Never reused, which is the property GCM depends on absolutely.

  • Authenticated header: the complete payload header — magic bytes, format version, KDF id, cipher id, iteration count, salt, IV and hint — is passed to GCM as additional authenticated data. Nothing in the header can be altered without failing the 16-byte tag check. In particular the iteration count cannot be silently lowered and the hint cannot be silently rewritten.

  • Payload: base64 of a length-prefixed binary struct, roughly 57 bytes of overhead.

  • Hint: stored in plaintext, by design, so it can be shown before the password is entered. Do not put anything sensitive in it.

  • Dependencies: none at runtime. No third-party cryptographic code is in the trust surface. Everything is the platform's own Web Crypto implementation. main.js requires nothing but obsidian, so there is no supply chain to compromise.

  • Network: none. The plugin issues no fetch, XMLHttpRequest, WebSocket or beacon, and loads no remote assets; the built bundle contains no URLs at all. Nothing you encrypt, and no password you type, can leave the machine, because no code path exists to send it.

  • Telemetry: none, client-side or server-side. Nothing is counted, sampled or reported.

  • File access: none. The plugin manipulates the text of the active note through Obsidian's editor API and never reads or writes files itself, inside the vault or outside it.

  • Dynamic code: none. No eval, no new Function, no runtime imports. The plugin cannot alter its own behaviour after installation.

    These five are the claims most worth distrusting, so they are written to be checkable rather than believed: each is a single grep over src/ or the released main.js.

  • Password handling: the password is cached in memory for the app session only, and is never passed to saveData() or written to disk in any form. Derived keys are memoised per password:salt:iterations. Forget cached password (lock) and the status-bar indicator clear the cache.

The security-relevant code is confined to src/crypto/ and src/format/, which import nothing from obsidian and are unit-tested directly. The tests include wrong-password, flipped-ciphertext-byte and tampered-header cases; the last of these is what demonstrates the AAD binding actually holds.

Supported versions

Version Supported
1.0.x Yes

Security fixes are released against the latest version. Given the plugin's size, the expectation is that you update rather than that fixes are backported.

Reporting a vulnerability

Do not open a public issue for a security problem.

Report it privately through GitHub's private vulnerability reporting:

  1. Go to https://github.com/Shockwave3301/encrypt-selection/security/advisories/new
  2. Or: the repository's Security tab → Report a vulnerability

This opens a private advisory visible only to you and the maintainers.

Please include the plugin version, the Obsidian version and platform, what you observed, and the smallest reproduction you can manage. Do not include a real password or any real note content — construct a throwaway example.

What counts as a vulnerability here:

  • Recovering plaintext without the password.
  • Making an incorrect password appear to succeed, or making a tampered payload decrypt without an error.
  • Any nonce, salt or IV reuse.
  • The password or plaintext being written to disk, logged, or leaving the process.
  • Anything that makes existing correctly-encrypted text permanently undecryptable.

What does not:

  • Anything already listed under "What this does not protect" — those are documented properties, and reporting them privately just delays the reply. If you think one is worse than described, or that the documentation is misleading, please do open a normal issue.
  • Offline guessing of a weak password.
  • Compromise of the machine while Obsidian is running.

Expect an acknowledgement within a week. If a fix is warranted, it ships as a patch release with the advisory published alongside it, and you will be credited unless you prefer otherwise.

There aren't any published security advisories