Search...Search plugins and themes...
⌘K
Sign in
  • Get started
  • Download
  • Pricing
  • Enterprise
  • Account
  • Obsidian
  • Overview
  • Sync
  • Publish
  • Canvas
  • Mobile
  • Web Clipper
  • CLI
  • Learn
  • Help
  • Developers
  • Changelog
  • About
  • Roadmap
  • Blog
  • Resources
  • System status
  • License overview
  • Terms of service
  • Privacy policy
  • Security
  • Community
  • Plugins
  • Themes
  • Discord
  • Forum / 中文论坛
  • Merch store
  • Brand guidelines
Follow us
DiscordTwitterBlueskyThreadsMastodonYouTubeGitHub
© 2026 Obsidian

Halfday Rune

Halfday.devHalfday.dev992 downloads

Born-encrypted notes for Obsidian. age (X25519) at rest, live-preview markdown in memory.

Add to Obsidian
Halfday Rune screenshot
Halfday Rune screenshot
Halfday Rune screenshot
Halfday Rune screenshot
  • Overview
  • Scorecard
  • Updates9

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.

Why Rune

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.


What it does

  • Open .age files inline. A custom view decrypts the file with your age identity and shows the cleartext in a normal-looking editor.
  • Live-preview markdown. Headings, bold, italic, inline code, links, wikilinks, fenced code blocks, and lists render the same way they do in any Obsidian note.
  • Save re-encrypts in memory. 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.
  • Multi-recipient encryption. Encrypt to any number of age recipients (your daily-driver key + an offline backup, say). Decrypt with any matching identity.
  • Four commands registered in the command palette:
    • 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.

Install

Manual (today — pre-community-catalog)

  1. Download manifest.json, main.js, and styles.css from the latest release (TBD: link will go live with the v0.7 submission).
  2. Copy them into <your-vault>/.obsidian/plugins/halfday-rune/.
  3. Enable the plugin in Settings → Community plugins. (You'll need Settings → Community plugins → "Turn on community plugins" if you've never enabled one before.)

Community catalog (post-v0.7)

Once submitted and approved, install via Settings → Community plugins → Browse → Halfday Obsidian Rune.


Setup

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.

Keys

# 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.

Plugin settings

Halfday Rune's settings tab has these fields (the first three are desktop-only; the mobile ones are described under Using rune on your phone):

  • Recipients file path — defaults to ~/.age/recipients.txt.
  • Identity path — defaults to ~/.age/vault.identity.
  • Auto-backup before rotate — on by default. Copies every .age file to ~/halfday/logs/age-backups/ before running Rotate vault keys.
  • Mobile unlock file, Auto-lock after idle, Lock after leaving the app — see below.

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.

The confirmed recipient set

Rune only encrypts to a set of keys you have confirmed. The first time it encrypts (or the first unlock on a phone) it shows every key, the count and a fingerprint, and asks. After that, every unlock and every save checks the current set (your recipients file plus your own key, or the list inside the unlock copy) against the confirmed one. Any difference stops it with "your recipients differ from the set you confirmed (+N added, −M removed)" and nothing is encrypted. A key that some other program appended to recipients.txt is never used silently. Saving the recipients editor asks again when you added a key, and updates the confirmed set without asking when you only removed keys. After changing the file any other way, run Halfday Rune: Confirm recipient set; it lists keys new since the last confirm separately.

The confirmed set lives in the plugin's settings (data.json) with a check value made from your private key, so changing it takes your key, not just write access to a file. Every confirm also bumps a counter inside that check. Each device keeps its own record of the newest confirmation it has seen (and whether it uses passphrase unlock) in Obsidian's local storage for the vault, which does not sync. A confirmation made on another device carries a higher counter and is accepted, so changing your keys on the Mac doesn't look like an attack on the phone. If the settings' copy is deleted, replaced with something that doesn't check out, or rolled back to an older confirmation, or the device's record is damaged, rune refuses with "your confirmed recipient set was removed or replaced" instead of asking as if it were the first time, and Confirm recipient set shows what differs from that record. An unlock copy that still holds keys you removed is refused as older than your confirmed set; make a new one on desktop. Only a device with no record at all is asked from scratch, so removing both the settings and Obsidian's local storage, then restoring an older unlock copy you made, still brings back a first-time prompt for that older set: read every key before you confirm. Every key in the prompt is shown whole and labelled "your key" or "other key".

Using rune on your phone

