# 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](https://obsidian.md) 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" | 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:` ``` --- ## 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) ```bash 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 ```bash 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`](ARCHITECTURE.md) for architecture decisions and module responsibilities.