share: the key never travels in a URL

The share page used to put the key in the API path, and every embedded
picture or video carried it in its src, so a reverse proxy's access log
held whole links. The page now sends the key in the body of
POST /api/share/{token}. The answer sets a diary_share cookie
(__Secure- over HTTPS) scoped to /api/share/{token}/, HttpOnly,
SameSite=Strict, good for twelve hours, and signed over the key's hash.
Media is served from /api/share/{token}/media/{id} only to a browser
holding that cookie, so minting a new key or unsharing voids it.

The old /api/share/{token}/{key} routes are gone; links themselves are
unchanged, since the key was always in their fragment.
This commit is contained in:
nvrl committed 2026-10-20 12:33:59 +02:00
1 parent fe1b12042d
commit 947cc2ba96
8 files changed
+257 -51

No files matched your search

+9 -2
View File
@@ -546,6 +546,13 @@ the path holds a URL that answers `404`. The server stores nothing but a
SHA-256 of the key and compares in constant time, and a wrong key is answered
exactly like an unknown token, so guessing never confirms that a token is live.
The page sends the key on to the server once, in the body of the request that
reads the entry, never in a URL. That answer gives the browser a cookie scoped
to this one link's path, good for 12 hours, and the entry's pictures and
videos are fetched with it — so not even this server's reverse proxy logs the
key. A fresh key from `:link`, or `:unshare`, voids the cookies handed out
under the old one.
Because only the hash is kept, a link exists in full exactly once: in the moment
`:share` copies it. It cannot be re-read from the entry afterwards. `:link`
mints a fresh key for the same token — which retires the previous link, making
@@ -593,8 +600,8 @@ sealed or not.
| `POST` | `/api/vault/unlock`, `/api/vault/lock` | open / close the vault |
| `GET`/`POST` | `/api/ssh-keys` | the keys that may mount over SFTP / add one |
| `DELETE` | `/api/ssh-keys/{id}` | remove a key, which ends its mounts |
| `GET` | `/api/share/{token}/{key}` | public: read a shared document |
| `GET` | `/api/share/{token}/{key}/media/{id}` | public: media inside a shared document |
| `POST` | `/api/share/{token}` | public: read a shared document, body `{"key"}`; sets the share's media cookie |
| `GET` | `/api/share/{token}/media/{id}` | public: media inside a shared document, with that cookie |
## Deploying