Rune runs on Obsidian mobile. Your key never leaves your desktop in the clear: you make a passphrase-protected copy of it, it syncs with the vault, and the phone unlocks it once per session and keeps it in memory only.

  1. Set up (desktop, once). Command palette → Halfday Rune: Create mobile unlock copy. Choose a long random passphrase (20+ characters, from a password manager). Rune writes _rune/identity.age in your vault and checks it by unwrapping it again before reporting success. If the file already exists you are asked before it is replaced. The path is the Mobile unlock file setting.
  2. Let it sync. The _rune folder is a normal visible folder so Obsidian Sync and iCloud carry it. If you use Obsidian Sync, enable "Other file types" so .age files sync.
  3. Unlock (phone). Open any .age note, or run Halfday Rune: Unlock rune. Enter the passphrase (a password manager can fill it). A wrong passphrase shows "Wrong passphrase" and lets you retry. Notes you write on the phone are encrypted to the same recipients desktop uses (the list is stored inside the passphrase-protected file), so they open on desktop and with your backup key as usual.
  4. Lock. Halfday Rune: Lock rune now, or tap the "rune: unlocked" status-bar item. Open notes switch to a locked screen and their decrypted text is discarded. Two settings lock automatically:
    • Auto-lock after idle (minutes), default 15. 0 = never.
    • Lock after leaving the app (seconds), default 60. 0 = lock the moment the app is backgrounded.

Rotating the key and editing recipients stay desktop-only. The unlock file holds your key and your full recipients list (a small rune-mobile-key v1 text payload: the identity plus one recipient: age1... line per recipient, all inside the passphrase-encrypted file, so nobody can add a key to the phone's list without the passphrase). If your own key's recipient is not in your recipients file, rune adds it to the copy and says so. Re-run Create mobile unlock copy whenever you change your recipients; the success message tells you what changed (recipients updated: +N / −M) when you reuse the passphrase. Unlock files from earlier builds hold the bare key only; unlocking one says to re-run the command.

Limits, stated plainly. The phone cannot tell which keys a note's header was made for, only how many, so it cannot detect that you swapped a recipient on desktop. If you forget to re-run Create mobile unlock copy, a phone save encrypts to the OLD list: the new key loses access to that note and the removed key gets it back. To catch this, the command also writes a public sidecar (_rune/identity.recipients.sha256, a hash of the recipients list) and desktop shows a persistent warning on load, and after you save the recipients editor, when it no longer matches (or is missing). The sidecar is advisory only: editing or deleting it can only hide or trigger the warning, never change what gets encrypted. Also note that a phone save widens a note to the full wrapped list: a note that had fewer recipients gains all of them.

Before saving an existing note, the phone checks that your unlocked key opens it, then re-encrypts it to the whole wrapped list. If the note on disk has more recipient stanzas than the list (for example you added a recipient on desktop after making the copy), the save is refused with "This note is encrypted to keys your phone doesn't know about — edit it on desktop, or re-run Create mobile unlock copy" and nothing is written. Rotate and backup skip the unlock file itself. The unlock file must use a scrypt work factor of 16 to 19; rune writes 18.

Lost your phone?

The unlock file in your vault is useless without its passphrase, but anyone holding the file can guess the passphrase offline with no rate limit, which is why it must be long and random. If you lose a phone:

  1. Change the passphrase. On desktop, run Create mobile unlock copy again with a NEW passphrase and confirm the replacement. The old passphrase stops working on new copies of the file. This does not help against someone who already has a copy of the old file plus the old passphrase: assume that person has your key.
  2. If the old passphrase may have leaked (or the phone was unlocked when lost), rotate the key: create a new key, run Rotate vault keys to re-encrypt every note, then run Create mobile unlock copy again so the phone copy holds the new key.
  3. Remove rune from the lost device's sync if you can (sign the device out of Obsidian Sync / iCloud), and delete any stale copies of the old _rune/identity.age.

Rotation does not reach old copies: earlier synced versions of your notes and any backups are still encrypted to the old key, so they stay readable with the old key even after a rotation.

Usage

  • Edit an existing .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).
  • Create a new private note: command palette → Halfday Rune: New private note, enter a filename, hit Create. Plaintext never touches disk.
  • Encrypt an existing .md: command palette → Halfday Rune: Encrypt current note → .age. The plaintext is replaced with <name>.md.age after round-trip verify.
  • Decrypt a .age back to plaintext: command palette → Halfday Rune: Decrypt current .age → .md. Modal asks whether to delete the .age after writing.

Threat model

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.

