Security
This page describes what the Farbeam application does, based on its source code. It lists the weak points as well. The service has had no third-party audit, and it holds no security certification. The docs have a shorter developer note.
#Wallet sign-in and sessions
An account is a Solana address. There is no password and no email. Reading the API needs no account. EVM wallet sign-in is Planned.
| Step | What happens |
|---|---|
| Challenge | POST /auth/challenge returns a text message with a random nonce. It expires in 5 minutes and works once |
| Signature | The wallet signs the text. This sends no transaction and costs nothing. The server never sees a private key |
| Verify | POST /auth/verify checks the ed25519 signature against the address, and deletes the nonce |
| Session | A random token in the cookie fb_session. The database holds only its SHA-256 hash, and it expires after 7 days |
| Sign-out | POST /auth/logout deletes the session row and clears the cookie |
The cookie has HttpOnly, SameSite=Strict and Path=/api, and Secure over HTTPS. Page scripts cannot read it, and browsers do not send it on requests from other sites. Sign-in attempts are limited to 30 per hour per network.
The account row holds the address, the created and last-seen times, and a linked Telegram chat id if you link one. A signature proves control of the address at that moment and nothing more. A person who controls the wallet controls the account. Removing the Telegram link is DELETE /account/telegram.
#API keys
| Property | Behaviour |
|---|---|
| Format | fbk_ plus 24 random bytes in hex |
| Storage | SHA-256 hash of the key. The key itself is never stored |
| Shown | Once, in the response to POST /keys |
| Recovery | Not possible. Create a new key |
| Identity | Belongs to the signed-in account. A label you choose, cut to 60 characters |
| Stored with the hash | Key id, label, created time, last-used time, calls per UTC day |
| Sending | Authorization: Bearer <key> or x-api-key |
| Revoking | DELETE /keys/{id} in a session, or DELETE /me with the key. The record stays in the database |
| Limit | 5 active keys per account, 20,000 units per UTC day per key |
The console does not store a key in the browser. It uses the session cookie, and the server uses a hidden key of the account for limits and rule ownership. Do not put a key in client-side code that other people can read.
#Webhook secrets
Each rule has a signing secret: fbs_ plus 24 random bytes in hex. It is returned once, when the rule is created. The API never returns it again.
Unlike API keys, signing secrets are stored in readable form in the database, because the server needs the value to compute each HMAC. Someone with access to the database file could read them and forge signatures. There is no rotate endpoint. To replace a secret, delete the rule and create a new one.
Signatures use HMAC-SHA256 over <t>.<raw body>. Verify the signature and check t so an old request cannot be replayed. See webhook signatures.
#Webhook SSRF protection
Rule URLs point at hosts chosen by users, so the server treats them as hostile. Checks run when a rule is saved and again on every delivery.
| Check | Rule |
|---|---|
| Scheme | https only |
| Port | 443 only |
| Credentials in URL | Refused |
| Host | IP literals refused. localhost, .local and .internal names refused |
| DNS | Every resolved address must be public. If any address is private, the delivery is refused |
| Connection | Uses the address that was checked, so a second DNS answer cannot change the target |
| Redirects | Not followed |
| Timeout | 6 seconds |
| Length | URL up to 500 characters |
Refused IPv4 ranges: private ranges (RFC 1918), loopback, link-local, carrier-grade NAT, benchmark ranges, multicast and reserved ranges.
The response body from your endpoint is not read or stored. The delivery log keeps the HTTP code and a short error class.
#Rule language
Beamline expressions are parsed by a small hand-written compiler into closures. The code never calls eval. A rule can have up to 1,000 characters and 64 conditions.
#Contact messages
Messages from the contact form contain name, email, optional company, topic and message. They are written to the database first, then forwarded to the inbox for contact@farbeamlabs.com. Failed sends retry every minute, up to 50 times. After a send, the content in the database is replaced with an empty object, and the row is deleted after 30 days. If forwarding keeps failing, the content stays in the database until someone handles it.
The form allows 3 messages per network per 10 minutes.
#Logs
The application writes no request logs. It does not store IP addresses on disk.
| Data | Where it is held | For how long |
|---|---|---|
| SHA-256 hash of the client IP, for rate limits | Memory | Up to about 6 minutes |
| SHA-256 hash of the client IP, for key creation and contact limits | Memory | Up to 24 hours after the last event, or until a restart |
| SHA-256 hash of the client IP, for open stream counts | Memory | While the stream is open |
| Method, path and error text of an unexpected server error | Process output | As kept by the host |
A hash of an IPv4 address can be reversed by trying all addresses, which is one reason these values stay in memory. The error output can contain a path, and a path can contain an address or a transaction hash. It does not contain the IP or the key.
This table covers the application. A network provider or hosting layer between you and the application can see connection data, as with any website.
#HTTP headers
Pages and API responses from the application have these headers.
| Header | Value |
|---|---|
content-security-policy | default-src 'self'; img-src 'self' data:; style-src 'self'; script-src 'self'; connect-src 'self'; font-src 'self'; media-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none' |
x-content-type-options | nosniff |
x-frame-options | DENY |
referrer-policy | no-referrer |
permissions-policy | geolocation=(), camera=(), microphone=() |
The policy blocks inline scripts and third-party scripts, styles and fonts. Fonts are self-hosted. The site uses no third-party analytics. The only cookie is fb_session, set after a wallet sign-in. API responses add cache-control: no-store and access-control-allow-origin: *. The API is meant to be called from any origin, so never rely on CORS to protect a key. The session cookie is SameSite=Strict, so a request from another site does not carry it.
#Request handling
- JSON bodies are capped at 32,768 bytes, and contact bodies at 16,384 bytes.
- Request time is capped at 30 seconds and header time at 15 seconds.
- Static paths are normalized, and paths with dot segments are refused.
- Upstream calls do not follow redirects and have timeouts.
- Upstream error text is not passed to clients. A client sees a code and the source name.
#What the service never does
Farbeam does not hold funds and does not ask a wallet to sign a transaction. The console asks a Solana wallet to sign one text message for sign-in. That message says that it sends no transaction and costs nothing. The site never asks for a seed phrase or a private key. If anyone claims otherwise in the name of Farbeam, treat it as a scam and report it.
#Reporting a problem
Use the contact form with the topic "Security report", or write to contact@farbeamlabs.com.
Please include:
- The URL or endpoint, and the
x-request-idif you have one. - Steps to reproduce it, with the UTC time.
- What you could read or change.
Please test only against your own keys and rules. Do not read other users' data, do not run load tests, and do not send spam through the contact form. Give a reasonable time to fix a problem before you publish it. There is no paid bounty program.