Halfday.dev102 downloadsBorn-encrypted notes for Obsidian. age (X25519) at rest, live-preview markdown in memory.
X25519 age encryption for Obsidian notes.
Open a .age file in your vault and Halfday Rune decrypts it in memory, mounts a CodeMirror editor on the cleartext, and re-encrypts on save. Plaintext never touches disk for born-encrypted notes; for converted notes it lives on disk only as ciphertext.
🌐 halfday.dev/products/rune — full product page, screenshots, and the launch story.
Status: pre-release. The plugin is in active development by halfday. v0.7 will submit it to the Obsidian Community Plugins catalog.
Your Obsidian vault is plaintext on disk. That used to be fine. In 2026 it isn't: AI desktop tools (Claude Desktop via MCP filesystem servers, Cursor's codebase indexing, ChatGPT's macOS Work-with-Apps), cloud sync providers, indexers like Microsoft Recall, and the growing pile of agents with disk access can all read your notes. Obsidian Sync is end-to-end encrypted in transit but the local files are plaintext.
Rune encrypts at rest using age. Plaintext exists only in editor memory while a note is open; close the tab and it's gone from the process. New notes can be born-encrypted (plaintext never on disk at all). Multi-recipient support means you encrypt to both your daily-driver key and a backup, so losing one device doesn't lose the vault. Rotate-keys re-encrypts every .age file to your current recipients when you change the set.
Full case in the launch post: Using Obsidian Securely in the AI Era.
.age files inline. A custom view decrypts the file with your age identity and shows the cleartext in a normal-looking editor.cmd-S (or 30s of inactivity) encrypts the buffer, round-trip-verifies, then overwrites the .age on disk. If verification fails, the on-disk file is left untouched.Halfday Rune: Test round-trip (X25519) — proves your keys + the typage WASM stack work without touching any files.Halfday Rune: Encrypt current note → .age — seals an existing .md to .md.age, deletes the plaintext after round-trip verify.Halfday Rune: New private note — born-encrypted .age file; plaintext never on disk.Halfday Rune: Decrypt current .age → .md — inverse of the encrypt command. Two modes: replace (delete .age after writing .md) or scratch (keep both).Halfday Rune: Rotate vault keys — re-encrypts every .age file in the vault to your current recipients list. Optional pre-rotation tar.gz backup.manifest.json, main.js, and styles.css from the latest release (TBD: link will go live with the v0.7 submission).<your-vault>/.obsidian/plugins/halfday-rune/.Once submitted and approved, install via Settings → Community plugins → Browse → Halfday Obsidian Rune.
Halfday Rune needs an age recipient list and a matching identity on disk. The defaults match what the halfday CLI sealer uses, so if you already use seal.sh you're done.
# generate an X25519 keypair
mkdir -p ~/.age && chmod 700 ~/.age
age-keygen -o ~/.age/vault.identity
chmod 600 ~/.age/vault.identity
# extract the public recipient line into recipients.txt
grep '^# public key:' ~/.age/vault.identity \
| sed 's/^# public key: //' > ~/.age/recipients.txt
~/.age/recipients.txt is the canonical recipient list. One age1... recipient per line; #-prefixed lines are comments. Adding a backup recipient is just adding another line — the plugin will encrypt new notes to all listed recipients, and Rotate vault keys updates already-sealed files.
Halfday Rune's settings tab has three fields:
~/.age/recipients.txt.~/.age/vault.identity..age file at ~/halfday/logs/age-backups/ before running Rotate vault keys.There's also an inline "Recipients (file content)" editor — paste your age1... lines, save, and the plugin writes them to disk verbatim (preserving comments and ordering). Save validates the whole file before writing; malformed input refuses to save with the offending line called out inline.
.age file: click it in the file tree. The custom view decrypts to memory, mounts an editor, and saves with cmd-S (or after 30s).Halfday Rune: New private note, enter a filename, hit Create. Plaintext never touches disk..md: command palette → Halfday Rune: Encrypt current note → .age. The plaintext is replaced with <name>.md.age after round-trip verify..age back to plaintext: command palette → Halfday Rune: Decrypt current .age → .md. Modal asks whether to delete the .age after writing.Halfday Rune exists because plaintext on disk is the primary risk for a vault that contains personal reflection, therapy notes, or anything else you wouldn't paste into an LLM. The plugin treats your local disk as semi-trusted (the OS protects your home directory) but treats anything that touches a cloud-sync provider, an LLM context window, or another process's memory as adversarial.
.age files on disk are byte-compatible with the standard age CLI: ChaCha20-Poly1305 over an X25519 + HKDF key-agreement. The plaintext is recoverable only by an identity holder..age files are routed through Halfday Rune's custom view; they never reach Obsidian's MarkdownView, file cache, or backlinks resolver. There is no [[wikilink]] graph routing, no backlink, no full-text search index for sealed notes.javascript: or data: URLs as clickable affordances, refuses to transclude ![[embed]] references, and renders raw HTML (<script>, <iframe>, <b>, …) as literal text rather than parsed markup..age files first, and continues on per-file failure rather than aborting..age view is open. The whole point of the live editor is to manipulate cleartext; while a .age file is open in a Halfday Rune view, its content lives in the CodeMirror document and in the plugin's heap. Closing the view discards the buffer; the plugin never writes plaintext to a temp file, swap, or recovery file. Browser/OS memory dumps, debugger access, or a malicious extension running in the same Electron process can still see it.~/.age/vault.identity is unencrypted on disk. The threat model assumes your local user account is not adversarial. If disk theft is in scope, wrap the identity with age -p or use age-plugin-se (Secure Enclave) when that integration ships.therapy_2026_q1.md.age reveals "I went to therapy in Q1 2026" to anyone who can read your filesystem listing. Halfday Rune doesn't rename or scramble paths..age ciphertext, not plaintext, so confidentiality holds — but a sync provider with timing visibility can correlate edit patterns. If that's in scope, host your vault locally.A security plugin should be legible about every capability it uses. Here's the full list — including the ones an automated reviewer (correctly) flags — and why each is necessary:
fs). Halfday Rune reads your age identity from ~/.age/vault.identity and your recipient public keys from ~/.age/recipients.txt, and writes rotation logs and pre-rotation backups under ~/halfday/logs/. The identity lives outside the vault on purpose: storing your private key inside the encrypted vault it unlocks would defeat the encryption. Logs and backups stay out of the vault so cloud sync doesn't churn on recovery artifacts. This out-of-vault filesystem dependency is also why the plugin is desktop-only — Obsidian mobile has no equivalent filesystem access..age files via Obsidian's vault API so it can re-encrypt every sealed note to your current recipient set. It only ever reads .age paths; it does not read or index your plaintext notes.child_process and runs no shell commands. The pre-rotation backup is a plain Node fs file copy. (Earlier versions shelled out to tar; that was removed.)Everything above is auditable in the open-source code under src/.
Decrypted markdown flows through these passes before rendering:
<script>, <iframe>, <object>, <embed> ever evaluates. Inline tags (<b>, <i>, …) render as visible angle-bracket source, not as styled markup.javascript: and data: link schemes get no link affordance. No accent color, no underline, no hidden-URL trick. The raw [text](javascript:...) source stays visible verbatim so what you see is what's on disk. is replaced with a placeholder chip reading [image: alt — url]. Moving the cursor onto the span reveals the raw markdown for editing. No HTTP request fires. v0.6.3 does not distinguish local-vault images from remote — both are deferred uniformly.![[embed]] is inert. The ! prefix bypasses the wikilink decoration entirely. The text renders as literal prose. No transclusion is performed; transclusion would require resolving the embed against Obsidian's metadata cache, which the rest of the plugin spends a lot of effort avoiding.See SECURITY.md. In short: please report security issues privately (not via a public GitHub issue) and we'll get back to you.
Rune is free and open source, and always will be. If it's part of how you keep your vault private, you can buy us a coffee — a thank-you for the work, not a feature gate. Security OSS only works when trust is the currency, so nothing is ever paywalled.
Apache 2.0. Copyright 2026 — see the LICENSE file for the copyright holder line.
Halfday Rune is built in TypeScript with obsidian-plugin-cli for the build, esbuild under the hood, and vitest for unit tests.
npm install
npm run dev # watch-mode build into the plugin folder
npm test # vitest run
npx tsc --noEmit # type-check
The plugin design plan and milestone history live in the vault_plugin_v0_plan document (link TBD).
Plugin scaffold initially generated by create-obsidian-plugin.