{"id":49294112,"url":"https://github.com/flowdesktech/flowvault","last_synced_at":"2026-05-08T06:53:34.520Z","repository":{"id":352567884,"uuid":"1215603700","full_name":"Flowdesktech/flowvault","owner":"Flowdesktech","description":"Privacy-first notepad: Argon2id + AES-256-GCM, hidden-volume plausible deniability (N notebooks per URL), optimistic concurrency, dead-man's switch, drand/tlock time-locked notes, and one-shot encrypted send. Next.js frontend, Firebase Functions, auditable Firestore rules. MIT.","archived":false,"fork":false,"pushed_at":"2026-04-24T13:19:25.000Z","size":13018,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-26T02:32:49.034Z","etag":null,"topics":["aes-gcm","argon2id","client-side-encryption","cryptpad-alternative","encrypted-notepad","end-to-end-encryption","firebase","firestore","hidden-volumes","nextjs","plausible-deniability","privacy-tools","privnote-alternative","protectedtext-alternative","self-hostable","standard-notes-alternative","tailwindcss","trusted-handover","typescript","zero-knowledge"],"latest_commit_sha":null,"homepage":"https://useflowvault.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Flowdesktech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T04:39:17.000Z","updated_at":"2026-04-24T13:19:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"2c3a033f-2318-4673-b095-6b3d05ecde00","html_url":"https://github.com/Flowdesktech/flowvault","commit_stats":null,"previous_names":["flowdesktech/flowvault"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/Flowdesktech/flowvault","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowdesktech%2Fflowvault","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowdesktech%2Fflowvault/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowdesktech%2Fflowvault/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowdesktech%2Fflowvault/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Flowdesktech","download_url":"https://codeload.github.com/Flowdesktech/flowvault/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowdesktech%2Fflowvault/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32320683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T23:26:28.701Z","status":"online","status_checked_at":"2026-04-27T02:00:06.769Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aes-gcm","argon2id","client-side-encryption","cryptpad-alternative","encrypted-notepad","end-to-end-encryption","firebase","firestore","hidden-volumes","nextjs","plausible-deniability","privacy-tools","privnote-alternative","protectedtext-alternative","self-hostable","standard-notes-alternative","tailwindcss","trusted-handover","typescript","zero-knowledge"],"created_at":"2026-04-26T02:30:31.721Z","updated_at":"2026-05-08T06:53:34.506Z","avatar_url":"https://github.com/Flowdesktech.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Flowvault\n\n**The encrypted notepad where a decoy password is a feature, not a\nbug.** Pick a URL, set a password, write. One Flowvault link can hold\nup to 64 independent notebooks behind 64 different passwords \u0026mdash;\neach unlocks its own workspace, and the server sees one\nindistinguishable blob either way. Hand over a decoy if you\u0026rsquo;re\never forced to; your real notebook stays invisible.\n\n\u003e **1 URL · up to 64 notebooks · 512 KiB total · Argon2id + AES-256-GCM · no account · open source end-to-end**\n\n**[Try it](https://useflowvault.com)** · **[1min intro (video)](https://youtu.be/wkoAIseRicY)** · **[Blog](https://useflowvault.com/blog)** · **[Security](https://useflowvault.com/security)** · **[Self-host](#setup)** · **[Donate](https://useflowvault.com/donate)**\n\n## See it in 30 seconds — live demo, no sign-up\n\nA 1-minute intro covering plausible deniability, time-locked notes, trusted handover, and Encrypted Send (also on **[YouTube](https://youtu.be/wkoAIseRicY)**):\n\n\u003cp align=\"center\"\u003e\n  \u003cvideo src=\"https://github.com/user-attachments/assets/733273ea-787a-44c2-81cd-437f57104443\" poster=\"https://raw.githubusercontent.com/Flowdesktech/flowvault/master/public/video/intro-poster.jpg\" controls muted playsinline width=\"720\"\u003e\n    \u003ca href=\"https://youtu.be/wkoAIseRicY\"\u003e\n      \u003cimg src=\"https://raw.githubusercontent.com/Flowdesktech/flowvault/master/public/video/intro-poster.jpg\" alt=\"Flowvault 1-minute intro — click to watch on YouTube\" width=\"720\" /\u003e\n    \u003c/a\u003e\n  \u003c/video\u003e\n\u003c/p\u003e\n\nThere's also a public demo vault at **[useflowvault.com/s/demo](https://useflowvault.com/s/demo)** with two pre-loaded notebooks behind two different passwords, so you can see the hidden-volume design work before picking a password of your own:\n\n| Password | What opens |\n| --- | --- |\n| `CorrectPassword` | The \"real\" notebook — walkthrough + example tabs for wallet seeds, API keys, scratchpad |\n| `DecoyPassword` | The decoy — boring on purpose, shaped like what a plausible cover notebook actually looks like |\n\nSame URL, same ciphertext blob on the server, two completely different screens. Lock the vault in between to feel it. The server rejects all writes at the demo URL — local edits only, nothing is saved or shared with the next visitor. Don't put real secrets in it anyway; it's a public walkthrough.\n\n## Moving from another tool?\n\n- **ProtectedText:** [modern ProtectedText alternative](https://useflowvault.com/alternatives/protectedtext) with Argon2id (64 MiB / 3 iter), AES-256-GCM, hidden-volume decoy passwords, and `.fvault` backups.\n- **Privnote / Bitwarden Send:** [Encrypted Send](https://useflowvault.com/alternatives/privnote) creates view-capped, expiring, self-destructing links for short secrets.\n- **Standard Notes / Notesnook:** [use Flowvault as the deniable scratchpad beside your main notes app](https://useflowvault.com/alternatives/standard-notes), not as a full PKM replacement.\n- **Plausible deniability:** [try the live use case](https://useflowvault.com/use-cases/plausible-deniability) with `CorrectPassword` and `DecoyPassword`.\n\n## Is this the right tool for you?\n\nFlowvault is deliberately narrow. It's good at roughly two jobs and\nhonest about everything else.\n\n**Reach for Flowvault when you want:**\n\n- An encrypted scratchpad you can open from any browser without signing up \u0026mdash; borrowed laptops, work machines, phones, library kiosks.\n- A place for notes you'd rather not be associated with: recovery phrases, wallet seeds, medical info, contact details for sensitive relationships, things you'd hand over a *decoy* password for at a border crossing.\n- A modern ProtectedText replacement with stronger crypto, hidden volumes, and an open backend you can self-host.\n- A one-shot self-destructing link (**Encrypted Send**) to share a password or API key without making the recipient sign up for Bitwarden or 1Password first.\n- A one-shot self-destructing **file** upload (**Encrypted File Send**) for sharing up to 10 MiB \u0026mdash; recovery key files, signed PDFs, screenshots of secrets \u0026mdash; with a separate *secure delete link* you can fire any time before it expires.\n- To keep the ciphertext off our servers entirely \u0026mdash; same app, but the vault lives as a single `.flowvault` file on your own disk (**Bring Your Own Storage**).\n\n**Pick something else if:**\n\n- You want a multi-device notes app with sync \u0026mdash; try [Standard Notes](https://standardnotes.com) or [Notesnook](https://notesnook.com). Flowvault is browser-only; no native mobile app today.\n- You keep long-form journals. Each slot holds ~8 KiB (roughly 1,500 words). Great for dense notes; too tight for daily journaling across a year. Use [Obsidian](https://obsidian.md) or [Joplin](https://joplinapp.org) with your own E2EE sync instead.\n- You need real-time collaborative editing. Use [CryptPad](https://cryptpad.fr). Flowvault handles two editors on the same vault via optimistic concurrency, but it isn't a live-cursor experience.\n- You're stashing a crypto wallet seed and want an air-gapped workflow \u0026mdash; keep using [KeePassXC](https://keepassxc.org) or a paper backup in a safe. Flowvault is great as a *second* location for a split seed, not as the only copy.\n- Your threat model includes a persistent network observer correlating writes against a specific identity. Content deniability is intact; vault *existence* at your chosen URL is observable. See the [security page](https://useflowvault.com/security) for the honest version.\n\n---\n\n## What's unique in this category\n\n- **Hidden volumes** \u0026mdash; VeraCrypt-style plausible deniability for a browser notepad. One URL, up to 64 notebooks, each behind its own password. Empty slots are indistinguishable from real ciphertext. ([deep dive](https://useflowvault.com/blog/plausible-deniability-hidden-volumes-explained))\n- **Trusted handover** \u0026mdash; nominate a beneficiary with a separate password; if you stop checking in for an interval you configure, the vault auto-hands over. No account required for either party. ([deep dive](https://useflowvault.com/blog/trusted-handover-encrypted-notes-beneficiary))\n- **Time-locked notes** \u0026mdash; drand + tlock identity-based encryption, so even the sender can\u0026rsquo;t decrypt a message before its target date. ([deep dive](https://useflowvault.com/blog/time-locked-notes-drand-tlock))\n- **Encrypted Send** \u0026mdash; one-shot, self-destructing links for sharing a password or recovery phrase. Key in the URL fragment, view cap enforced server-side by a Cloud Function. ([vs Bitwarden Send / Privnote](https://useflowvault.com/blog/encrypted-send-vs-bitwarden-send-privnote))\n- **Encrypted File Send** \u0026mdash; same shape as Encrypted Send, but for files up to 10 MiB. Pick an expiry (max 7 days) and a download cap (default 1); the encrypted ciphertext lives in Cloud Storage and is hard-deleted the moment the last download is consumed. The sender also gets a separate **secure delete link** (a token bound to the upload by SHA-256) so they can destroy it before the cap or expiry. Optional password gate, just like Encrypted Send.\n- **Bring Your Own Storage** \u0026mdash; keep the whole ciphertext on your own disk as a single `.flowvault` file via the File System Access API. Same hidden-volume format, same Argon2id + AES-GCM, same multi-notebook tabs \u0026mdash; the server just never sees the blob. S3-compatible and WebDAV backends are on the roadmap.\n- **Markdown preview \u0026amp; syntax-highlighted code blocks** \u0026mdash; GitHub-flavored Markdown (tables, task lists, fenced code) renders in-browser with a toggleable Edit / Preview / Split view. HTML is blocked by default, external images are click-to-load, and external links use `no-referrer` \u0026mdash; a preview that can\u0026rsquo;t quietly exfiltrate your vault contents. ([deep dive](https://useflowvault.com/blog/markdown-preview-code-highlighting))\n- **Cmd+K search, bounded by your session** \u0026mdash; a command-palette (`Ctrl`/`Cmd` + `K`) that searches titles and content across every notebook you\u0026rsquo;ve unlocked in this browser session. No persistent index, no server contact, no IndexedDB cache \u0026mdash; the corpus is simply the plaintext already in memory. Slots whose password you haven\u0026rsquo;t supplied stay invisible to it by construction; locking the vault drops the search surface with the bundle.\n- **Zero-knowledge `.fvault` backup + plaintext Markdown export** \u0026mdash; portable across self-hosted instances, still opaque on disk. ([format spec](https://useflowvault.com/blog/encrypted-backup-fvault-format))\n- **Open end-to-end.** Frontend, Cloud Functions, and Firestore security rules all live in this repo. MIT-licensed. Self-hostable. ([vs ProtectedText](https://useflowvault.com/blog/flowvault-vs-protectedtext))\n\nSecurity and crypto review is welcome. Open a GitHub issue with the\n`security-review-wanted` or `crypto-review-wanted` template if you spot\na threat-model gap, metadata leak, crypto invariant problem, or wording\nthat overclaims what Flowvault can protect.\n\nBuilt with Next.js, Firebase Firestore (opaque ciphertext storage),\nFirebase Functions, and client-side Argon2id (64 MiB / 3 iter) +\nAES-256-GCM. No account, no email, no phone number \u0026mdash; a URL slug\nand a password is the whole identity system.\n\n\u003e **Available for hire** \u0026mdash; Flowvault is built by\n\u003e **[Flowdesk](https://flowdesk.tech)**, a small studio shipping\n\u003e privacy-first web apps, end-to-end encrypted systems, crypto/web3\n\u003e products, and native \u0026 hybrid mobile apps. Led by a senior engineer\n\u003e with **4 years shipping production cryptography at\n\u003e [FlowCrypt](https://flowcrypt.com)** (OpenPGP email \u0026mdash; iOS + Chrome\n\u003e Extension, 2022\u0026ndash;2026). Limited engagements per quarter \u0026mdash;\n\u003e reach out at\n\u003e **[contact@flowdesk.tech](mailto:contact@flowdesk.tech?subject=Flowdesk%20%E2%80%94%20project%20inquiry%20(via%20Flowvault))**.\n\n---\n\n## Why use Flowvault instead of ProtectedText (or similar)?\n\nFlowvault is not \"another ProtectedText clone.\" It's a deliberate upgrade on\nnearly every dimension that matters for a zero-knowledge notepad.\n\n### Security\n\n\n| Property                           | Flowvault                                                                      | ProtectedText                                                                             |\n| ---------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |\n| Password-to-key derivation         | **Argon2id**, 64 MiB memory-hard, 3 iterations, HKDF expansion                 | Argon2id, 32 MiB, adaptive ~300 ms (per current `main.js`)                                |\n| Legacy plaintext-password blob     | **No** — every blob requires the full Argon2 chain                             | **Yes** — every save also uploads `encryptedContentLegacy` keyed only by the raw password |\n| Encryption                         | **AES-256-GCM** (authenticated: ciphertext cannot be tampered with undetected) | AES-256-CBC via CryptoJS (no authentication, malleable)                                   |\n| Plausible deniability              | **Yes** — multiple passwords unlock different notebooks on the same URL        | No — one password, one blob                                                               |\n| Fixed-size ciphertext              | **Yes** — every vault is exactly 512 KiB regardless of content                 | No — blob size leaks how much you've written                                              |\n| Tamper detection                   | **Yes** — GCM auth tag fails on any modification                               | No — bitflips go undetected                                                               |\n| KDF parameters stored in the vault | **Yes** — upgradable without breaking old vaults                               | No — clients pick parameters at save time                                                 |\n| Optimistic concurrency on writes   | **Yes** — two tabs can't silently clobber each other                           | Hash-based overwrite protection (works, but pessimistic — fails the second writer)        |\n| Open source                        | **Frontend + Cloud Functions + Firestore rules**, MIT-licensed, self-hostable  | Client JS inspectable in browser; **server code explicitly closed** (per their own FAQ)   |\n| Bring Your Own Storage             | **Yes** — vault can live as a single `.flowvault` file on your own disk (File System Access API); S3-compatible \u0026 WebDAV planned | No — vault always lives on their server                                                    |\n\n\n### Features you actually want\n\n- **Hidden-volume vaults** — the headline feature. One URL, N notebooks, one blob. If someone coerces a password out of you at a border crossing, you hand over the decoy. Cryptographically indistinguishable from a single-notebook vault.\n- **Multi-notebook tabs** — each password now unlocks a *workspace*, not just one page. Add tabs, rename them, reorder them, delete them. Everything lives inside the same encrypted slot, so the tab list, titles, and contents are all zero-knowledge — the server sees one opaque blob, same as always. Decoy passwords get their own independent tab set in their own slot; adding tabs in your real notebook doesn't touch the decoy and vice versa.\n- **Time-locked notes** — encrypt a message to a future date using the [drand](https://drand.love) public randomness beacon and the [tlock](https://github.com/drand/tlock-js) scheme (identity-based encryption over BLS12-381). The ciphertext is stored opaquely; the decryption key literally does not exist until drand's network publishes the target round signature. Nobody — not us, not the sender, not a subpoena — can unlock it early. Share links look like `useflowvault.com/t/\u003cid\u003e`. **Optional password gate:** tick *\"Also require a password to read\"* and the note is double-wrapped — an inner AES-256-GCM layer keyed by Argon2id(password), and an outer tlock layer keyed to the unlock round. Leaked link alone can't read it; the reader needs both the time to pass and the password (shared out-of-band).\n- **Encrypted Send** — one-shot, self-destructing notes for sharing a password, an API key, a recovery phrase, or any snippet you'd rather not sit in chat history. AES-256-GCM encrypted in the browser; the 256-bit key travels in the URL fragment (`#k=...`), which browsers never send to servers. Pick an expiry (up to 30 days) and a view count (default 1); the server hard-deletes the ciphertext the moment the last view is consumed, and a scheduled sweep removes anything past its TTL. Reads go through a Cloud Function so the view counter is atomic — clients can't read the document directly (rules deny it). **Optional password gate** on top, using the same FVPW frame as time-locks, so even a leaked link needs an out-of-band password. Share links look like `useflowvault.com/send/\u003cid\u003e#k=\u003ckey\u003e`.\n- **Encrypted File Send** — same threat model as Encrypted Send, but for actual files. Drop a file (up to 10 MiB), pick an expiry (max 7 days) and a download cap (default 1, max 10), and you get back two links: a *download link* to share with the recipient (`useflowvault.com/file/\u003cid\u003e#k=\u003ckey\u003e`) and a *secure delete link* to keep for yourself (`useflowvault.com/file/\u003cid\u003e/delete#t=\u003ctoken\u003e`). The file bytes are AES-256-GCM encrypted in the browser; the ciphertext lives in Cloud Storage and the metadata (filename, type, size) is encrypted into a small AEAD blob in Firestore. A Cloud Function atomically consumes a download by issuing a short-lived (5 min) v4 signed URL, deletes the document on the final download, and the scheduled `fileSendsSweep` purges expired or consumed objects (plus orphan-uploads from failed creates). The secure delete is authorized by SHA-256-matching a 256-bit token — the server never stored the token, so possession of the original delete link is the proof. Optional password gate is the same Argon2id-derived layer as Encrypted Send, mixed into HKDF so an attacker who steals the URL still needs an out-of-band password.\n- **Bring Your Own Storage (BYOS) — local `.flowvault` files.** Prefer not to leave even ciphertext on a server? Create a vault that lives as a single file on your own disk (`D:\\notes\\journal.flowvault`, an encrypted external drive, whatever you like). The editor opens the file via the File System Access API and reads/writes ciphertext in place; our backend never sees the blob or the file name. The on-disk format is a small JSON header (UUID, Argon2id salt, KDF params, volume layout, monotonic CAS counter) followed by the raw fixed-size hidden-volume blob — byte-for-byte the same ciphertext that would live in Firestore for a hosted vault. Multi-notebook tabs, decoy passwords, and `.fvault` backup/plaintext-Markdown export all work the same; trusted handover is disabled for local vaults because it needs a server-held scheduler. Chromium-based browsers only for now (Chrome, Edge, Brave, etc.); S3-compatible (R2, B2, MinIO) and WebDAV backends are on the roadmap — [open an issue](https://github.com/Flowdesktech/flowvault/issues/new) if one of those would unblock you.\n- **Trusted handover** — nominate a beneficiary and a check-in cadence. If you stop saving for the interval + grace you configure, the vault auto-hands over to a pre-chosen beneficiary password. Weekly / monthly / quarterly / yearly presets. The beneficiary key wraps your master key client-side; the server just schedules the release. Hourly Cloud Function sweeps expired configs; the Firestore rules forbid clients from faking a release or extending one they can't actually open.\n- **Markdown preview with security-first defaults.** Notes render as GitHub-flavored Markdown in-browser: tables, task lists, strikethrough, autolinks, fenced code blocks with Prism syntax highlighting for every common language. A segmented toggle in the toolbar flips between Edit / Preview / Split; the mode preference persists per device in `localStorage` (not in the encrypted blob, so you don't burn bytes of your 512 KiB slot on UI state). The preview is deliberately unusual: **raw HTML is blocked** (`\u003cscript\u003e`, `\u003ciframe\u003e`, arbitrary tags render as literal text), **external images are click-to-load** with a placeholder showing the URL (so `![](https://attacker/pixel?v=target)` can't silently phone home the moment your vault opens), and **external links open with `target=\"_blank\" rel=\"noopener noreferrer\" referrerPolicy=\"no-referrer\"`** so the destination site never learns where the referrer was. Code highlighting runs locally via `prism-react-renderer` \u0026mdash; no network request, no WASM, no remote theme fetch. The renderer bundle itself is lazy-loaded via `next/dynamic`, so users who live in Edit mode never download it.\n- **In-memory Cmd+K search across unlocked notebooks.** `Ctrl`/`Cmd` + `K` opens a command palette that searches titles and content across every tab in the slot you've unlocked this session. Case-insensitive substring matching, grouped by notebook, match highlighting, line numbers; `↑ ↓` to navigate, `Enter` to jump straight to the match with the range selected in the textarea (so the browser scrolls it into view for you), `Esc` to close. The corpus is just the plaintext already sitting in memory — there is no persistent index, no IndexedDB store, no server round-trip, and no way for the palette to see a slot whose password you haven't supplied. Locking the vault drops the search surface with the bundle, so search can't outlive your session.\n- **Optimistic concurrency** — edit the same vault in two browser tabs without losing work.\n- **Modern editor** — keyboard-first (Ctrl/Cmd+S), auto-save with visible status, dark mode, clean typography.\n- **Slot capacity meter** — you always know how much space you have in your notebook.\n- **Encrypted backup \u0026 restore** — download the full vault as a `.fvault` file (opaque ciphertext + KDF params + volume layout). The file is exactly as zero-knowledge as the live vault: it still needs your password to read, and every decoy slot stays indistinguishable from random bytes. Restore to any fresh slug on any Flowvault instance (including a self-hosted one) from `/restore`. Plaintext Markdown export (current slot only) is also available, behind a confirmation, for migrating out to Obsidian et al.\n\n### Trust \u0026 transparency\n\n- **Open source, end to end.** Not just the frontend — the **Cloud Functions** (the trusted-handover sweep) and the **Firestore security rules** (the actual boundary that stops us from reading or mutating your data) are in the same repository, deployed unmodified. ProtectedText publishes its client JS for inspection but explicitly does not open its server code (per their own FAQ). Flowvault is reviewable, licensed, forkable, and self-hostable end-to-end.\n- **No ads. No tracking. No analytics.** Your browser talks to Firestore and nothing else.\n- **No account, no email, no phone number.** A URL slug and a password are all you ever provide.\n- **Self-hostable, whole-stack.** Bring your own Firebase project; the frontend, Functions, and rules all deploy from a single `npm` workspace.\n- **Published threat model.** We tell you exactly what we can and cannot protect against — including the cases where plausible deniability is weaker (e.g., a persistent network observer correlating writes).\n- **Zero-knowledge firewall enforced by Firestore rules.** The rules are short, public, and auditable. We physically can't read your notes even if we wanted to.\n- **Planned build transparency.** Release commits will be tagged and their bundle hashes published, so you can verify the JS your browser runs matches a reviewable commit.\n\n### Ergonomic upgrades\n\n- Clean, modern UI with dark mode by default\n- Ctrl/Cmd+S to save, save status indicator, dirty-state warning before close\n- Ctrl/Cmd+K to open the in-memory search palette across every unlocked notebook\n- Slug validation (no surprises with weird characters)\n- Fast password gate that tells you clearly whether a vault exists or is new\n- Byte/capacity counter so you see when you're near slot limits\n\n---\n\n## Setup\n\n```bash\n# 1. Install deps\nnpm install\n(cd functions \u0026\u0026 npm install)\n\n# 2. Create a Firebase project and paste the config\ncp .env.local.example .env.local\n# Fill in NEXT_PUBLIC_FIREBASE_* values from the Firebase console.\n\n# 3. Deploy the security rules (requires firebase-tools)\nnpx firebase deploy --only firestore:rules,storage\n\n# 4. Run locally\nnpm run dev\n```\n\nTo exercise the Firebase emulators instead of deploying:\n\n```bash\nnpx firebase emulators:start --only firestore,functions,storage\n```\n\n\u003e **Encrypted File Send setup:** the feature uses Firebase Cloud\n\u003e Storage in addition to Firestore. Three one-time setup steps are\n\u003e required before the first deploy will succeed:\n\u003e\n\u003e 1. **Provision the default Storage bucket.** Open\n\u003e    `https://console.firebase.google.com/project/\u003cproject-id\u003e/storage`\n\u003e    → **Get started** → pick a region (match your Firestore region\n\u003e    when you can). Without this, `firebase deploy --only storage`\n\u003e    fails with `Permission 'firebasestorage.defaultBucket.get' denied`\n\u003e    or `defaultBucket … may not exist`. The CLI cannot create the\n\u003e    default bucket itself; the console flow is required once.\n\u003e 2. **Grant the deploy service account the Firebase Storage role.**\n\u003e    Add `roles/firebasestorage.admin` (or the broader\n\u003e    `roles/storage.admin`) to whatever service account is in your\n\u003e    `FIREBASE_SERVICE_ACCOUNT` GitHub secret \u0026mdash; the\n\u003e    Firebase Rules Admin role alone is not sufficient to look up the\n\u003e    default bucket configuration. Quickest path:\n\u003e    ```bash\n\u003e    gcloud projects add-iam-policy-binding \u003cproject-id\u003e \\\n\u003e      --member=\"serviceAccount:\u003cdeploy-sa-email\u003e\" \\\n\u003e      --role=\"roles/firebasestorage.admin\"\n\u003e    ```\n\u003e 3. **Grant the Functions runtime service account the Token Creator\n\u003e    role on itself** (`roles/iam.serviceAccountTokenCreator`), so\n\u003e    `readFileSend` can sign v4 download URLs. Without this, the\n\u003e    function falls through to an `internal` error on the first call.\n\u003e    The runtime SA on a v2 Cloud Functions deploy is usually\n\u003e    `\u003cproject-number\u003e-compute@developer.gserviceaccount.com`;\n\u003e    confirm with\n\u003e    `gcloud functions describe readFileSend --gen2 --region=us-central1 --format='value(serviceConfig.serviceAccountEmail)'`.\n\u003e    The role must be bound on the SA *resource*, not at the project\n\u003e    level \u0026mdash; use `gcloud iam service-accounts add-iam-policy-binding`\n\u003e    (singular `service-accounts`), not `gcloud projects ...`:\n\u003e    ```bash\n\u003e    SA=\u003cruntime-sa-email\u003e\n\u003e    gcloud iam service-accounts add-iam-policy-binding \"$SA\" \\\n\u003e      --member=\"serviceAccount:$SA\" \\\n\u003e      --role=\"roles/iam.serviceAccountTokenCreator\"\n\u003e    ```\n\u003e 4. **Set CORS on the Storage bucket** so the browser can fetch\n\u003e    signed-URL ciphertext. `firebase deploy` handles `storage.rules`\n\u003e    but not bucket-level CORS; apply once with:\n\u003e    ```bash\n\u003e    gcloud storage buckets update gs://\u003cbucket-name\u003e \\\n\u003e      --cors-file=storage.cors.json\n\u003e    ```\n\u003e    Without this, browser downloads fail with `ERR_FAILED 400` /\n\u003e    `Failed to fetch`. The committed `storage.cors.json` allows the\n\u003e    canonical app origin, the Firebase Hosting domains, and\n\u003e    localhost; expand it if you serve File Send from a different\n\u003e    host.\n\n## Firestore schema (summary)\n\n```\nsites/{siteId}\n  ciphertext:  bytes         # fixed-size hidden-volume blob (default 64 × 8 KiB = 512 KiB)\n  kdfSalt:     bytes         # per-site Argon2id salt\n  kdfParams:   map           # algorithm + cost parameters, upgradable\n  volume:      map           # { slotCount, slotSize, frameVersion }\n  version:     number        # CAS counter\n  createdAt:   Timestamp\n  updatedAt:   Timestamp\n\nfileSends/{id}                # Encrypted File Send metadata\n  storagePath:        string  # \"fileSends/{id}\" — points at the Storage object\n  ciphertextSize:     number  # bytes in the Storage object (≤ 10 MiB + AEAD overhead)\n  metadataCiphertext: bytes   # small AEAD blob: { name, contentType, size }\n  expiresAt:          Timestamp\n  maxViews:           number  # 1..10\n  viewCount:          number  # bumped by readFileSend (Admin SDK only)\n  deleteTokenHash:    bytes   # SHA-256 of the 256-bit secure-delete token\n  passwordProtected?: bool\n  passwordSalt?:      bytes   # 16 bytes when password gate is enabled\n  consumedAt?:        Timestamp # set on the final download; sweeper drops the object\n  createdAt:          Timestamp\n```\n\nThe File Send ciphertext itself lives in **Cloud Storage** at\n`fileSends/{id}`; `storage.rules` denies client reads (only Admin SDK\ncan sign URLs) and caps uploads at 10 MiB with `application/octet-stream`.\n\nSee `firestore.rules` and `storage.rules` for the complete\nzero-knowledge rule set.\n\n## Security \u0026 threat model\n\nSee the `/security` page rendered in the app, which documents what the server\nsees, what the hidden-volume format protects against, and — honestly — the\ncases where it does not (e.g., persistent network observation of writes).\n\n## Roadmap\n\nShipped:\n\n- Core zero-knowledge notepad\n- Hidden-volume format for plausible deniability (64 × 8 KiB slots)\n- Argon2id + AES-GCM\n- Optimistic concurrency on writes\n- Decoy-password management UI (\"Add password\" in the editor)\n- Trusted handover: configure / heartbeat-on-save / scheduled release / beneficiary unlock flow\n- drand-backed time-locked notes (`/timelock/new` compose → `/t/{id}` view) via [tlock-js](https://github.com/drand/tlock-js)\n- Encrypted Send: one-shot, self-destructing notes (`/send/new` → `/send/{id}#k=\u003ckey\u003e`)\n- Encrypted File Send: one-shot, self-destructing file uploads up to 10 MiB with a secure delete link (`/file/new` → `/file/{id}#k=\u003ckey\u003e` + `/file/{id}/delete#t=\u003ctoken\u003e`)\n- Multi-notebook tabs per slot — one password, many tabs, all inside the same encrypted blob\n- Encrypted backup/restore (`.fvault`) + plaintext Markdown export for migration\n- Bring Your Own Storage — local `.flowvault` vault files via the File System Access API (first non-Firestore adapter)\n- Markdown preview + syntax-highlighted code blocks — GitHub-flavored Markdown, Edit / Preview / Split toggle, HTML blocked, external images click-to-load, external links `no-referrer`\n- Cmd+K in-memory search across unlocked notebooks — command palette, keyboard-first navigation, jump-to-match in the textarea, no persistent index, no server contact\n\nIn progress / planned:\n\n- **Bring Your Own Storage: more backends** — S3-compatible (AWS S3, Cloudflare R2, Backblaze B2, Wasabi, MinIO), WebDAV (Nextcloud, ownCloud), and experimental IPFS / Storj. All sharing the same `VaultStorageAdapter` interface the local-file adapter already uses. Prioritised by user demand — [open a GitHub issue](https://github.com/Flowdesktech/flowvault/issues/new) if one of these would unblock you.\n- PWA / offline mode\n- Signed build hashes + transparency log\n\n## Deployment \u0026 CI\n\nThe split is: **Vercel deploys the Next.js frontend automatically via its\nGitHub integration. GitHub Actions deploys the Firebase backend** — Cloud\nFunctions, Firestore security rules, and Firestore indexes — which Vercel\ndoes not touch.\n\nWorkflows in `.github/workflows/`:\n\n- `ci.yml` — runs on every PR and push: lint, TypeScript type-check, and\nproduction build for both the Next.js app and the Cloud Functions\nworkspace. Catches regressions before they reach either Vercel or\nFirebase.\n- `deploy-firebase.yml` — runs on pushes to `master` that touch\n`functions/`**, `firestore.rules`, `firestore.indexes.json`,\n`storage.rules`, or `firebase.json` (plus a manual\n`workflow_dispatch`). Builds the Functions, authenticates with a\nservice account, and runs\n`firebase deploy --only functions,firestore:rules,firestore:indexes,storage`.\n\nRequired repository **secrets** (Settings → Secrets and variables → Actions):\n\n\n| Secret                                     | Purpose                                                                                                                                                                                      |\n| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `FIREBASE_SERVICE_ACCOUNT`                 | Full JSON of a service account with roles: Cloud Functions Admin, Firebase Rules Admin, Firebase Storage Admin, Service Account User, Cloud Datastore Index Admin, and (first deploy only) Artifact Registry Writer. |\n| `NEXT_PUBLIC_FIREBASE_API_KEY`             | Public client config — also configured in Vercel, duplicated here so CI builds succeed.                                                                                                      |\n| `NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN`         | same                                                                                                                                                                                         |\n| `NEXT_PUBLIC_FIREBASE_PROJECT_ID`          | same                                                                                                                                                                                         |\n| `NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET`      | same                                                                                                                                                                                         |\n| `NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID` | same                                                                                                                                                                                         |\n| `NEXT_PUBLIC_FIREBASE_APP_ID`              | same                                                                                                                                                                                         |\n\n\nRequired repository **variables** (non-secret, Settings → Secrets and\nvariables → Actions → *Variables* tab):\n\n\n| Variable                 | Example                                     |\n| ------------------------ | ------------------------------------------- |\n| `FIREBASE_PROJECT_ID`    | `flowvault-prod`                            |\n| `NEXT_PUBLIC_APP_URL`    | `https://useflowvault.com`           |\n| `NEXT_PUBLIC_GITHUB_URL` | `https://github.com/Flowdesktech/flowvault` |\n\n\n**Vercel's GitHub integration handles the frontend automatically.**\nConnecting the repository to a Vercel project is enough:\n\n- Push to `master` \u0026rarr; production deploy at the canonical domain.\n- Push to any other branch, or open a PR \u0026rarr; preview deploy at a\n  `*.vercel.app` URL.\n- `NEXT_PUBLIC_*` env vars live in Vercel's project settings and are\n  injected at build time.\n\nNothing in this repo needs to push to Vercel, so no `VERCEL_TOKEN` /\n`VERCEL_ORG_ID` / `VERCEL_PROJECT_ID` secrets are required in GitHub.\nThe Firebase workflow above is the only deploy this repo owns.\n\n(If you ever want to drive Vercel from Actions instead \u0026mdash; for\nexample to keep all build logs in one place \u0026mdash; the commands are\n`vercel pull --environment=production`, `vercel build --prod`, and\n`vercel deploy --prebuilt --prod`. Plain `vercel deploy` without\n`--prod` produces a preview, not a production release.)\n\n## Support Flowvault\n\nFlowvault is built on the honor system. We don't show ads, sell data, or ask\nfor your email — by design, not oversight. Those are the usual ways an app\npays for itself, and all of them conflict with being zero-knowledge.\n\n### Crypto donations via NOWPayments\n\nDonations go through the [NOWPayments](https://nowpayments.io) donation\nwidget, embedded on `/donate`. We picked it because it's one of the\nvery few processors where **a donor can contribute without creating an\naccount or providing an email** — receipts are optional, only generated\nif the donor wants one. The widget also generates a fresh deposit\naddress per donation, so two donors can't cross-reference each other\non-chain.\n\n- ~100+ coins supported (BTC, ETH, LTC, XMR, USDT on TRC-20 / ERC-20, SOL, and many more)\n- Monero (XMR) available for donors who want amount + identity cryptographically hidden, not merely pseudonymous\n- No donor sign-up, no donor email required\n- Tor / VPN friendly on the donor side\n- Short / vanity link: `https://nowpayments.io/donation/flowdesktech`\n\nEvery traditional payment rail (cards, PayPal, Stripe) requires\nidentifying info to receive money. A processor with an anonymous\ndonation flow is the closest match to the rest of Flowvault's model.\n\n### Configuring the donation widget\n\nSet these in `.env.local` (they aren't secrets — both are embedded in\nthe public bundle and visible in the iframe `src`):\n\n```\nNEXT_PUBLIC_NOWPAYMENTS_API_KEY=d1809dbe-265d-44fc-af65-16cce1b7186b\nNEXT_PUBLIC_NOWPAYMENTS_DONATION_URL=https://nowpayments.io/donation/flowdesktech\n```\n\nIf you fork Flowvault and want donations to go to your own NOWPayments\naccount, replace both values with your own api key and vanity slug.\n\n### Non-crypto ways to help\n\nIf crypto is a non-starter for you, no pressure — these help just as much:\n\n- Use Flowvault and tell someone who'd benefit\n- Star the GitHub repository\n- File a thoughtful bug report or security issue\n- Submit a PR\n\n## License\n\nMIT. See [`LICENSE`](./LICENSE) for the full text.\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.shipit.buzz/products/flowvault?ref=badge\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003cimg src=\"https://www.shipit.buzz/api/products/flowvault/badge?theme=dark\" alt=\"Featured on Shipit\" /\u003e\u003c/a\u003e\n\u003c/p\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowdesktech%2Fflowvault","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflowdesktech%2Fflowvault","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowdesktech%2Fflowvault/lists"}