You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: Deploy paste service with end-to-end encryption (zero-knowledge)
Add AES-256-GCM client-side encryption to the short URL sharing flow.
Plans are encrypted in the browser before upload — the paste service
stores only ciphertext it cannot read. The decryption key lives only
in the URL fragment (#key=...) and never leaves the browser.
- New packages/shared/crypto.ts with encrypt/decrypt via Web Crypto API
- Encrypt before POST in createShortShareUrl, decrypt on load
- Parse #key= fragment in useSharing.ts for both direct and import URLs
- Deploy Cloudflare Worker with KV namespaces to workers.dev
- Add deploy-paste CI/CD job to deploy.yml
- Fix Cache-Control from public to private, no-store
- Update ExportModal strings with encryption messaging
- Update README, docs, and blog with zero-knowledge encryption details
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: README.md
+10-5Lines changed: 10 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,14 +39,19 @@ Interactive Plan Review for AI Coding Agents. Mark up and refine your plans usin
39
39
40
40
Plannotator lets you privately share plans, annotations, and feedback with colleagues. For example, a colleague can annotate a shared plan, and you can import their feedback to send directly back to the coding agent.
41
41
42
-
Plans are shared via compressed URL through a static site: **share.plannotator.ai**
42
+
**Small plans**are encoded entirely in the URL hash — no server involved, nothing stored anywhere. The data never leaves your browser.
43
43
44
-
- No backend or database; nothing is stored
45
-
- The site's deployment is open source
46
-
- You can self-host your own share site and point Plannotator to it via an environment variable ([see docs](https://plannotator.ai/docs/guides/sharing-and-collaboration/))
44
+
**Large plans** use a short link service with **end-to-end encryption**. Your plan is encrypted with AES-256-GCM in your browser before it's uploaded — the server stores only ciphertext it cannot read. The decryption key lives only in the URL you share and is never sent to the server. Pastes auto-delete after 7 days.
45
+
46
+
- Zero-knowledge storage — not even the service operator can read stored plans (similar to [PrivateBin](https://privatebin.info/))
47
+
- No accounts, no tracking, no cookies on the share portal
48
+
- Fully open source and self-hostable ([see docs](https://plannotator.ai/docs/guides/sharing-and-collaboration/))
49
+
50
+
> [!NOTE]
51
+
> [share.plannotator.ai](https://share.plannotator.ai) uses a default fallback (demo) plan that is hard-coded into the site. This isn't a leaked plan — the site has no storage layer.
47
52
48
53
> [!NOTE]
49
-
> [share.plannotator.ai](https://share.plannotator.ai) uses a default fallback (demo) plan that is hard-coded into the site. This isn't a leaked plan—the site has no storage layer.
54
+
> Short links are end-to-end encrypted. A single-use AES-256-GCM key is generated in your browser via the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey), used to encrypt the plan, and embedded in the URL fragment (`#key=...`). The key never leaves the browser — it is never sent to the paste service or any server. Only someone with the exact URL can decrypt the plan.
Copy file name to clipboardExpand all lines: apps/marketing/src/content/blog/sharing-plans-with-your-team.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -108,7 +108,8 @@ The hash fragment of a URL is never sent to a server in HTTP requests — that's
108
108
This means:
109
109
110
110
-**No accounts.** No sign-ups, no OAuth, no tokens.
111
-
-**No storage.** Nothing is persisted anywhere. Close the tab and the data exists only in the URL you copied.
111
+
-**No storage (small plans).** Nothing is persisted anywhere. Close the tab and the data exists only in the URL you copied.
112
+
-**End-to-end encrypted (large plans).** When a plan is too large for a URL, short links encrypt your plan with AES-256-GCM in your browser before uploading. The paste service stores only ciphertext it cannot read — the decryption key lives only in the URL you share. Pastes auto-delete after 7 days.
112
113
-**No tracking.** The share portal has no analytics, no cookies, no telemetry.
113
114
-**Self-hostable.** If even a static page hosted by someone else isn't acceptable, you can [self-host the portal](/docs/guides/self-hosting/) and point Plannotator at it with `PLANNOTATOR_SHARE_URL`.
Copy file name to clipboardExpand all lines: apps/marketing/src/content/docs/guides/self-hosting.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -20,11 +20,11 @@ Plannotator has three components. Only the hook is required.
20
20
21
21
Small plans are encoded entirely in the URL hash — the share portal reads the hash and renders the plan. No backend involved. The data remains private — it never leaves the URL.
22
22
23
-
Large plans don't fit in a URL. **When a user explicitly confirms** short link creation, the compressed plan is sent to the paste service, which stores it and returns a short ID. A compressed plan goes in, a link to retrieve it comes out. The share URL becomes `share.plannotator.ai/p/aBcDeFgH` (or `your-portal.example.com/p/aBcDeFgH` if self-hosting). When someone opens that link, the portal fetches the compressed data from the paste service, decompresses it, and renders the plan.
23
+
Large plans don't fit in a URL. **When a user explicitly confirms** short link creation, the plan is **encrypted in the browser** (AES-256-GCM) before being sent to the paste service, which stores only the ciphertext and returns a short ID. The decryption key is embedded in the URL fragment (`#key=...`) and never sent to the server — not even the paste service operator can read stored plans. When someone opens that link, the portal fetches the ciphertext, decrypts it client-side using the key from the URL, and renders the plan.
24
24
25
25
**Without paste service:** Sharing still works for plans that fit in a URL. Those plans stay completely private — the data lives only in the URL hash and never touches a server. Large plans show a warning that the URL may be truncated by messaging apps.
26
26
27
-
**With paste service:** Large plans get short, reliable URLs that work everywhere. Plannotator temporarily stores the compressed plan data — it auto-deletes after the configured TTL.
27
+
**With paste service:** Large plans get short, reliable URLs that work everywhere. Data is end-to-end encrypted and auto-deletes after the configured TTL.
Copy file name to clipboardExpand all lines: apps/marketing/src/content/docs/guides/sharing-and-collaboration.md
+5-2Lines changed: 5 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -70,10 +70,12 @@ When a plan is too large for a URL (~2KB+ compressed), messaging apps like Slack
70
70
5. A short URL like `share.plannotator.ai/p/aBcDeFgH` is generated
71
71
6. Both the short URL and the full hash URL are shown — the short URL is safe for messaging apps
72
72
73
-
### Privacy
73
+
### Privacy & encryption
74
74
75
+
- Plans are **end-to-end encrypted** (AES-256-GCM) in your browser before upload — the paste service stores only ciphertext it cannot read
76
+
- A single-use encryption key is generated in your browser via the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey). The key **never leaves the browser** — it is never sent to the paste service or any server. It exists only in the URL fragment (`#key=...`), which browsers never include in HTTP requests per the HTTP specification. Not even the service operator can decrypt stored plans.
75
77
- Plans are only uploaded when you explicitly click "Create short link" — no data leaves your machine until you confirm
76
-
- Pastes auto-expire and are permanently deleted (hosted: a few days, self-hosted: configurable via `PASTE_TTL_DAYS`)
78
+
- Pastes auto-expire and are permanently deleted (hosted: 7 days, self-hosted: configurable via `PASTE_TTL_DAYS`)
77
79
- The paste service is fully open source — you can audit exactly what it does
78
80
- Self-hosters can run their own paste service for complete control — see the [self-hosting guide](/docs/guides/self-hosting/)
79
81
- If the paste service is unavailable, the full hash URL is always available as fallback
@@ -88,3 +90,4 @@ By default, share URLs point to `https://share.plannotator.ai`. You can self-hos
88
90
- The share portal is a static page — it only reads the hash and renders client-side
89
91
- No analytics, no tracking, no cookies on the share portal
90
92
- Short URLs are opt-in — data is only uploaded when you explicitly click "Create short link" (see [Short URLs for large plans](#short-urls-for-large-plans) for details)
93
+
- Short URLs use end-to-end encryption (AES-256-GCM) — the decryption key is embedded in the URL fragment and never sent to the server. The paste service stores only opaque ciphertext, similar to [PrivateBin](https://privatebin.info/)
Copy file name to clipboardExpand all lines: apps/marketing/src/content/docs/reference/environment-variables.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -23,7 +23,7 @@ All Plannotator environment variables and their defaults.
23
23
24
24
| Variable | Default | Description |
25
25
|----------|---------|-------------|
26
-
|`PLANNOTATOR_PASTE_URL`|`https://paste.plannotator.ai`| Base URL of the paste service API. Set this when self-hosting the paste service. |
26
+
|`PLANNOTATOR_PASTE_URL`|`https://plannotator-paste.plannotator.workers.dev`| Base URL of the paste service API. Set this when self-hosting the paste service. |
0 commit comments