Frame docs · Reference
Security
Frame is built to be secure by being small: few moving parts, few dependencies, and nothing that parses untrusted media on the server. This page is the operator's summary; the full model is in SECURITY.md.
Who is trusted
| Party | Trusted? | What they can see or do |
|---|---|---|
| You, the server operator | Yes, fully | Everything stored on your server: photos, videos, captions, messages, reactions, friend lists, e-mail addresses, phone numbers and IP addresses. That's the self-hosting deal. |
| Other users on the same server | No | Only moments their friends addressed to them, chats with friends, and public profiles. |
| Network attackers | No | With HTTPS, endpoints, timing and sizes only. |
| The push relay (Crow Tech) | No | Opaque relay tokens, ciphertext sizes, timing and which server IPs push. No content, no user identities. |
| Apple and Google push services | No | Device tokens, timing, ciphertext and the generic text "Frame: New activity". |
| R2 or another bucket provider | No | Encrypted objects, their sizes and access times. |
| A tunnel or CDN (such as Cloudflare Tunnel) | Partially | It terminates TLS, so it can technically see the traffic. Built-in HTTPS avoids this. |
- You, the server operatorTrusted?:Yes, fullyWhat they can see or do:Everything stored on your server: photos, videos, captions, messages, reactions, friend lists, e-mail addresses, phone numbers and IP addresses. That's the self-hosting deal.
- Other users on the same serverTrusted?:NoWhat they can see or do:Only moments their friends addressed to them, chats with friends, and public profiles.
- Network attackersTrusted?:NoWhat they can see or do:With HTTPS, endpoints, timing and sizes only.
- The push relay (Crow Tech)Trusted?:NoWhat they can see or do:Opaque relay tokens, ciphertext sizes, timing and which server IPs push. No content, no user identities.
- Apple and Google push servicesTrusted?:NoWhat they can see or do:Device tokens, timing, ciphertext and the generic text "Frame: New activity".
- R2 or another bucket providerTrusted?:NoWhat they can see or do:Encrypted objects, their sizes and access times.
- A tunnel or CDN (such as Cloudflare Tunnel)Trusted?:PartiallyWhat they can see or do:It terminates TLS, so it can technically see the traffic. Built-in HTTPS avoids this.
What the server does
- HTTPS for anything on the internet, from Frame itself (Let's Encrypt), Caddy or a tunnel. The apps refuse plain
http://except for local network addresses. - Passwords are hashed with Argon2id. Sign-in is rate limited per IP and per account, and an unknown account takes as long to reject as a wrong password, so sign-in can't be used to find accounts.
- Apple and Google sign-ins are bound to your server's name (
FRAME_PUBLIC_URL) and work once, so a token someone obtained for another Frame server can't be replayed against yours. - Sessions are random tokens stored only as hashes, revocable per device. Changing a password signs out every other session.
- Authorisation: every object is checked against the requesting user. Things you can't see answer
404, not403, so their existence isn't revealed. - No server-side media processing. The server checks file types and sizes and stores the bytes; it never decodes images or video. Thumbnails are made on the phone, and the apps strip location and other metadata before upload.
- Media URLs are signed and expire after 24 hours, and responses tell shared caches not to keep copies.
- Storage encryption: with R2 or S3, every object is encrypted on the server before upload, with a key in
secrets.json. - Push payloads are end-to-end encrypted between your server and each phone (Push notifications).
- Logs never contain passwords, tokens or message text, and Frame's access log records route patterns, not paths or query strings. Invite codes are part of the path, so a reverse proxy's or tunnel's own access log does contain them.
- Few dependencies: the server uses the Go standard library plus a pure-Go SQLite and
golang.org/x/crypto; no web framework, ORM or JWT library. Releases shipSHA256SUMS, and the installer refuses a file whose checksum doesn't match.
Hardening
Linux installer (frame.service): a throwaway system user (DynamicUser) that can write only its data directory; ProtectSystem=strict, ProtectHome, PrivateTmp, PrivateDevices; NoNewPrivileges and no capabilities, except binding ports 80/443 when FRAME_DOMAIN needs them; MemoryDenyWriteExecute, restricted address families and namespaces, a system-call filter and UMask=0077. The config file is root-only and read by systemd before privileges are dropped.
Docker: a distroless image with no shell or package manager, running as uid 65532 on a read-only root filesystem with all capabilities dropped and no-new-privileges. In the Caddy setup, the Frame container publishes no ports.
What Frame deliberately doesn't do
- No end-to-end encryption of photos and messages against the operator. You can read everything on your server. It keeps the system simple, fast and recoverable; your friends are trusting you.
- Metadata is visible to the operator: who is friends with whom, who posted when and to whom, reactions, views, read receipts and IP addresses.
- Recipients can keep what you send: screenshots, saved photos and widget caches can't be prevented.
- A signed media URL works for anyone who has it until it expires (24 hours). Deleting a moment deletes the file, which ends access immediately.
- Deletion is best-effort beyond the server: earlier backups still contain deleted content.
- No automatic moderation or content scanning. Members can block people and report content to you, the operator; nothing goes to Crow Tech or anyone else.
- E-mail addresses and phone numbers aren't verified (Frame sends no SMS). On an
openserver someone could register another person's number first; invite-only servers, the default, make this practically impossible. - A small server can be overwhelmed. Rate limits protect against abuse, not volumetric attacks.
- Servers don't talk to each other. A user on one server can't see another server's content.
Checklist for your server
- To do: HTTPS works:
https://your-domain/healthzshows a valid lock. - To do:
FRAME_PUBLIC_URLis set to your address(es). - To do:
FRAME_TRUSTED_PROXIESmatches your setup: unset with built-in HTTPS, only your proxy's address otherwise. - To do: Registration is
invite(the default) orclosed, unless you really want an open server. - To do: You created your admin account with the setup link and turned on two-factor authentication in the panel (Security).
- To do: Consider
FRAME_ADMIN=localorFRAME_ADMIN_LISTENwith an SSH tunnel if you don't need the admin panel on the internet (Keep the admin panel private). That also turns off Server admin in the apps, unless you setFRAME_ADMIN_APP=on. - To do: With
FRAME_ADMIN=local, a reverse proxy or tunnel on the same host is listed inFRAME_TRUSTED_PROXIES. - To do: Only ports 22, 80 and 443 are open (or none, with a tunnel). You log in with SSH keys, not passwords.
- To do: Automatic OS security updates are on (
sudo apt install unattended-upgrades). - To do: Nightly backups of the database and
secrets.jsonare copied off the server, and you've tried a restore once. - To do:
frame.confstays private (chmod 600with Docker) and is never committed or shared: it can contain R2 and SMTP credentials. - To do: You upgrade when a release is announced.
Report a vulnerability
Please report security issues privately through GitHub private vulnerability reporting on Crow-Tech-Pte-Ltd/frame (Security tab → Report a vulnerability), or by e-mail to [email protected] with the subject "Security". Don't open public issues for vulnerabilities. We aim to acknowledge reports within 3 working days. Test only against servers you run yourself.
For AI tools: this page as Markdown, the page index and all pages in one file.