SecretSpans

approved

by blueloop46

Encrypt inline secrets in Markdown notes; copy mixed selections as plaintext. - This plugin has not been manually reviewed by Obsidian staff.

19 downloadsUpdated 5d agoMIT

SecretSpans

⚠️ Use at your own risk

  • No password recovery. Your password is never stored anywhere. Lose it and your encrypted secrets are gone permanently.
  • No security audit. This plugin has not been reviewed by a cryptographer. Do not rely on it to protect high-value secrets.
  • No stability guarantee. This is early software. Keep backups of any note containing encrypted spans.

An Obsidian plugin for encrypting inline secrets inside Markdown notes.

A note stays ordinary plaintext — except for chosen values, which are stored as encrypted inline tokens. After unlocking a note once with a master password, you can select any region (ordinary text + N secrets) and press Ctrl/Cmd+C once to get the full decrypted plaintext on the clipboard. The .md file on disk always contains only ciphertext.


Features

  • Partial encryption — only chosen values are encrypted; the rest of the note is plain Markdown.
  • One password per note — unlock a note once; all its secrets become copyable.
  • Mixed-selection copy — select text + secretA + text + secretB, one Ctrl/Cmd+C → clipboard has the full plaintext. No ciphertext ever reaches the clipboard.
  • No plaintext persistence — unlocking never modifies the .md file. Plaintext lives only in memory until you lock or close Obsidian.
  • Live Preview + Reading View — works in both editor modes.
  • Edit secrets in-place — re-encrypt an existing secret without it ever appearing as plaintext in the raw editor.

Quick start — secure workflow

Always use Insert secret at cursor when adding a new secret. This is the safest approach: the secret value is typed directly into a modal and goes straight to ciphertext — the note body never sees the plaintext, so Obsidian's autosave, sync, and File Recovery cannot capture it.

  1. Place the cursor where you want the secret in the note.
  2. Right-click → Insert secret at cursor (or use the command palette).
  3. Type the secret value and your master password in the modal → Insert.
  4. The token appears as 🔒 in the note.
  5. To read or copy secrets: right-click → Unlock note, enter your password.
  6. Select any mix of text and secrets → Ctrl/Cmd+C → clipboard holds the full plaintext.
  7. When done: right-click → Lock note.

Commands

All commands are available from two places:

  • Right-click in the editor — context menu (recommended for quick access)
  • Command palette — Ctrl/Cmd+P, search "SecretSpans"
CommandWhen to use
Insert secret at cursorAdd a new secret safely — plaintext never touches the document
Encrypt selectionEncrypt already-typed text into a secret token (see sync risk below)
Unlock current notePrompt for password; decrypt all secrets into memory
Lock current noteClear decrypted state; all secrets re-lock to 🔒
Edit secret at cursorChange an existing secret value; note must be unlocked, cursor on the token

Suggested hotkeys

Assign these in Settings → Hotkeys (search "SecretSpans"):

CommandSuggested hotkey
Insert secret at cursorCtrl/Cmd+Shift+I
Encrypt selectionCtrl/Cmd+Shift+E
Unlock current noteCtrl/Cmd+Shift+U
Lock current noteCtrl/Cmd+Shift+L

Important behaviours

Password consistency. All secrets in a note must use the same password. If you try to insert or encrypt using a password that does not match the existing secrets, the operation is blocked with a notice. Unlock the note first to verify the correct password.

Auto-lock on insert. When you insert or encrypt a new secret into an already-unlocked note, the plugin automatically locks the note before writing the token. This keeps the display consistent (all secrets show as 🔒) rather than mixing unlocked and locked. Unlock the note again to see all secrets together.

Edit secret preconditions. Edit secret at cursor requires the note to be unlocked AND the cursor placed on an existing secret token. If the note is locked, unlock it first. If the cursor is not on a token, move it onto one and try again.


Security

  • Crypto: AES-256-GCM with PBKDF2-SHA-256 at 210,000 iterations, random 16-byte salt + 12-byte IV per secret.
  • Web Crypto API only — no third-party crypto libraries.
  • Nothing persisted — passwords and plaintext are never written to disk or localStorage.
  • Block-and-warn copy policy — any copy touching a locked secret is blocked with a notice; ciphertext never reaches the clipboard.

Token format

`secretlock:v1:<base64(salt[16] | iv[12] | ciphertext+GCM-tag)>`

Security guidance for users

Choose a strong password

Your password is the only protection for your secrets. Use a long random passphrase (4+ words) or a password generated by a password manager. A weak password can be brute-forced offline by anyone who obtains your .md files — the encryption does not prevent guessing, it only makes it slow.

There is no recovery path. If you forget your password, your secrets are gone. Store the password in a password manager, not in another note.

The window of exposure — sync and autosave

Obsidian may save and sync your note before you encrypt.

When you type a secret value into a note body and then run Encrypt selection, there is a window — even if only a few seconds — during which Obsidian has already auto-saved the plaintext to disk and potentially uploaded it to cloud sync. Use Insert secret at cursor to avoid this entirely.

SourceRisk
Obsidian Sync / iCloud / Dropbox / GitThe plaintext version of the note may be uploaded before you encrypt. Check your sync provider's version history and delete any plaintext snapshots after encrypting.
Obsidian File Recovery plugin (core, on by default)Snapshots the note periodically. A snapshot taken between typing and encrypting will contain plaintext. Find snapshots at Settings → File recovery and verify none predate your encryption.
Editor undo historyAfter encrypting, pressing Ctrl/Cmd+Z will undo the encryption and restore the plaintext. The note is in plaintext until you encrypt again or close without saving.

Clipboard safety

  • This plugin only writes plaintext to the clipboard on an explicit Ctrl/Cmd+C of an unlocked selection. It never writes ciphertext.
  • Clipboard managers (Alfred, Raycast, Paste, etc.) capture everything written to the system clipboard. Disable clipboard history for Obsidian, or lock the note immediately after copying.
  • On macOS, copying any other item replaces the clipboard contents. On Windows/Linux, clear it manually or use a clipboard manager with auto-clear.

Backups

Keep regular backups of your vault. Encrypted tokens are unrecoverable without the correct password — a corrupted token cannot be repaired. Backups protect against accidental deletion and file corruption, not against password loss.


Installation

Manual (development)

git clone https://github.com/blueloop46/secret-spans
cd secret-spans
npm install
npm run build

Copy main.js, manifest.json, and styles.css into your vault's .obsidian/plugins/secret-spans/ folder, then enable the plugin in Settings → Community plugins.

Tests

npm test

Runs unit tests for the pure core (format, substitute, crypto) — no Obsidian runtime needed.


Project layout

src/
  main.ts          # Plugin entry: commands, context menu, owns unlock state
  format.ts        # TOKEN_RE, tokenRegex(), parseTokens, isToken — pure
  crypto.ts        # encrypt / decrypt — pure, Web Crypto
  substitute.ts    # substituteTokens — pure, the copy core
  unlockState.ts   # Per-file plaintext cache; unlock / lock / lockAll
  livePreview.ts   # CM6 decorations + copy handler
  readingView.ts   # Markdown post-processor + copy handler
  ui.ts            # PasswordModal, InsertSecretModal, EditSecretModal
test/
  substitute.test.ts
  crypto.test.ts
  crypto.fuzz.test.ts
ARCHITECTURE.md    # Architecture decisions and module map

Contributing

See ARCHITECTURE.md for architecture decisions and module responsibilities.

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.