Serein is a cross-platform messaging application with web account and conversation interfaces, plus native account clients. The backend has an authenticated, quota-bounded opaque-ciphertext chunk-transfer API: resumable/idempotent uploads, per-chunk SHA-256 integrity checks, seven-day expiry, and per-device encrypted manifest envelopes for the sender's and recipient's currently active devices. It stores no filenames or plaintext manifest. This is transport plumbing only: no client currently creates/decrypts those envelopes. Browser message encryption uses a shared passphrase and is not an audited end-to-end protocol. The web client has registration/OTP, account, contact, and message-history source integration; Android/iOS have OTP/account/device-session source flows but no native encrypted messaging or media. Email provider delivery, compilation, and end-to-end verification remain unverified. MAIL_DELIVERY=disabled is the default and prevents registration.
The repository also contains a first-party, dependency-free Rust media track/object routing core and a separate WebTransport/QUIC echo spike. The routing core is not an MOQT wire implementation and the echo server is not a call service. WebRTC has not been added. CI and gated release workflows are under .github/workflows/; no release artifacts have been verified.
Security status:
VERSIONis an unverified development snapshot, not a production secure messenger. Passwords are hashed with Argon2id and session tokens are opaque, random, and stored hashed server-side. These measures do not make the service production-ready. The browser's shared-passphrase AES-GCM scheme has no verified identity, audited ratchet, or forward secrecy. Do not use it for sensitive communications.“Three layers” should mean independent protections—not three custom ciphers stacked on one payload: (1) authenticated TLS in transit, (2) a reviewed client-side E2EE protocol with authenticated device keys, and (3) OS-backed, device-local key/storage protection. Only TLS/session handling is established; the client E2EE/key lifecycle and native storage protections still need implementation and review. Extra nested encryption does not substitute for authenticated key agreement or forward secrecy.
A server cannot reliably identify an “original” client from User-Agent, app version, Origin, CORS, or a hard-coded app secret; Postman can imitate these, and modified clients can extract embedded secrets. The server must treat every API as public and enforce authentication, authorization, validation, abuse limits, and audit controls. Origin checks/CSP added here are browser defense-in-depth, not a Postman block. Genuine mobile-app attestation requires platform verification (for example Play Integrity/App Attest), server-side nonce/signature validation, provider credentials, and testing on signed production builds. Web clients have no universal tamper-proof attestation. None of those attestation integrations is configured yet.
Privacy controls added in source include account-scoped contact requests (generic response for matching/non-matching/ineligible verified accounts), a per-account discovery limit, request consent, opt-in email discoverability (off by default), presence visibility, and blocking. They are defense-in-depth only; the discovery limit is per account (not a trusted edge/IP limit), timing/abuse testing remains necessary, and none of these changes has passed a database/API build test.
Requirements: Rust stable, Docker (or PostgreSQL 14+), and a modern browser.
docker compose up -d postgres
cd backend
DATABASE_URL=postgres://serein:dev-only-change-me@localhost:5432/serein LISTEN_ADDR=0.0.0.0:8080 COOKIE_SECURE=false cargo runEmail verification is deliberately disabled by default. To enable registration, configure exactly one provider before starting the API:
- HTTPS mail API:
MAIL_DELIVERY=api,MAIL_API_URL=https://api.resend.com/emails(or a compatible HTTPS endpoint),MAIL_API_KEY=…, andMAIL_FROM='Serein <no-reply@your-domain.example>'. The API key is sent only as a bearer header to that configured HTTPS origin; redirects, URL user-info/query strings, automatic proxy discovery, response-body logging, and provider error-body logging are disabled. - SMTP:
MAIL_DELIVERY=smtp,SMTP_HOST=…,SMTP_TLS_MODE=starttlswith port 587 (default) orimplicitwith port 465,MAIL_FROM=…, and optionally bothSMTP_USERNAMEandSMTP_PASSWORD. Plaintext SMTP is rejected.
Configure verified sender/domain DNS and provider credentials outside source control. Use a provider key scoped to sending from this domain, place it in the server's secret manager/environment (never the browser/mobile config), and rotate it if it may have appeared in logs or shell history. The server sends it only to the configured provider; it remains in server process memory while running. Only then open http://localhost:8080, create an account, retrieve the six-digit code from email, and verify it before sign-in. Codes are hashed, expire after ten minutes, allow five guesses, and have a 60-second resend delay with a per-account daily send cap. These controls are not a substitute for edge/IP rate limiting. The provider setup and email receipt have not been verified here. Registration requires at least eight Unicode characters and no more than 1024 UTF-8 bytes; sign-in does not apply the registration minimum. /status shows sanitized API, database, web-interface, and email-provider configuration states; /healthz is the machine-readable readiness check. Email status indicates configuration only, so test by registering with an inbox you control and verifying the six-digit code. To test messaging, create a second verified account in a separate browser profile, add each account by username or verified email address, and use the conversation service. Do not treat the shared-passphrase encryption as secure.
For a static frontend hosted separately from the API, set frontend/web/config.js's apiBase to the API base URL, configure ALLOWED_WEB_ORIGINS on the API to the exact origin(s) of the browser page (scheme, host, and non-default port—not just the API host), use HTTPS/WSS, and keep the frontend/API on the same site (for example, sibling subdomains) so the secure Lax session cookie is sent. The API now rejects disallowed Origin headers and emits a CSP plus common security headers on API responses and when it serves the bundled web UI. If the web UI is hosted separately, configure equivalent document security headers there too. This is browser defense-in-depth, not client attestation. Cross-site cookie behavior still needs deployment testing. The web UI uses Rust/WASM username validation when frontend/web/serein_core.wasm is built and present; otherwise the local JavaScript fallback is used. CI builds and includes the WASM file in its web artifact.
- Before pushing, keep
.env, provider credentials, Android keystores, Apple certificates/profiles, and all user data out of Git. Only rootREADME.mdis tracked Markdown; the complete source ZIP includes ignored documentation. This checkout has noCargo.lock; after installing Rust 1.85+, generate/review one withcargo generate-lockfile, then runcargo check --workspaceandcargo test --workspacebefore committing it. - Push
mainto your GitHub repository and wait for Actions → Checks and mobile builds to pass. CI uploads the static web app, an Android debug APK plus an unsigned release APK, and an iOS Simulator app zip. Use onlyapp-debug.apkfor sideload testing. The simulator.appzip is not an iPhone IPA. - For local web/API testing, use Docker Compose PostgreSQL and the local-only settings in
.env.example. Copy it to ignored.env, then configure a real test-mail provider:MAIL_DELIVERY=disableddeliberately prevents registration. From the repo root rundocker compose up -d postgres; in a second terminal runcd backend,set -a; source ../.env; set +a, thencargo run. Openhttp://localhost:8080and test two separate browser profiles/accounts. Do not run with production data or use the current shared-passphrase encryption for sensitive chats. Detailed checks and the manual smoke-test command are in the ignoreddocs/TESTING_GUIDE.mdand in the complete source ZIP. - After web/API tests, Android debug installs on API 26+; the CI debug APK is signed with a temporary debug key, not a Play signing key. Android/iOS native messaging is not connected yet. Native apps require a reachable HTTPS API; local
localhoston a phone refers to the phone itself. - For physical iOS device testing, the new manual iOS Ad Hoc test IPA workflow requires Apple signing secrets and an unexpired Ad Hoc profile containing the test devices' UDIDs. It creates a test IPA without the stable-release approval gate. The existing
release.ymlremains separately gated and only accepts a stableX.Y.Zversion/tag; it creates signed Android release outputs and App Store Connect/Ad Hoc iOS IPAs but does not upload to stores.
A green CI run is not a product/security sign-off: the PostgreSQL/mail smoke test is manual, the Rust/mobile builds have not been run in this environment, and native messaging, audited E2EE, attachments, and calls remain incomplete. See BUILDING.md (in the complete archive) for artifact and signing details.
POST /api/v1/transfers creates a transfer using an account UUID, chunk count/total ciphertext bytes, and exactly one {device_id, iv, ciphertext} encrypted manifest envelope for every active device of both participants. The sender must be authenticated; the recipient must be a verified contact (or the same account). Upload each chunk with PUT /api/v1/transfers/{id}/chunks/{index}, Content-Type: application/octet-stream, and X-Chunk-SHA256: <lowercase-or-uppercase hex SHA-256>. A transfer becomes visible to its devices only after all declared chunks arrive and the aggregate byte count matches. GET /api/v1/transfers lists visible transfers; GET /api/v1/transfers/{id}/chunks/{index} downloads a chunk; POST /api/v1/transfers/{id}/ack acknowledges it; the originating device may DELETE /api/v1/transfers/{id} to abort.
Limits are 256 KiB per chunk, 512 chunks / 64 MiB per transfer, 32 active participant devices per transfer, 100 live transfers and 256 MiB of active transfer data per participant, and a seven-day expiry. Upload is resumable and byte-identical repeats are idempotent. SHA-256 is a transport checksum only—not an authenticated E2EE integrity proof. A future client must encrypt the file and manifest locally, include the expected chunk hashes inside the encrypted manifest, verify those hashes and AEAD tags before opening content, and encrypt a manifest/key envelope separately for every device. The API validates envelope shape and device authorization but cannot prove ciphertext is actually encrypted or decrypt it.
frontend/web/— static HTML/CSS/JavaScript; no npm runtime packages. Includes first-partyserein-avatarandserein-buttonWeb Components.crates/serein-core/— first-party zero-dependency Rust domain validators and C/WASM ABI; used by the API and the browser's optional WASM validator. Includes an allocation-free packet framing helper, not a secure transport or integrated network protocol.crates/serein-moq-engine/— first-party media-track/object router with per-subscriber and aggregate queue bounds; transport-agnostic, not interoperable MOQT on the wire.design/tokens.json+scripts/generate-design-tokens.py— single source for web CSS, Android Java, and iOS Swift design tokens.backend/— Rust account/session/contact/device APIs, OTP email delivery over HTTPS API or TLS SMTP, authenticated WebSocket/HTTPS message relay, PostgreSQL ciphertext history, and an opaque encrypted attachment-chunk transport. Transfer clients and call media/signaling are not integrated. It uses the shared Rust core. Startup creates/updates schema directly; SQL migrations are not automatically applied.media-transport-spike/— older isolated WebTransport/QUIC datagram echo experiment; not MoQ and not a calling service.apps/android/— native Java client with HTTPS auth, contacts, settings, and device-session API wiring; messaging/calls are not connected.apps/ios/— native SwiftUI client with URLSession auth, contacts, preferences, and device-session API wiring; messaging/calls are not connected..github/workflows/— CI and release-artifact workflows; hosted builds/signing have not been verified.
VERSION is manually maintained and currently 0.4.0-dev.6, an internal development snapshot—not an Alpha/Beta or release claim. Version bumps are not automatic and a tag does not auto-publish a GitHub Release. A new version-consistency CI job runs on every branch/tag push, PR, and manual run; it checks SemVer, all product Cargo package versions, Android’s versionName source, iOS MARKETING_VERSION and platform floors, and exact stable tag matches. On stable version tags it also rejects a version that is not greater than existing stable tags. Signed release jobs require a matching stable vX.Y.Z tag and the repository variable SEREIN_RELEASE_APPROVED=true; leave it unset until the implementation and release gates are reviewed. Release builds are uploaded as workflow artifacts, not automatically published as a GitHub Release. Dependabot version-update schedules have been removed; dependency bumps are maintainer-driven, while CI resolves/builds dependencies on every push and pull request. GitHub's repository-level security alerts are separate. Markdown notes other than this root README are ignored by Git but remain in the complete source archive.
- Opaque transfer transport (implemented in backend source; unbuilt): upload/download encrypted chunks, per-chunk SHA-256 integrity, resumable idempotent writes, participant/device authorization, quota limits, expiry, and device-specific encrypted manifests. The service sees routing and size/timing metadata but never receives filenames or plaintext. At transfer creation, manifests must cover every currently active device of sender and recipient; devices enrolled later cannot decrypt old transfers yet.
- Reviewed key and device lifecycle (not implemented): choose/integrate a reviewed interoperable E2EE protocol under the dependency policy; bind identity/device keys, verify device additions/revocations, handle key rotation, backup/recovery, replay protection, and multi-device key envelopes. Do not build custom “triple encryption” or a home-grown ratchet.
- Client file-transfer integration (not implemented): encrypt content/manifest on clients using that protocol, persist resumable transfer state locally, stream bounded chunks, verify hashes before decrypting, support pause/retry/ACK, and test cross-platform plus revoked/new-device behavior. Until then, the server transfer API is not exposed as a working attachment feature.
- Calls (not implemented): establish reviewed call signaling and identity-bound session keys, then implement encrypted audio/video over an interoperable QUIC/MoQ stack with congestion control, loss/recovery, device switching, and group limits. The current QUIC echo and object router are not call transports. The current no-new-dependencies-outside-email rule blocks adopting a maintained QUIC/MoQ implementation; do not replace it with custom TLS/QUIC or claim a call works.
- OTP email code paths are implemented but provider credentials, DNS, mail receipt, build, and end-to-end verification are not verified. There is no password reset, account recovery, or IP/edge rate limiting; independent security review is still required.
- The new transfer API is opaque ciphertext storage/routing only. No web/Android/iOS sender or receiver is integrated; it is not a production encrypted-file feature until the reviewed key lifecycle and client work above land. Server metadata (participants, device IDs, byte counts, and timing) is visible.
- Contact discovery by username or verified email now creates a pending request; the recipient must accept. Unknown/ineligible targets get the same response body as eligible targets, and submitters cannot inspect request status before acceptance (to reduce enumeration); requests expire after 30 days and cap at 50 outgoing/100 incoming pending requests; per-account discovery is limited to 10 attempts/hour. Privacy settings control email discovery, request acceptance, and presence; blocks remove the relationship and deny new message/transfer authorization. Timing and distributed abuse resistance are not verified, and there is no trusted edge/IP rate limiter. Mobile-number discovery is not implemented.
- Browser message encryption uses a shared passphrase; there are no verified device keys, audited end-to-end protocol, or key transparency.
- Calls and media are not implemented. The QUIC spike is not a call stack; WebRTC is intentionally excluded.
- Native apps now include account-management API wiring and OTP entry/resend flows, but have no native message encryption/transport or encrypted history store. Android/iOS build outputs and compatibility have not been verified here.
- Existing third-party dependencies remain. Two new crates are permitted only for OTP mail delivery (
lettreSMTP andreqwestHTTPS API); both are exact-pinned and Dependabot is configured to open weekly review PRs. Automated package PRs are not auto-merged. No new crypto, media, QUIC, or MoQ dependencies were added; real interoperable QUIC/MOQT is therefore not implemented.
The complete source archive also contains deeper audit, security, protocol, build, roadmap, and test-plan notes under docs/ and the component folders. Markdown notes other than this root README are intentionally excluded from Git by .gitignore; they remain in the supplied archive. All notes are source reviews, not substitutes for successful builds, integration tests, or security review.