|
| 1 | +# Security Model |
| 2 | + |
| 3 | +This document explains how ZenNotes approaches security across desktop, web, and self-hosted deployment. |
| 4 | + |
| 5 | +It is intentionally candid. The goal is not to sound maximally hardened. The goal is to explain what ZenNotes actually protects, what it assumes, and where the current boundaries are. |
| 6 | + |
| 7 | +## The core security assumption |
| 8 | + |
| 9 | +ZenNotes is easiest to reason about when you treat it as: |
| 10 | + |
| 11 | +- a single-user notes system |
| 12 | +- built around plain files on disk |
| 13 | +- optionally reachable over a network through a trusted self-hosted server |
| 14 | + |
| 15 | +That assumption shapes almost every security choice. |
| 16 | + |
| 17 | +ZenNotes is not currently built as: |
| 18 | + |
| 19 | +- a public multi-tenant SaaS security boundary first |
| 20 | +- a zero-trust collaboration system |
| 21 | +- a product that expects arbitrary untrusted browser clients and untrusted filesystem tenants |
| 22 | + |
| 23 | +## Why the security model changed |
| 24 | + |
| 25 | +When ZenNotes was effectively only a local desktop app, the threat model was simpler. |
| 26 | + |
| 27 | +Once ZenNotes gained: |
| 28 | + |
| 29 | +- a self-hosted browser mode |
| 30 | +- a Go server |
| 31 | +- desktop remote workspaces |
| 32 | +- Docker deployment |
| 33 | + |
| 34 | +the old assumptions stopped being good enough. |
| 35 | + |
| 36 | +The biggest changes were: |
| 37 | + |
| 38 | +- moving browser auth to session cookies |
| 39 | +- treating browse roots as real authorization boundaries |
| 40 | +- separating host config from vault config |
| 41 | +- keeping desktop remote credentials out of renderer-visible state |
| 42 | +- making Docker defaults more restrictive |
| 43 | + |
| 44 | +## The system has three main trust zones |
| 45 | + |
| 46 | +### 1. Local desktop runtime |
| 47 | + |
| 48 | +The desktop app is still a trusted local application. |
| 49 | + |
| 50 | +It can: |
| 51 | + |
| 52 | +- access the local vault directly |
| 53 | +- talk to the remote server from the main process |
| 54 | +- use native integrations |
| 55 | + |
| 56 | +Its protection story is mostly about: |
| 57 | + |
| 58 | +- isolating the renderer from privileged APIs |
| 59 | +- preventing untrusted renderers from using privileged IPC |
| 60 | +- storing secrets outside plain config where possible |
| 61 | + |
| 62 | +### 2. Self-hosted server |
| 63 | + |
| 64 | +The Go server is the trust boundary for browser access. |
| 65 | + |
| 66 | +It is responsible for: |
| 67 | + |
| 68 | +- authenticating clients |
| 69 | +- scoping accessible filesystem roots |
| 70 | +- validating origins |
| 71 | +- serving only what the browser should get |
| 72 | + |
| 73 | +The server is where a browser request becomes a filesystem operation, so the server boundary matters more than the client boundary in self-hosted mode. |
| 74 | + |
| 75 | +### 3. Host filesystem |
| 76 | + |
| 77 | +The host filesystem is the real source of truth for notes. |
| 78 | + |
| 79 | +That is a product strength, but it also means security is not only about HTTP. It is also about: |
| 80 | + |
| 81 | +- what directories the server can reach |
| 82 | +- whether Docker mounts are scoped correctly |
| 83 | +- where secrets are stored |
| 84 | +- whether the app accidentally writes sensitive operational config into a synced vault |
| 85 | + |
| 86 | +## Why browser auth uses sessions now |
| 87 | + |
| 88 | +Using a bootstrap token in URLs or long-lived browser storage creates avoidable leakage points. |
| 89 | + |
| 90 | +So the current browser model is: |
| 91 | + |
| 92 | +1. the user enters a bootstrap token once |
| 93 | +2. the server verifies it |
| 94 | +3. the server issues a random session |
| 95 | +4. the browser continues with an `HttpOnly` cookie |
| 96 | + |
| 97 | +This matters because it reduces exposure through: |
| 98 | + |
| 99 | +- local storage |
| 100 | +- copied URLs |
| 101 | +- logs and history |
| 102 | +- accidental token reuse in browser-visible state |
| 103 | + |
| 104 | +It is not full identity and account management. It is a tighter single-user session model. |
| 105 | + |
| 106 | +## Why browse roots matter so much |
| 107 | + |
| 108 | +ZenNotes now has a server-side directory browser and remote vault switching. |
| 109 | + |
| 110 | +That is useful, but it is also the most obvious place where convenience can turn into overreach. |
| 111 | + |
| 112 | +So the current design treats browse roots as a real boundary: |
| 113 | + |
| 114 | +- resolve the requested path |
| 115 | +- resolve symlinks |
| 116 | +- reject anything outside allowed roots |
| 117 | + |
| 118 | +This is one of the most important practical controls in the self-hosted product, because it narrows what a remote client can even ask the server to consider a vault. |
| 119 | + |
| 120 | +## Why Docker is part of the security story |
| 121 | + |
| 122 | +Docker is not only a convenience story for ZenNotes. It is also part of the security posture. |
| 123 | + |
| 124 | +The current default container setup: |
| 125 | + |
| 126 | +- binds to loopback |
| 127 | +- runs non-root |
| 128 | +- drops Linux capabilities |
| 129 | +- uses `no-new-privileges` |
| 130 | +- keeps the root filesystem read-only |
| 131 | + |
| 132 | +This does not make Docker magically secure. But it does mean the default self-hosted path is narrower and safer than a broad all-interfaces, writable-root, root-running container. |
| 133 | + |
| 134 | +## Why desktop secrets stay in the main process |
| 135 | + |
| 136 | +For remote workspaces, the risky design would be: |
| 137 | + |
| 138 | +- renderer holds raw server token |
| 139 | +- renderer fetches server directly |
| 140 | + |
| 141 | +ZenNotes instead pushes that work toward: |
| 142 | + |
| 143 | +- main-process networking |
| 144 | +- main-process asset proxying |
| 145 | +- secret-store-backed credential persistence |
| 146 | + |
| 147 | +That is the right direction because the Electron renderer should not be treated as the place where you casually keep long-lived remote credentials. |
| 148 | + |
| 149 | +## Why the vault must not store server secrets |
| 150 | + |
| 151 | +ZenNotes intentionally supports: |
| 152 | + |
| 153 | +- shared mounted vaults |
| 154 | +- synced vaults |
| 155 | +- desktop plus browser against the same files |
| 156 | + |
| 157 | +That means the vault is a terrible place to hide operational server secrets. |
| 158 | + |
| 159 | +So the current direction is: |
| 160 | + |
| 161 | +- vault stores vault behavior |
| 162 | +- host config stores host/server operations and auth secrets |
| 163 | + |
| 164 | +That separation is fundamental. Without it, the product would keep smuggling server operational state into the same content tree users want to sync and inspect. |
| 165 | + |
| 166 | +## The current honest limitations |
| 167 | + |
| 168 | +ZenNotes is more hardened than before, but a few important tradeoffs remain. |
| 169 | + |
| 170 | +### Electron sandboxing is not fully done |
| 171 | + |
| 172 | +Desktop currently keeps: |
| 173 | + |
| 174 | +- `contextIsolation: true` |
| 175 | +- `nodeIntegration: false` |
| 176 | + |
| 177 | +but still uses: |
| 178 | + |
| 179 | +- `sandbox: false` |
| 180 | + |
| 181 | +That is not ideal. It is an acknowledged technical debt item driven by the current preload path. |
| 182 | + |
| 183 | +### CSP still has exceptions |
| 184 | + |
| 185 | +The current CSP is materially better than a meta-tag-only approach, but it still contains: |
| 186 | + |
| 187 | +- `unsafe-eval` |
| 188 | +- `unsafe-inline` for styles |
| 189 | + |
| 190 | +That reflects the current rendering/editor stack, not an ideal end state. |
| 191 | + |
| 192 | +### The product is not pretending to be multi-tenant SaaS-hard |
| 193 | + |
| 194 | +ZenNotes is strongest today when used as: |
| 195 | + |
| 196 | +- local desktop app |
| 197 | +- single-user self-hosted web app |
| 198 | +- remote desktop-to-server setup in a trusted deployment |
| 199 | + |
| 200 | +That is the practical security promise. |
| 201 | + |
| 202 | +## The right mental model for operators |
| 203 | + |
| 204 | +If you are deploying ZenNotes for browser use, think like this: |
| 205 | + |
| 206 | +- the server is privileged |
| 207 | +- the mounted vault is valuable |
| 208 | +- the browser is a client, not the authority |
| 209 | +- the reverse proxy is your public perimeter if you expose it remotely |
| 210 | + |
| 211 | +That model will lead you to the right defaults more often than treating ZenNotes like a static web app that happens to read files somewhere. |
| 212 | + |
| 213 | +## Related docs |
| 214 | + |
| 215 | +- [Security Reference](../reference/security-reference.md) |
| 216 | +- [Secure Self-Hosting](../how-to/secure-self-hosting.md) |
| 217 | +- [How ZenNotes Works](./how-zennotes-works.md) |
0 commit comments