Security & protocol
Everything beam does, stated precisely — including the parts that favor honesty over marketing. The deployed code is open source at github.com/benkimz/beam; every claim on this page can be checked against it.
Threat model, in one paragraph
beam is designed so that a fully compromised beam server — or an honest one under legal compulsion — cannot read your secrets and cannot read files sent over a direct beam, because it never possesses the plaintext or the keys. What the server can see is metadata: that a session existed, connection-setup messages (which include participant IP addresses), and, in one specific fallback case described below, temporary file contents. beam does not defend against a compromised device, a malicious browser extension, or someone who obtains your one-time link before the recipient opens it. Possession of a link or code is the capability — there are no accounts to check against.
Pairing & signaling protocol
- A beam session is identified by a 6-character code drawn from a 32-character
alphabet (
A–ZminusI,O, plus2–9) — about 1.07 billion combinations. Joining with an unknown code is delayed 400 ms server-side to make scanning impractical. - Sessions live 15 minutes from creation, accept at most 8 guest devices, and can be ended early by the host. Ended and expired sessions refuse joins and messages.
- Devices exchange messages through the server by short polling
(
api/beam.php). Each participant has a peer id (hfor the host,gXXXXfor guests); every message is addressed to exactly one peer. These messages carry only WebRTC handshake material (SDP offers/answers, ICE candidates), plus small control notices (text-via-relay, file-waiting, goodbye). - Known limitation, stated plainly: peers within a session are not
authenticated to each other. Anyone who has the code during the 15-minute window
is a legitimate participant by definition, and a participant could forge the
fromfield of a signaling message. The code is the security boundary; treat it like a key.
WebRTC configuration — exactly
RTCPeerConnectionwith two STUN servers:stun:stun.l.google.com:19302andstun:stun1.l.google.com:19302. STUN only helps devices discover their own public address; no traffic flows through it.- There is no TURN server. If NAT traversal fails, beam does not silently route your traffic through a stranger's relay — it falls back to the size-limited relay described below, and tells you so in the UI.
- Transfers use a single ordered
RTCDataChannel. WebRTC data channels are always encrypted in transit (DTLS 1.2+, keys negotiated directly between the two browsers). Files are sent in 16 KB chunks with backpressure (1 MB high-water mark). - What the server necessarily sees during setup: the SDP and ICE candidates it relays, which contain the participants' IP addresses. That is inherent to WebRTC signaling. These messages are deleted with the session — at most 15 minutes after creation.
The relay fallback — what the server sees
When a direct connection cannot be established within ~15 seconds (symmetric NATs, strict corporate networks), beam offers a store-and-forward fallback for files up to 7 MB. Full honesty about this path:
- The file is uploaded to the server and sits unencrypted at rest in a directory outside the web root, along with its (sanitized) filename and size.
- It is deleted the moment the recipient downloads it, when the session ends, or when the session expires — whichever comes first. Never longer than 15 minutes.
- This is the one case where the server could technically read file contents. The UI labels relay mode explicitly whenever it is active, so you are never on this path without knowing. If that trade-off is unacceptable for a given file, don't send it over relay — direct beams and secrets never have this property. End-to-end encryption for the relay path is on the roadmap.
Private chat
- Chat rooms reuse the beam session and signaling machinery (same codes, same handshake, same 8-guest cap), with one lifetime difference: rooms stay alive while participants are active (15-minute sliding idle timeout, 12-hour hard cap) instead of a fixed 15 minutes.
- Chat messages never transit the server — by construction. They travel only over the DTLS-encrypted WebRTC data channels, star-routed through the room's host device. This includes chat media (images and files, ≤ 10 MB), which is chunked over the same channels — a guest's photo is reassembled on the host device and re-sent to the other members, never uploaded anywhere. Unlike file beams, chat has no relay fallback: if a direct connection cannot be formed, the room refuses to work rather than downgrade. The server's involvement is the connection handshake, nothing more.
- Nicknames and the member roster are exchanged over the same data channels — the server never sees names. The identity colors are derived locally from peer ids.
- Nothing is persisted anywhere: no history, no offline delivery, and a participant who joins late cannot see earlier messages, because no copy of them exists outside the browsers that were present. When the host leaves, the room ends for everyone.
- Same caveats as beams: the invite code is the capability (anyone holding it during the room's life can join and will be visible in the member list), and the signaling handshake necessarily reveals participant IP addresses to the server for ≤ the session's life.
Secrets — the exact cryptography
- Algorithm: AES-256-GCM via the browser's WebCrypto API
(
crypto.subtle). - Key: 32 random bytes from
crypto.getRandomValues(the browser's CSPRNG). There is no key derivation and no password — the key is pure random material, generated in your browser, used once. - Nonce: 12 random bytes, generated per secret. The stored payload
is
base64url(IV || ciphertext || GCM tag). - Where the key lives: only in the URL fragment —
beamtm.com/#s=<id>.<key>. Browsers never transmit the fragment in HTTP requests, so the key never reaches the server, its logs, or any proxy. The server stores only the ciphertext, a random 96-bit id, and an expiry time. - Burn semantics: reading is an atomic read-and-delete inside an immediate SQLite transaction — two simultaneous readers cannot both win. Unread secrets are deleted at their chosen TTL (5 minutes to 7 days; default 1 day). Note that fetching burns the ciphertext regardless of whether decryption then succeeds: possession of a valid link is the read capability.
- Ciphertext is capped at 64 KB (~40,000 characters of text).
What the server stores, and for how long
| Data | Contents | Retention |
|---|---|---|
| Sessions | code, timestamps, guest count, ended flag | ≤ 15 minutes |
| Signaling messages | SDP / ICE (includes IPs), control notices | ≤ 15 minutes |
| Relay files | file bytes (cleartext), filename, size | until pickup; ≤ 15 minutes |
| Secrets | ciphertext, random id, expiry | until read; ≤ chosen TTL (max 7 days) |
| Usage counters | one integer per metric per day (page_view, beam_created, beam_joined, chat_created, chat_joined, secret_created, secret_read, relay_upload) | indefinite (aggregate only) |
Deletion is enforced by opportunistic garbage collection on incoming requests rather than a scheduled job, so precise deletion time can lag TTL by a short, traffic-dependent interval.
Application logging: beam's own code writes no logs — no IPs, no user agents, no per-user records of any kind. The usage counters above are the entirety of application-level analytics; there are no cookies and no third-party scripts (fonts are the single external resource, loaded from Google Fonts).
Web-server logging: beam runs on ordinary shared hosting (LiteSpeed). Like virtually every web host, the platform keeps standard HTTP access logs (IP, URL path, timestamp, user agent) under the hosting provider's own rotation policy. URL paths do not contain codes, ids, or keys — session traffic is JSON POST bodies, which access logs do not capture, and secret keys live in fragments, which browsers never send at all.
Code transparency
The complete source — this page included — is at github.com/benkimz/beam under the MIT license. There is no build step: the files in the repository are byte-for-byte the files this site serves, so you can diff production against the repo directly. A machine-readable API description lives at /api/openapi.json and an agent-oriented guide at /llms.txt.
Vulnerability disclosure policy
Security reports are genuinely welcome — this is a small independent project and fresh eyes are its best audit.
- Contact: benkim3619@gmail.com (also listed in /.well-known/security.txt). You can also open a GitHub security advisory on the repository.
- What we promise: acknowledgement within 72 hours, a good-faith fix for confirmed issues as fast as a one-person project allows (target: well inside 90 days), and public credit in the repository if you want it. There is no paid bounty — saying so up front beats implying otherwise.
- Safe harbor: good-faith research against beamtm.com is authorized. We will not pursue legal action for testing that respects two limits: don't access, alter, or destroy data belonging to other users (create your own sessions and secrets to test against), and no volumetric denial-of-service — the service runs on shared hosting and its neighbors don't deserve your load test.
- In scope: anything that breaks a claim on this page — reading secrets server-side, bypassing burn-once semantics, cross-session access, signaling injection with real impact, XSS on any page.
Last updated 21 July 2026 · matches the deployed code as of that date. If this page and the code ever disagree, the code is the truth and the discrepancy is a bug — please report it.