A private way to say what matters.
Locknote is a zero-knowledge, self-destructing note-sharing application. It encrypts each note in the browser before upload, stores encrypted envelopes only, and gives the sender control over expiry, burn-on-read delivery, dead switches, file sharing, receipts, and remote withdrawal.
Locknote is documented and tested as a complete submission rather than a visual prototype. The material below maps directly to the evaluation criteria.
| Evaluation criterion | Review-ready evidence |
|---|---|
| Problem Understanding & Core Functionality | Browser-side encryption, fragment-keyed sharing, burn-on-read, expiry, remote withdrawal, encrypted files, optional encrypted recipient replies, and delivery receipts. |
| Innovation & Meaningful Differentiation | Proof-based delivery acknowledgements, private one-use encrypted-file leases, opt-in encrypted replies, Guardian Wipe threshold revocation, dead switches, seal fingerprints, and pre-seal realtime collaboration. |
| Technical Implementation & Architecture | React/Vite client, Web Crypto API, Express/Zod/Helmet API, Supabase Auth and owner-only RLS account tables, Vercel functions, static CSP, and typed tests. |
| User Experience & Accessibility | Intentional compose-to-share workflow, theme support, session-aware protected routes, keyboard command palette, skip link, semantic controls, responsive views, actionable errors, and automated axe checks. |
| Performance & Reliability / Demo Quality | Health/version readiness, request IDs and safe timing telemetry, validation, rate limits, protected maintenance, bundle budget, live smoke test, and automated CI. |
| Documentation & Explanation | This README plus dedicated evaluation, demo, architecture, security, API, testing, comparison, and environment guides. |
Start with the Evaluation Guide for a rubric-by-rubric explanation, then use DEMO.md for a repeatable evaluator walkthrough.
Locknote’s private-correspondence interface in light and dark themes.
After GitHub sign-in through Supabase Auth, Locknote presents a personalized identity view with the provider avatar, username, email, an opt-in private account bio, and a route to the user's browser-local vault. The bio and contact usernames are protected by owner-only Supabase row-level security; the vault summary keeps sender controls and tracked link status easy to find without creating a server-side plaintext archive.
The delivery screen makes the privacy model understandable at the point of sharing. It provides a copyable private link, policy badges, a human-verifiable seal fingerprint, browser-native sharing where available, a remote-withdrawal control, and a QR code for opening the full link on another intended device.
The QR code represents the full private share URL, including its fragment-held decryption material. Treat it with the same care as the copied link and show it only to the intended recipient.
For a detailed feature-by-feature explanation and privacy boundary, see the Feature Guide.
flowchart LR
A[User selects\nContinue with GitHub] --> B[Supabase Auth\nstarts OAuth]
B --> C[GitHub\nverifies identity]
C --> D[Supabase callback\ncreates browser session]
D --> E[Locknote /auth/callback\nexchanges PKCE session]
E --> F[Private account profile\nprovider identity + opt-in bio]
F --> G[Browser-local vault\ntracked links, receipts, withdrawal]
B -. GitHub client secret remains in Supabase .-> H[Supabase provider settings]
G -. plaintext never added to vault records .-> I[Encrypted envelope lifecycle]
classDef trusted fill:#e8f5ef,stroke:#247a56,color:#173f30,stroke-width:1.5px;
classDef local fill:#f4edff,stroke:#7958a8,color:#3e275e,stroke-width:1.5px;
classDef external fill:#fff4dd,stroke:#b7791f,color:#6d4508,stroke-width:1.5px;
class A,F,G local;
class B,D,H trusted;
class C external;
class E,I trusted;
This flow keeps responsibilities clear: GitHub verifies identity, Supabase manages OAuth, the browser session, and owner-only profile/contact rows, and Locknote presents a browser-local capability vault without storing note plaintext, keys, share URLs, or owner tokens in a profile record.
| Resource | Link |
|---|---|
| Live application | https://lock-note-sigma.vercel.app/ |
| Video walkthrough | Watch the final evaluator walkthrough on Google Drive — a 2:47 narrated walkthrough of the live product, GitHub evidence, and Supabase schema. |
| Evaluator demo script | docs/DEMO.md |
| Rubric evidence guide | docs/EVALUATION.md |
| Local application | http://localhost:5173 |
| Local API health check | http://localhost:3001/api/health |
The live application is deployed on Vercel. The screenshots above show the supplied light and dark homepage views.
| Capability | Description |
|---|---|
| Browser-side encryption | The client encrypts content with AES-256-GCM before calling the API. The API persists ciphertext, salt, IV, and non-secret delivery metadata only. |
| Fragment-keyed links | The decryption key lives after # in the share URL. Browsers do not send URL fragments in HTTP requests, so the server never receives the key. |
| Passphrase gate | A sender can add an extra passphrase-based key derivation layer before sharing. |
| Burn after reading | A note can be consumed exactly once; a successful non-owner read makes further reads unavailable. |
| Expiry and dead switches | Notes can expire at a set time or disappear after an inactivity window. |
| Remote withdrawal | The owner capability lets a sender delete a still-active note and its encrypted file blob. |
| Private encrypted-file delivery | Files are encrypted in the browser. The Storage bucket is non-public; only a successful consume receives a 60-second, one-use API lease for ciphertext bytes. |
| Verified delivery receipts | A receipt is recorded only when a browser successfully decrypts a random proof authenticated inside a version-two envelope. The server stores only the proof hash, never the raw proof or plaintext. |
| Encrypted recipient replies | A sender may opt in to short voluntary recipient replies. The raw reply capability is inside the authenticated envelope; the browser encrypts each reply under a dedicated AES-GCM AAD domain, while the API stores only opaque ciphertext and a hash-only capability verifier. Owner capability is required to retrieve replies. Replies are unavailable for burn-after-read notes and do not authenticate a human identity. |
| Guardian Wipe | A sender can create 2-of-3 through 5-of-5 trustee cards. A quorum may withdraw the server copy, but a single card—or every card—cannot decrypt the note or reveal its key. |
| Realtime collaboration drafts | Temporary draft rooms use Supabase Realtime and are sealed or automatically purged after inactivity. |
| GitHub sign-in | GitHub OAuth is handled by Supabase Auth; GitHub credentials stay in Supabase provider configuration, never in the browser bundle or API source. |
| Private account profile | Authenticated users see provider identity, avatar, username, email, and an optional ≤160-character bio saved through owner-only Supabase RLS. No secret content or key material is stored there. |
| Browser-local vault | The dashboard keeps links created in the current browser available for copying, receipt checks, and withdrawal without becoming a server-side plaintext archive. |
| Private vault contacts | Each authenticated user can save/remove private GitHub username shortcuts through owner-only RLS. Contacts are not a directory, share recipient, or decryption-permission system. |
| Accessibility safeguards | Axe-backed public-route checks, keyboard command-palette behavior, a skip link, visible focus treatment, and reduced-motion-aware interaction provide measurable accessibility evidence. |
| Production hardening | Static CSP and companion security headers, request IDs, safe timing telemetry, a JavaScript bundle budget, pull-request CI, and a scheduled public smoke check protect release quality. |
| QR-assisted delivery | The sealed-delivery card renders the full private link as an accessible QR code for intentional cross-device transfer. |
| Seal fingerprints | Human-friendly word and glyph fingerprints can be compared with a recipient out of band before opening a sensitive note. |
Locknote is designed around one boundary: the server can store an encrypted envelope, but it must not receive the decryption key.
Browser
├─ generates encryption material
├─ encrypts note or file locally
├─ sends ciphertext + delivery metadata ──────────┐
└─ shares key only in URL fragment (#...) │
▼
Locknote API / Supabase
stores ciphertext only
The encrypted envelope includes ciphertext, a public 32-byte salt, IV, fixed KDF configuration, and delivery metadata. New version-two envelopes also carry a random delivery proof inside authenticated ciphertext; only its SHA-256 verifier is stored. When the sender enables encrypted replies, a separate random reply capability also stays inside the envelope while the service retains only its SHA-256 verifier. The service-role key is used only by server-side API functions to write and clean up encrypted records, private storage objects, opaque replies, and audit events. It must never be exposed through a VITE_ variable or committed to source control.
Guardian Wipe is revocation, not recovery. The browser splits a separate random wipe capability, not the encryption key. Guardian shares are checksum-protected and paste-bound; the service accepts only the reconstructed capability verifier. Guardians cannot open, decrypt, or recover the note.
Locknote improves private delivery; it does not protect a compromised sender or recipient device, an exposed share URL, or availability failures of the hosting provider. Use a trusted channel to send links and compare the note fingerprint out of band when assurance matters.
Lock Note cannot erase plaintext a recipient has already copied, downloaded, photographed, or screen-captured. A verified delivery receipt proves that a browser with the encrypted proof successfully opened the envelope; it does not prove human comprehension. An encrypted reply is voluntary and does not authenticate a recipient identity; anyone holding the full link may be able to send or read one. Guardian Wipe revokes future server delivery but cannot retract a recipient copy. Collaboration rooms remain a temporary pre-seal workspace, not end-to-end encrypted co-editing. Planned work is limited to a staging-only load report, an optional independent security review, and user-recorded accessibility/demo evidence—rather than adding server-side access to plaintext or keys.
Realtime collaboration is a pre-seal drafting feature, not an end-to-end encrypted co-editing protocol. Draft content is synchronized through Supabase before the owner seals it into an encrypted Locknote envelope. Treat a collaboration room as a temporary workspace and do not enter material that must remain zero-knowledge until it has been sealed and shared as a note.
| Layer | Technology | Responsibility |
|---|---|---|
| Client | React 19, Vite, TypeScript, Tailwind, Motion | Encrypt/decrypt content, render the editor, manage share links, and use Supabase Auth/Realtime. |
| API | Express 5, Zod, Helmet, rate limits | Enforces fixed KDF policy, issues private one-use encrypted-file leases, validates proof/reply/Guardian verifiers, manages lifecycle operations, and emits privacy-safe timing/correlation metadata. |
| Persistence | Supabase Postgres, private Storage, Realtime, Auth | Stores ciphertext, hash-only capability verifiers, private encrypted file blobs, opaque encrypted reply envelopes, temporary collaboration drafts, and owner-only profile/contact metadata. It never stores note keys, raw proofs/capabilities, guardian shares, or plaintext. |
| Hosting | Vercel | Builds the Vite SPA, serves Express API functions under /api, and calls the protected daily maintenance function. |
locknote/
├── api/ # Vercel function entrypoints
│ ├── index.ts # /api
│ ├── [...path].ts # /api/*
│ └── maintenance/purge.ts # protected daily reconciliation
├── client/ # React + Vite application
├── server/ # Express API and storage abstractions
├── docs/
│ ├── assets/ # README screenshots
│ ├── EVALUATION.md # rubric-aligned evaluator evidence
│ ├── DEMO.md # three-to-five-minute walkthrough
│ ├── ENVIRONMENT.md # local, Vercel, OAuth, and submission setup
│ └── sql/ # Supabase bootstrap and RLS hardening migrations
├── vercel.json # Vite output, SPA routing, headers, cron
├── .env.example # safe local configuration template
└── .env.submission.template # copyable private-submission template
The repository is ready for code review and a live evaluation. Before submitting, replace the optional video placeholder above, confirm the live link opens, confirm the Quality gate workflow is green, and follow the submission checklist for private environment values.
| Submission item | Repository location |
|---|---|
| Rubric-by-rubric explanation | docs/EVALUATION.md |
| Three-to-five-minute demo script | docs/DEMO.md |
| Architecture and trust boundary | docs/ARCHITECTURE.md and docs/SECURITY.md |
| API, changelog, and test evidence | docs/API.md, CHANGELOG.md, and docs/TESTING.md |
| Safe environment templates | .env.example and .env.submission.template |
| Live deployment | https://lock-note-sigma.vercel.app/ |
Install Node.js 20 or newer, npm, and create a Supabase project. A GitHub OAuth application is only required if you want GitHub sign-in.
node --version
npm --versiongit clone https://github.com/Simondavid07/Lock_NOTE.git
cd Lock_NOTE
npm installcp .env.example .envOn Windows PowerShell:
Copy-Item .env.example .envFill the following values in .env:
SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your_server_only_service_role_key
VITE_SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
VITE_SUPABASE_ANON_KEY=your_browser_safe_publishable_or_anon_key
CORS_ORIGINS=http://localhost:5173
PORT=3001The SUPABASE_SERVICE_ROLE_KEY is server-only. Do not put it in a VITE_ variable and do not commit .env.
For a new project, open Supabase Dashboard → SQL Editor, paste the complete contents of docs/sql/001_init.sql, and run it once. For an existing Locknote project, apply the migrations in numerical order through docs/sql/007_encrypted_recipient_replies.sql. These migrations are idempotent and create or harden:
public.pastesfor encrypted note envelopes;public.draftsfor short-lived collaboration rooms, with database access restricted to the server-side API while Realtime Broadcast and Presence handle ephemeral collaboration;public.eventsfor privacy-safe lifecycle audit events;public.paste_repliesfor bounded opaque encrypted recipient-reply envelopes, accessible only through server-side service-role operations and removed when their parent note is deleted;- the private
secretsbucket for encrypted file blobs, accessed only through a short-lived server-issued lease; and - recurring database cleanup for expired notes and old drafts.
The
secretsbucket is private by design. Even though objects are ciphertext, the recipient must first complete the envelope lifecycle to receive a 60-second one-use API lease; a Storage path is never included in public metadata or consume responses.
npm run devThis starts the Vite application at http://localhost:5173 and the Express API at http://localhost:3001. Vite forwards local /api requests to the Express server automatically.
Locknote uses Supabase Auth for GitHub OAuth. This is safer and simpler than exchanging GitHub codes in the application API.
-
In GitHub, create an OAuth App at GitHub Developer Settings.
-
In Supabase Dashboard → Authentication → Providers → GitHub, copy the supplied callback URL. It has this form:
https://YOUR_PROJECT_REF.supabase.co/auth/v1/callback -
Set that value as the GitHub OAuth App Authorization callback URL.
-
Enter the GitHub Client ID and Client Secret in the Supabase GitHub provider settings, enable the provider, and save.
-
In Supabase Dashboard → Authentication → URL Configuration, set the production Site URL and allow these redirects:
http://localhost:5173/auth/callback https://lock-note-sigma.vercel.app/auth/callback
The app asks Supabase to redirect to /auth/callback, exchanges the PKCE session code in the browser, and persists the resulting Supabase session. GitHub credentials do not belong in .env, VITE_ variables, or Vercel environment variables. Follow the official Supabase GitHub provider and redirect-URL guidance when configuring the provider. 1 2
Locknote is configured to deploy as one Vercel project:
client/distis the static Vite output;/apiand/api/*are Express-backed Vercel functions;- SPA deep links resolve to
index.html; - API responses receive no-store and MIME-sniffing protections; and
/api/maintenance/purgeis a protected daily reconciliation job.
In Vercel, import the repository and keep the repository root as the project root. The checked-in vercel.json supplies the build command, output directory, SPA rewrite, headers, and cron schedule.
Add these values for Production and Preview as appropriate:
| Variable | Required | Exposure | Value |
|---|---|---|---|
SUPABASE_URL |
Yes | Server only | Supabase project URL. |
SUPABASE_SERVICE_ROLE_KEY |
Yes | Server only | Service-role key. Never expose to the browser. |
VITE_SUPABASE_URL |
Yes | Browser build | Same project URL. |
VITE_SUPABASE_ANON_KEY |
Yes | Browser build | Supabase publishable/anon key. |
CRON_SECRET |
Yes | Server only | Random string of at least 16 characters for maintenance authorization. |
CORS_ORIGINS |
Optional | Server only | Only needed for a separately hosted frontend; same-project /api calls are same-origin. |
VITE_API_BASE |
Optional | Browser build | Leave empty for the same Vercel project; set only for a separate API host. |
Vite exposes only variables prefixed with
VITE_to the browser bundle. Treat everyVITE_value as public. The Vercel API runtime rejects missing Supabase server credentials instead of silently falling back to ephemeral in-memory storage.
Deploy from the Vercel dashboard or with the Vercel CLI. Once the deployment URL is available, run the live smoke test against it:
API_URL=https://lock-note-sigma.vercel.app npm run test:liveVercel triggers the maintenance endpoint at 03:17 UTC daily. The endpoint checks CRON_SECRET, then performs an idempotent cleanup pass for expired records, stale drafts, and orphaned encrypted file blobs. Vercel cron requests use a protected Authorization header when CRON_SECRET is configured. 3
Vercel deploys Express applications as functions and supports Vite static builds; its SPA rewrite pattern is used here so direct links such as /paste/:id and /auth/callback work after a refresh. 4 5
Run these commands before considering a deployment ready:
# Validates server, client, and Vercel function TypeScript entrypoints
npm run typecheck
# Builds the Express server and Vite client
npm run build
# Runs unit and integration coverage
npm run test
# Runs the API lifecycle smoke test against local API or API_URL
npm run test:liveThe smoke test checks the health endpoint, encrypted-note creation, safe metadata responses, owner preview, burn-after-read behavior, delivery receipts, draft sealing, and remote withdrawal. Before releasing, confirm that GET /api/health reports ok: true and store: "supabase"; a local memory store or an unhealthy response is not production-ready.
| Command | Purpose |
|---|---|
npm run dev |
Starts Vite and the Express API together. |
npm run dev:client |
Starts only Vite on port 5173. |
npm run dev:server |
Starts only Express on port 3001. |
npm run typecheck |
Type-checks server, client, and api/ Vercel functions. |
npm run build |
Builds server and client production artifacts. |
npm run test |
Runs server and client test suites. |
npm run test:live |
Runs the lifecycle smoke test against API_URL or localhost:3001. |
npm run test:e2e |
Runs the optional end-to-end workspace suite. |
npm run demo |
Runs the server demo workflow. |
The API reference lives in docs/API.md. For operations, use GET /api/health to check that the deployed function can reach Supabase. A healthy production response should report store: "supabase"; do not treat the local in-memory development fallback as a deployable persistence layer.
Before sharing a production URL, test these user journeys manually:
- Seal and open a text note from a second browser session.
- Confirm a burn-on-read note returns unavailable after the first non-owner open.
- Upload and retrieve an encrypted file note.
- Sign in with GitHub and return to
/dashboard. - Create and seal a collaboration draft.
- Run the live smoke test against the production URL.
| Document | Purpose |
|---|---|
| Documentation index | Quick map of every evaluator and implementation document. |
| Evaluation guide | Rubric-aligned evidence for problem fit, innovation, architecture, UX, reliability, and documentation. |
| Demo guide | A concise evaluator walkthrough and troubleshooting sequence. |
| Environment guide | Safe local, Vercel, OAuth, and submission configuration instructions. |
| Submission checklist | Final reviewer, deployment, and secret-safety checks before handoff. |
| Feature guide | Visual product tour of GitHub profile, vault, QR delivery, fingerprints, and lifecycle controls. |
| Architecture guide | Service boundaries, data flow, and component overview. |
| Security and threat model | Cryptographic protocol details and residual risks. |
| API reference | API operations, payloads, and lifecycle behavior. |
| Testing guide | Test coverage and verification guidance. |
| Supabase bootstrap migration | Database, Storage, Realtime, RLS, and cleanup setup for new projects. |
| Supabase RLS hardening migration | Removes legacy anonymous direct access to collaboration drafts. |
This project is licensed under the MIT License.



