Security
What the app defends, and what it does not
Your notes stay in a vault folder on your disk; the app reads and writes that folder. Community plugins are third-party JavaScript that runs inside the app. Once you trust a vault, plugin code runs with your user's rights. The app decides whether plugin code runs at all, asks before a plugin starts a program or opens a local file, and keeps plugin file access inside the vault. It does not review plugin code, and it cannot stop a program you allowed from doing what your user account can do.
Restricted mode
Every vault starts in restricted mode.
In restricted mode no community plugin code is loaded: the app refuses to serve a plugin's main.js.
Plugins also get no file access, no programs, no servers, no HTTP requests, no opener and no keychain.
The page keeps a strict content security policy: no eval, no blob: scripts.
Trust is stored in ~/.opensidian.json, outside the vault, so a vault you download cannot ship itself already trusted.
Trust is keyed by the vault's canonical path (symlinks resolved).
Turning restricted mode off asks first, in a native window outside the web page, showing the vault's path. The buttons are Cancel (the default) and Trust; Cancel writes nothing.
Turning restricted mode back on is never asked. When you turn it back on, every program a plugin started is killed and every server a plugin opened is closed. A request that was already under way when you switched is refused.
Community plugins
Plugins are installed into the vault's .obsidian/plugins/<id>/ folder and enabled in .obsidian/community-plugins.json, as in Obsidian.
The built-in browser (Settings > Community plugins > Browse) lists the community plugin list published in obsidianmd/obsidian-releases.
Downloads go over HTTPS only, to a fixed list of GitHub hosts, with no login, token or cookies.
A plugin whose manifest needs a newer Obsidian API than the app provides is refused.
Installing does not trust the vault or enable the plugin; those stay your own steps.
The app records a SHA-256 of each installed file and shows whether the files still match.
A plugin loads only if it is enabled, its folder really is inside the vault (no symlink out), and main.js is a regular file under 32 MB.
In a trusted vault every enabled plugin gets the same desktop API Obsidian gives it; there are no per-plugin permissions.
The page then allows eval, which plugins need.
The desktop capabilities a plugin can use each check the vault's trust on every call:
- File access through the Node
fsAPI, confined to the vault; a path outside it is refused. - Starting programs: asked every time (see consent).
- Local servers: checked on every listen and closed when you turn trust off; no host means all interfaces, as in Node.
- HTTP requests to any host, plain
http://only;https://is refused. - Opening files and URLs with the default app:
http(s)andmailtolinks open without a question, everything else is asked. - Secret storage in the OS keyring (Secret Service), never written to disk by the app, keyed by vault.
Asking before programs and opens
Before a plugin starts a program, a native dialog that page script cannot answer asks you, every time: Deny is the default, Allow runs it once, and no answer is remembered. It shows the full program path, every argument, the working directory and the environment the plugin sets. The program is opened before the question and run through that open file, so swapping it while the dialog is open does not change what runs. Its PATH is cleaned, oversized requests are refused, and at most 5 questions can wait at once.
Opening a local file, folder or custom-scheme link asks too, and a file that changed after you allowed it is refused.
Sandbox
An optional Landlock sandbox (set OPENSIDIAN_LANDLOCK=1) confines the app's own writes to the vault and the caches it needs. It is off by default.
It needs a kernel with Landlock (5.13 or newer); otherwise the app runs as before.
Automated code review
Pull requests are reviewed by automated code-audit services before they are merged, to catch bugs and security issues early: CodeRabbit, Qodo, CodeAnt AI, Greptile and Strix. Their findings are answered in the pull request; they add to human review and testing, they do not replace them.
Reporting a vulnerability
Report security issues privately by email to jendrik@madewithtea.com, not as a public issue.
You can encrypt to the release signing key A6E6 9ED6 BC47 79F3 2817 2148 4CC1 AFDE 15B6 4EA3.
Include the version and package, your distribution, steps or a proof of concept, and what an attacker gains.
You get an acknowledgement within 7 days; the fix ships in a signed release and you are credited unless you decline. Please allow 90 days, or until the fix is released, before disclosing publicly. Only the latest release gets security fixes. In scope is anything a malicious vault, note, theme, CSS snippet or link can do. The full policy is in SECURITY.md.
Verifying downloads
See How do I check that a download is genuine? in the FAQ.
Software bill of materials (SBOM)
No machine-readable SBOM (SPDX or CycloneDX) is published yet. The complete list of Rust dependencies, with exact versions and checksums, is src-tauri/Cargo.lock: 472 packages, 470 of them from crates.io. One crate, glib 0.18.5, is vendored in src-tauri/vendor. The JavaScript the app ships is vendored in ui/vendor (CodeMirror, MathJax, Moment.js, PDF.js, Prism); nothing is fetched from npm at build or run time.