SecretSpans
approvedby blueloop46
Encrypt inline secrets in Markdown notes; copy mixed selections as plaintext. - This plugin has not been manually reviewed by Obsidian staff.
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
.mdfile. 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.
- Place the cursor where you want the secret in the note.
- Right-click → Insert secret at cursor (or use the command palette).
- Type the secret value and your master password in the modal → Insert.
- The token appears as 🔒 in the note.
- To read or copy secrets: right-click → Unlock note, enter your password.
- Select any mix of text and secrets → Ctrl/Cmd+C → clipboard holds the full plaintext.
- 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"
| Command | When to use |
|---|---|
| Insert secret at cursor | Add a new secret safely — plaintext never touches the document |
| Encrypt selection | Encrypt already-typed text into a secret token (see sync risk below) |
| Unlock current note | Prompt for password; decrypt all secrets into memory |
| Lock current note | Clear decrypted state; all secrets re-lock to 🔒 |
| Edit secret at cursor | Change an existing secret value; note must be unlocked, cursor on the token |
Suggested hotkeys
Assign these in Settings → Hotkeys (search "SecretSpans"):
| Command | Suggested hotkey |
|---|---|
| Insert secret at cursor | Ctrl/Cmd+Shift+I |
| Encrypt selection | Ctrl/Cmd+Shift+E |
| Unlock current note | Ctrl/Cmd+Shift+U |
| Lock current note | Ctrl/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.
| Source | Risk |
|---|---|
| Obsidian Sync / iCloud / Dropbox / Git | The 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 history | After 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.