What it protects

  • At-rest content of sealed notes. .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.
  • Plaintext leakage to Obsidian's metadata cache and search index. .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.
  • Auto-load network leaks. The v0.6.3 sanitization pass refuses to auto-load remote images, refuses to render 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.
  • Plaintext on encrypt. "Encrypt current note → .age" round-trip-verifies the ciphertext before deleting the plaintext source. If verification fails, the plaintext is preserved.
  • Plaintext on rotate. "Rotate vault keys" round-trip-verifies each file before overwriting it, optionally backs up (copies) the whole vault's .age files first, and continues on per-file failure rather than aborting.

What it does NOT protect

  • Cleartext in JS memory while a .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.
  • Your identity file at rest. By default ~/.age/vault.identity is unencrypted on disk. Optional: run "Create mobile unlock copy", then "Use passphrase unlock on this computer" (or the Unlock method setting). The desktop then unlocks like the phone, holds the key in memory only and locks on idle, and you can delete the identity file yourself once it works. A computer with no identity file can switch too: it shows the keys inside the unlock copy and you confirm them. Rotate and "Create mobile unlock copy" need key file mode. Malware running as you can still read the unlocked key from the app's memory. 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.
  • Side-channel inference from filenames or directory layout. A note named 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.
  • The age cryptography itself. Halfday Rune wraps typage (the TypeScript port of age); cryptographic correctness is age's responsibility, not ours.
  • Obsidian, Electron, or your operating system. A compromised Obsidian build, a malicious community plugin running in the same context, or a kernel-level adversary defeats this plugin trivially.
  • Cloud sync providers (iCloud, Dropbox, etc.). Sync replicates .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.

Permissions & why

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:

  • Filesystem access outside the vault (Node 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. On desktop this is the key source. Obsidian mobile has no such filesystem access, so the phone uses a passphrase-protected copy of the identity stored in the vault (_rune/identity.age, read through the Obsidian vault API) and holds the unlocked key in memory only; rotate, backup and the recipients editor stay desktop-only.
  • Vault enumeration (Obsidian API). The "Rotate vault keys" command lists .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.
  • No shell execution. As of v0.6.6 the plugin uses no 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.)
  • No network access. The plugin makes no network requests of any kind — no telemetry, no update checks, no remote image loading (see sanitization below).
  • No clipboard access. As of v0.6.6 the plugin reads and writes nothing to the system clipboard.

Everything above is auditable in the open-source code under src/.

Sanitization rules (v0.6.3)

Decrypted markdown flows through these passes before rendering:

  • Raw HTML rendered as literal monospace text. No <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.
  • Remote and local images are deferred. ![alt](url) 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.

Reporting security issues

See SECURITY.md. In short: please report security issues privately (not via a public GitHub issue) and we'll get back to you.


Support

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.


License

Apache 2.0. Copyright 2026 — see the LICENSE file for the copyright holder line.


Contributing

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.

HealthExcellent
ReviewCaution
About
Encrypt vault files at rest with age (X25519) keys and keep plaintext only in editor memory so notes never remain unencrypted on disk. Open .age files inline: decrypt in memory, show a CodeMirror editor with live Markdown preview, then re-encrypt on save while supporting multi-recipient encryption and key rotation.
FilesEditing
Details
Current version
0.8.1
Last updated
2 days ago
Created
7 months ago
Updates
9 releases
Downloads
992
Compatible with
Obsidian 1.8.7+
Platforms
Desktop, Mobile
License
Apache-2.0
Report bugRequest featureReport plugin
Sponsor
Buy Me a Coffee
Author
Halfday.devHalfday.devhalfday-dev
GitHubhalfday-dev
  1. Community
  2. Plugins
  3. Files
  4. Halfday Rune

Related plugins

Thino

Quickly capture memos and display them in the sidebar with a heatmap. (Closed source)

Gemini Scribe

Allows you to interact with Gemini and use your notes as context.

Code Space

Professional code file management with visual dashboard, syntax highlighting editor, Markdown embedding, external folder mounts, and terminal integration.

Remember cursor position

Remember cursor and scroll position for each note.

Notebook Navigator

A better file browser and calendar inspired by Apple Notes, Bear, Evernote and Day One.

LanguageTool Integration

Advanced grammar and spell checking, powered by LanguageTool.

Text Format

Format selected text upper/lower/capitalize.

Claudian

Embeds Claude Code/Codex and other local Agents as AI collaborators in your vault.

Meta Bind

Make your notes interactive with inline input fields, metadata displays, and buttons.

Advanced URI

Control everything with URI.