A privacy-focused ephemeral messaging platform built for self-hosting. No accounts, no phone numbers, no logs — just encrypted rooms that disappear.
Cinderbox Chat was designed from the ground up to run on infrastructure you control. It deploys on any standard PHP/MySQL shared host with no build step, no Node.js, no Docker, and no CDN.
| Desktop | Mobile |
|---|---|
![]() |
![]() |
A public instance is available at cc.outros.net — free to use for testing and real conversations. No registration required.
- Text, image, and audio messages — send text, photos, and voice clips up to 2 minutes
- Message replies — reply to any specific message; a quoted snippet appears inline and clicking it scrolls to the original
- Single-view messages — content that can only be opened once; the recipient must be online to open it, and it is permanently wiped from their device after viewing
- Message deletion — delete a message for yourself only, or request deletion from all recipients' devices and track confirmation per recipient
- No accounts — identify yourself with a handle and a password, nothing more
- Ephemeral rooms — choose a retention period (1h, 6h, 12h, 24h) or create a permanent room; ephemeral rooms and all their messages are automatically purged
- Custom room names and avatars — names stay on your device only, never stored on the server
- Multiple rooms — manage several conversations simultaneously from a single interface
- End-to-end encryption — all message content is encrypted in the browser before it leaves your device, using AES-256-GCM with keys derived via PBKDF2 (200,000 iterations, SHA-256)
- Zero plaintext on the server — the server stores only ciphertext blobs it cannot read
- No identity correlation — your identity tag is
SHA-256(handle + room_id), making it impossible to correlate your presence across different rooms - Password never leaves the browser — the room password is used locally to derive the encryption key and is never transmitted
- Single-view offline protection — opening a single-view message requires a confirmed server sync first; disabling your network connection before tapping will result in a failure, not content exposure
- Crash-recovery for single-view — if the app is force-killed mid-open, a sentinel flag ensures the deletion acknowledgement is sent on the next launch
- Message deletion tracking — "Delete for everyone" sends a signed deletion request to each recipient and tracks confirmation; the sender retains a tombstone to audit who confirmed
- Dark and light themes — persisted per device
- Internationalisation — English and Brazilian Portuguese (pt-BR); language selector in the nav bar
- 5-state delivery ticks — 🕐 queued → ✓ server received → ✓✓ downloaded → ✓✓ (partial) viewed → ✓✓ all viewed
- Message Info modal — tap any outgoing message to see per-recipient delivery, view, and deletion status
- Context menu — right-click or long-press any message to reply, delete, or view message data
- Image compression — photos are automatically resized to 1000px and re-encoded as AVIF (with WebP/JPEG fallback) before sending
- Single-view toggle in image preview — the single-view toggle is available directly inside the image preview modal, so it can be set after selecting the image
- No CDN, no external requests — the entire frontend is a single self-contained HTML file
Cinderbox Chat deploys as a small set of static files alongside a single PHP script. There is no build step, no package manager, and no environment variables.
| File | Role |
|---|---|
index.html |
Full SPA frontend — all UI and client-side logic |
api.php |
Backend API — all database interactions |
sw.js |
Service Worker — offline shell and PWA update cycle |
manifest.json |
PWA manifest — enables "Add to Home Screen" |
icon.svg |
Application icon |
.htaccess |
HTTP→HTTPS redirect and HSTS header (Apache) |
index.html and api.php are the only files strictly required for the app to function. The remaining three enable PWA installation and the offline fallback shell.
- PHP 8.0+ with PDO and PDO_MySQL
- MySQL 8.0+ (or MariaDB equivalent)
- Any standard web host (shared hosting works fine)
-
Copy the files to your web root:
scp src/api.php src/index.html src/sw.js src/manifest.json src/icon.svg src/.htaccess user@yourhost.com:~/public_html/ -
Visit your site in a browser. A setup screen will appear asking for your MySQL credentials.
-
Submit the form.
config.phpis written automatically and the database schema is created. The setup endpoint is permanently disabled after first run.
That's all. No build step, no package manager, no environment variables.
scp src/api.php src/index.html src/sw.js src/manifest.json src/icon.svg src/.htaccess user@yourhost.com:~/public_html/Any new database migrations run automatically on the first request after deployment.
Important — bump the Service Worker version on every deploy that changes index.html. Open sw.js and increment the cache name (e.g. cinderbox-v4 → cinderbox-v5) before uploading. This triggers an automatic update cycle: the new SW activates immediately and all open tabs reload with the fresh version. Without this step, users on Android/iOS PWA may continue running the old version indefinitely.
The test suite uses Playwright and runs against a live instance of the app. Each test produces a self-contained evidence report with screenshots and step-by-step descriptions.
Node.js is required. Install dependencies once:
npm install
npx playwright install chromiumnpm test # run all tests against cc.outros.net
BASE_URL=https://your-instance.com npm test # run against a different instance
npm run test:headed # run with a visible browser windowEach passing test generates a directory under docs/testing_evidences/ containing a README.md report and one screenshot per step. These reports are committed to the repository as verifiable records of tested behaviour — only after all tests pass.
All evidence reports live under docs/testing_evidences/.
| Document | Description |
|---|---|
| Test 001 — Solo Full Workflow | End-to-end solo session: create room, send text and image, set profile, toggle theme and language, rename and delete room |
| Test 002 — Two Participants Workflow | Two-browser session: join notification, text exchange, single-view message, leave room, delete room |
| Test 003 — Audio Message Workflow | Two-browser session: record and send audio, receive audio player, room deletion detected by participant |
| Test 004 — Single-View Image Message | Two-browser session: send single-view image, open and wipe on recipient side, deletion ack confirmed |
| Test 005 — Message Deletion | Two-browser session: delete for everyone, tombstone on sender, message removed on recipient |
| Test 006 — Message Reply | Two-browser session: reply via context menu, quoted snippet rendered inline on recipient |
| Test 007 — Multiple Rooms | Single-browser session: create two rooms, verify message isolation, switch between rooms |
| Test 008 — Room Retention Policy | Single-browser session: create rooms with 1h, permanent, and 12h retention, verify settings panel |
| Test 009 — Join with Wrong Password | Two-browser session: client-side key validation failure, error shown, join screen remains |
| Test 010 — Profile Propagation | Two-browser session: profile_update broadcast, handle visible in participant list on recipient |
| Test 011 — Clear Data | Single-browser session: send message, clear all device data, verify landing screen and clean localStorage |
| Property | Implementation |
|---|---|
| Encryption algorithm | AES-256-GCM |
| Key derivation | PBKDF2, SHA-256, 200,000 iterations |
| Key material | Room password (never transmitted) |
| Identity tag | SHA-256(handle + room_id) — per-room, non-correlatable |
| Server knowledge | Ciphertext only — no plaintext, no metadata, no IP logs |
| SQL injection | PDO prepared statements throughout, no exceptions |
| Delete tokens | Stored as SHA-256 hash, verified with hash_equals |
| Error disclosure | error_reporting(0) — no stack traces or error output |
| Rate limiting | 60 messages per sender tag per minute |
| Message size | 2 MB hard limit enforced server-side |
| Setup endpoint | Disabled permanently after first run (returns 403) |
| Config file | Excluded from git via .gitignore |
| XSS — received content | Avatars validated to data:image/ prefix; all user-controlled values HTML-escaped before DOM insertion |
| Document | Description |
|---|---|
| User Manual | End-user guide: rooms, messaging, single-view, profiles, notifications |
| Self-Hosting Guide | Deployment, database setup, Service Worker versioning, reverse proxy config |
| Architecture | Sync model, storage layers, message flow, presence model, ACK system |
| Encryption | Cryptographic primitives, key derivation, threat model |
| Client Database | IndexedDB schema, localStorage keys, outbox structure |
| Server Cleanup | Server-side expiry routines: lazy expiry, global expiry, inbox delivery |
See LICENSE.

