How Reborn Apps Protects Your Data - Security Overview
Zero Knowledge architecture, end-to-end encryption, and deliberate trade-offs - a detailed overview of how Reborn Apps protects your data.
Reborn Apps is a suite of PWA productivity applications (task management and notes) built on a True Zero Knowledge end-to-end encryption architecture. This post describes the security model, cryptographic design, and protective measures in place.
Zero Knowledge Architecture
Reborn Apps follows a strict Zero Knowledge principle: the server never has access to user data in plaintext. All encryption and decryption happens exclusively on the user’s device.
What the server knows
| Data | Visibility | Rationale |
|---|---|---|
| Username | Plaintext | Required for authentication and uniqueness |
| Password hash | Argon2id hash | Server verifies credentials; never sees the raw password |
| Encrypted master key | Opaque ciphertext | Stored for cross-device access |
| Cryptographic salt | Plaintext | Required for key derivation on the client |
| Record IDs and foreign keys | Plaintext UUIDs | Required for relational integrity, sync, and authorization |
| Timestamps | Plaintext | Required for delta sync and conflict resolution |
What the server never sees
- All user content: task titles, descriptions, note bodies, list/folder/tag names - stored as
*_encryptedfields (AES-GCM ciphertext) - Behavioral metadata: completion status, starred/pinned flags, due dates, reminders, tag associations - bundled into a single
metadata_encryptedfield per record - Device information: session device details are encrypted client-side (
device_info_encrypted) - No PII beyond the username - no email, phone, or any personally identifiable information
Deliberate privacy trade-offs
The server sees the structural graph (which tasks belong to which list, which notes belong to which folder) because it needs this for cascade operations, per-parent sync, and authorization. However:
- Tag associations are fully hidden. N-to-M tag relationships would expose a correlation graph that could fingerprint user behavior. Tag IDs are encrypted inside
metadata_encrypted- no join table exists on the server. - Tag filtering is client-side only, performed against a decrypted in-memory index after unlock.
Cryptography
| Purpose | Algorithm | Details |
|---|---|---|
| Password hashing | Argon2id (hash-wasm) | m=19456, t=3, p=1 |
| Key derivation | PBKDF2 (Web Crypto API) | 600,000 iterations, SHA-256 |
| Data encryption | AES-GCM | 256-bit keys, unique IV per operation |
| Master key wrapping | AES-GCM via PBKDF2-derived key | Master key generated locally, never transmitted in plaintext |
| Recovery codes | SHA-256 | 8 single-use codes (XXXXX-XXXXX format), hashed before storage |
| Token signing | HMAC-SHA256 (JWT) | With support for secret rotation via dual-key verification |
Key lifecycle
- Registration - a random master key is generated on the client, wrapped with a key derived from the user’s password (PBKDF2 600K), and the ciphertext is sent to the server alongside the Argon2id password hash.
- Login - the server returns the encrypted master key. The client derives the wrapping key from the password and decrypts the master key locally.
- Session - the decrypted master key is persisted in IndexedDB (survives browser/PWA restarts) and restored automatically on app load. The key is cleared only on explicit logout. This matches industry practice (Standard Notes, Bitwarden, Proton Mail) - in a Zero Knowledge architecture, client-side storage protection against local device access is out of scope; ZK protects against server-side attacks.
- Data operations - all encrypt/decrypt operations use the master key via AES-GCM. The key never leaves the device.
Authentication & Session Security
Authentication flow
- Username + password authentication with Argon2id server-side verification
- Optional two-factor authentication (TOTP-based)
- Account recovery via single-use recovery codes (no email/phone recovery by design)
Token management
- Short-lived access tokens (JWT) with JTI-based blacklisting on logout
- Refresh tokens delivered exclusively via
httpOnlycookies - never exposed in response bodies or accessible from JavaScript, eliminating XSS-based token theft - Refresh token rotation with family tracking - if a refresh token is reused (indicating theft), the entire token family is revoked
- Dual JWT secret support for zero-downtime secret rotation (
JWT_SECRET+JWT_SECRET_PREVIOUS)
Brute-force protection
- Per-username login lockout: 5 failed attempts trigger a 15-minute lockout window, covering both login and 2FA endpoints
- Per-user lockout on sensitive operations: change-password, 2FA disable, and account deletion all enforce attempt limits with automatic lockout
- 4-layer anti-bot protection on registration:
- Honeypot - hidden field rejected if filled
- Timing check - submissions faster than a human minimum are rejected
- Proof-of-Work (PoW) - client must solve a server-issued HMAC-SHA256 challenge before the server accepts the request (no third-party CAPTCHA, consistent with Zero Knowledge - no external tracking)
- Server-side signature verification - the PoW challenge is signed server-side and verified before solution is checked
- Constant-time responses on login and registration to prevent username enumeration via timing side-channels
- IP resolution hardening: right-to-left
X-Forwarded-Forparsing with configurable trusted proxies (RFC 1918 awareness) to prevent IP spoofing
Transport & Content Security
HTTP security headers
- HSTS:
Strict-Transport-Security: max-age=31536000; includeSubDomains(production only) - Content Security Policy: Nonce-based CSP (
mode: 'nonce') - eliminatesunsafe-inlinefromscript-src. Script sources restricted to'self','nonce-…', and'wasm-unsafe-eval'(required for Argon2id WASM). Additional directives:base-uri: 'self',form-action: 'self',object-src: 'none' - X-Frame-Options:
DENY- prevents clickjacking - X-Content-Type-Options:
nosniff- prevents MIME-type sniffing - Server identity hiding:
server_tokens offin nginx - prevents version disclosure - Request size limit: 1 MB maximum request body (enforced at both nginx and application level)
Authorization & input validation
- Ownership verification on every data endpoint - all CRUD operations filter by
user_idfrom the authenticated JWT; no endpoint relies on client-supplied user IDs (IDOR-safe by design, verified across all 47 API endpoints) - Auth middleware in both applications’ server hooks validates JWT on every protected request
- Centralized request validation using Zod schemas (
validateBody()) applied to all data endpoints (tasks, notes, folders, tags, subtasks, push subscriptions) - HTML output from Markdown rendering sanitized via DOMPurify with restricted URI schemes
Sync integrity
- Idempotency middleware on write endpoints prevents duplicate operations during offline sync retry
Client-Side Security
Offline-first storage model
- IndexedDB is the source of truth - the app works fully offline, syncing in the background when connectivity is available
- Separate IndexedDB databases per application (
Reborn_task_DB,Reborn_notes_DB) - prevents cross-app data corruption during schema upgrades - No optimistic UI updates - the interface reflects data only after confirmed writes
- Shadow indexes (decrypted copies of sort/filter fields like
is_completed,due_date) exist only in local IndexedDB for query performance - they are stripped before any server sync
Encryption Guard
All data leaving the client passes through a 3-layer encryption validation pipeline:
- Post-encrypt: validates ciphertext format (
iv:ciphertext) immediately after encryption - Pre-save: validates before writing to IndexedDB
- Pre-sync: validates before sending to the server
This defense-in-depth approach ensures that plaintext data cannot accidentally be persisted or transmitted due to an encryption failure.
Cross-app SSO
- Single sign-on between applications uses shared
localStorageon the same origin (behind a reverse proxy), not IndexedDB - preserving database isolation
Supply Chain & Repository Security
Build & dependencies
- Automated dependency auditing -
pnpm audit(a vulnerability scanner for installed libraries) runs on every CI build - Automated dependency updates via Renovate - a bot that opens pull requests with library version upgrades, covering both routine updates and security patches (Dependabot’s version-update feature is intentionally disabled to avoid duplicate PRs from two bots)
- Monorepo module boundaries enforced by Nx and ESLint - apps may only import from packages they explicitly declare as dependencies, preventing accidental cross-app coupling
- TypeScript strict mode across the entire codebase
- Docker production hardening: non-root containers (
su-exec node), no exposed database ports (the database is reachable only from inside the internal Docker network)
GitHub repository controls
Last verified: 2026-05-12
Beyond the code we write ourselves, the GitHub repository is configured so that every commit and every pull request passes through several automated checks before it can be merged. These controls apply regardless of who opens the pull request:
- CodeQL static analysis (a tool that inspects source code for known vulnerability patterns) - runs on every push and pull request, with an additional scheduled rescan. Findings rated High severity or above fail the merge check, blocking the pull request until the issue is resolved. Copilot Autofix (an AI-assisted remediation tool) is enabled and proposes patches for the detected issues.
- Dependabot alerts - vulnerability notifications (CVEs) for every library tracked by our manifests, with a custom rule preset to triage alert noise
- Dependabot malware alerts - a separate channel for packages flagged as malicious, independent of the CVE feed
- Secret scanning with push protection - GitHub scans every commit for known credential patterns (API keys, tokens, passwords) and rejects the push at upload time, not merely flagging it after the fact
- Code quality findings - GitHub’s standard quality analysis runs alongside CodeQL
- Private vulnerability reporting - external researchers can disclose findings through a private channel; see SECURITY.md
- Security advisories - when relevant, advisories are published so downstream consumers can react
- Audit trail on dismissed alerts - a finding cannot be silently closed; dismissing one requires submitting a request that stays in the audit log
Known Limitations & Transparency
In the spirit of transparency, these are deliberate trade-offs in the current architecture:
| Area | Status | Notes |
|---|---|---|
style-src: 'unsafe-inline' | Accepted | Required by SvelteKit’s inline style generation. CSS-based exfiltration is significantly harder to exploit than script injection. |
img-src: 'https:' in Notes | Accepted | Required for external images embedded in Markdown notes. Task app restricts to 'self' data: only. |
| JWT algorithm | HMAC-SHA256 | Asymmetric signing (ES256) is planned but not yet implemented. Acceptable for a single-server deployment. |
| Token blacklist | In-memory | Access token blacklist is not persisted across server restarts. Risk is low given the short token lifetime (15 minutes). Redis-backed persistence is planned for multi-instance deployments. |
| No email-based recovery | By design | This is a feature, not a limitation - it ensures the server holds no PII beyond the username. Users are responsible for securely storing their recovery codes. |
Reporting Security Issues
If you discover a vulnerability, please report it responsibly. See SECURITY.md in the repository for details, or open a private security advisory via GitHub.
This post describes the security posture as of April 2026. Full technical documentation is available in the project repository.
Updated 2026-05-12 - added a section on the GitHub repository security controls (CodeQL static analysis, Dependabot alerts, secret scanning with push protection). These controls were already active at the time of original publication but had not been described here.