SecretGram encrypts message and file content in the browser before transmission. The Cloudflare service stores, orders, and relays ciphertext. The implementation uses standard Web Crypto primitives but has not been independently audited.
Do not describe SecretGram as anonymous, zero-trust, forward-secret, identity-verified, or compliant by default.
SecretGram attempts to protect:
- message text;
- filenames and declared MIME types;
- file contents;
- preview contents;
- room authentication and content-encryption material;
- message and file integrity against accidental corruption or storage tampering.
The confidentiality claim depends on all of the following:
- room codes are generated by the application using a functioning cryptographic random number generator;
- users exchange the complete room code through a trusted channel;
- browsers, operating systems, extensions, input methods, and clipboards are not compromised;
- the delivered application JavaScript is the reviewed version;
- Web Crypto implementations behave correctly;
- AES-GCM nonces are never reused under the same key;
- users leave the room or close the tab when finished.
The current design provides meaningful protection against:
- an honest-but-curious application operator inspecting stored data;
- an R2 or Durable Object storage disclosure containing ciphertext and protocol metadata;
- passive network observers behind correctly validated HTTPS;
- accidental ciphertext corruption;
- replayed message/file requests when the exact encrypted envelope is reused;
- conflicting retries that attempt to replace existing ciphertext.
The application JavaScript is the cryptographic trust root. An operator, account takeover, malicious deployment, compromised dependency, service worker, or injected script could read room codes and plaintext in the browser. End-to-end encryption cannot protect against malicious code running at an endpoint.
Use deployment access controls, reviewed builds, strict CSP, dependency review, and Cloudflare account MFA. Consider reproducible builds and an independently distributed verification client for higher-assurance deployments.
SecretGram cannot protect content from malware, browser extensions, screenshots, accessibility tooling, clipboard managers, crash dumps, swap, or a person with access to an unlocked device.
The room code is both the shared access credential and the root of decryption authority. Anyone who obtains it can join and decrypt retained room content. There is no individual membership list or member revocation. Create a new room after suspected disclosure.
The protocol does not ratchet keys. A room secret obtained later can derive message keys for retained ciphertext from all sender epochs visible in envelopes. Room expiration and ciphertext deletion reduce exposure but are not forward secrecy.
Display names, device UUIDs, and left/right message placement are presentation data, not cryptographic identities. Any participant with the room code can claim another display name. Verify people and room codes through an independent channel.
Message pinning is authorized only by the shared room credential. Any participant who can enter the room can replace or clear its single pin; pin actions do not prove a human identity.
Message recall removes the retained ciphertext from the room and distributes an ordered tombstone to active and reconnecting clients. It cannot erase plaintext that a recipient already copied, downloaded, screenshotted, cached outside the application, or observed through a compromised endpoint.
Recall authorization uses a random capability generated with the message. Its verifier is authenticated with the encrypted envelope and stored by the service; the raw capability stays in the active sending tab until it is presented to the recall endpoint. Reloading or leaving discards that capability, and older messages without a verifier cannot be recalled. Recall does not establish or authenticate a human identity.
Cloudflare and the operator can observe IP addresses, timing, request paths, room locators, message ordering, pinned message IDs and update times, ciphertext length, file chunk counts and sizes, connection counts, and operational logs. Traffic analysis may reveal relationships or content categories even without plaintext.
For stronger metadata protection, use an appropriate network anonymity layer and consider padding/batching. Neither is implemented here.
Source and room-device rate limits reduce accidental and low-cost abuse. They do not provide a full abuse platform, proof of work, CAPTCHA, account reputation, billing controls, or protection against a distributed adversary.
Because the server does not possess plaintext or file keys, it cannot perform server-side DLP, malware scanning, eDiscovery, legal hold, or content moderation. Enterprise deployments must choose and document a different trust model if these controls are required, such as endpoint scanning before encryption or explicitly managed organizational keys. Do not claim both strict service-blind E2EE and server-side plaintext inspection.
- Random input: 15 bytes from
crypto.getRandomValues() - Entropy: 120 bits
- Encoding: 24 Crockford-style Base32 data symbols
- Error detection: two CRC-10 symbols
- The checksum does not reduce entropy
- HKDF-SHA-256
- Versioned fixed salt
- Independent labels for locator, authentication, and message root
- Server verifier: SHA-256 of the derived authentication token
- AES-256-GCM with 128-bit authentication tags
- Random 128-bit sender epoch
- Per-epoch HKDF-derived sender key
- Monotonic counter nonce
- Versioned authenticated additional data
- Durable Object uniqueness constraint on sender epoch and counter
- Optional 256-bit recall capability with a SHA-256 verifier authenticated by the message envelope
- Ordered recall tombstones that replace retained message ciphertext
- Independent random 256-bit AES-GCM key per file
- Random 64-bit nonce prefix plus 32-bit chunk index
- 128-bit authentication tag per chunk
- Authenticated file/chunk context
- Cached ciphertext for exact retries
- SHA-256 ciphertext digest used for retry conflict detection
The digest is not a substitute for AES-GCM authentication. It lets the service compare opaque retry payloads without a decryption key.
Static responses set a CSP and related headers. The application uses system fonts and local assets only. Blob sources are allowed specifically for decrypted image and PDF previews. Decrypted PDFs are processed by the browser's native PDF viewer inside the endpoint trust boundary; no PDF content is sent to an external viewer or CDN by the application. No analytics or third-party runtime script is required.
A CSP reduces exploitation opportunities but does not make a malicious first-party deployment safe.
Production logs must never include:
- room codes or URL fragments;
- authorization tokens or WebSocket tickets;
- raw recall capabilities or recall verifiers;
- request bodies or encrypted message envelopes;
- filenames, MIME types, captions, or plaintext;
- full URLs containing query parameters;
- R2 object bodies.
Current structured error logs contain event type, request ID, method/path, and error category. Review any future observability change against this policy.
- Expired rooms are rejected logically before physical cleanup.
- Message history enforces the configured retention cutoff.
- Hourly alarms remove stale message rows and abandoned uploads.
- A pin stores only message metadata, does not extend retention, and clears when its message is recalled or expires.
- Room expiration removes the room's R2 prefix and SQLite data.
- Failed R2 cleanup is rescheduled.
- A bucket lifecycle rule should delete orphaned ciphertext independently.
Cloudflare alarms and R2 lifecycle actions are not exact-to-the-millisecond deletion mechanisms. Public retention wording should say that data becomes inaccessible at expiration and is then physically cleaned up.
An operator must explicitly choose and document:
- SSO and organization membership model;
- anonymous room creation policy;
- retention defaults and legal requirements;
- maximum file size for supported browsers;
- R2 location/jurisdiction and data residency commitments;
- client endpoint requirements;
- DLP, malware, eDiscovery, and legal-hold model;
- abuse reporting and content-governance process;
- external penetration testing and cryptographic review cadence;
- incident notification and support contacts.
Before each release:
- Run
npm run check. - Review the complete Worker and browser diff.
- Scan for secrets and accidental payload logging.
- Confirm CSP and security headers on the deployed URL.
- Verify two independent browser sessions can exchange and decrypt content.
- Verify tampered messages and chunks fail closed.
- Verify room expiration rejects reads even before cleanup.
- Verify R2 lifecycle rules and alarm cleanup.
- Review dependency advisories and generated Cloudflare binding types.
- Record the deployed Worker version and rollback point.
The deployment owner must publish a private security contact before inviting external users. Do not report sensitive vulnerabilities through a public issue containing room credentials, ciphertext samples tied to real users, account identifiers, or deployment tokens.