Skip to content

Security

Every client kept apart, every key locked to its owner.

You are trusted with other people’s businesses: their leads, their customers’ phone numbers, their API keys. Here is how Thrum looks after them.

Thrum opens soon. Sign up now and you’ll be among the first in.

At a glance

What protects you and your clients.

  • A workspace for every client

    A client’s forms, leads, workflows and keys live in their own workspace, and every request is held to it. Details
  • Keys locked to their client

    Each client’s keys are encrypted and bound to their workspace. Copied into another workspace, they will not decrypt. Details
  • No passwords for clients

    Your clients sign in with a one-time link or a short code, so there is no password of theirs to leak. Details
  • The portal shows only the portal

    A client’s portal address never serves your screens, and it shows their leads in plain words. Details
  • Spam waits for you

    Suspicious submissions are held for review and start nothing until someone releases them. Details
  • Nothing typed runs as code

    A workflow reads a visitor’s answers as plain values, the way it reads a name or a date. Details

Each client is its own workspace

  • Every client’s forms, leads, workflows, runs and keys live in that client’s workspace. The app adds the workspace to every query on every client table and refuses a write into another one.
  • Postgres row-level security applies the same rule again inside the database, on every client table, for the app’s restricted database role.
  • Automated tests try to read and write from one client into another, through the app, through raw SQL as that role and through nested writes, and must fail every time.
  • The few jobs that look across all clients, such as finding runs that are due, run as database functions that return ids only.
  • Every route, page and database function that loads a client by an id from a request checks that the client belongs to the operator asking.
  • The critical flows, from sign-up to a public form, a run, the inbox and the portal, are tested end to end under a database role that cannot bypass row-level security.

Client keys are encrypted and bound to their client

  • Each client’s Twilio, Resend, webhook and API keys are encrypted with AES-256-GCM before they are stored, under a versioned key ring so the key can be rotated.
  • The client’s workspace is bound into each encryption, so a stored key copied into another workspace will not decrypt.
  • The app shows a key’s name, never its value. Templates and packs never copy a key from one client to another.
  • A file a run makes or fetches, such as a PDF, is encrypted like a credential, bound to the client’s workspace, and kept for 30 days.

Your clients sign in without passwords

  • A portal sign-in email carries a link that works once and expires after 15 minutes, and a six-digit code that lasts 10 minutes with 5 tries. Links and sessions are stored only as SHA-256 hashes.
  • The session cookie is HTTP-only, Secure and SameSite=Lax, and lasts 30 days.
  • Sign-in requests are limited to 5 per email address and 20 per network every 15 minutes. Network addresses are kept only as keyed hashes.
  • The answer to a sign-in request is the same, and takes at least as long, whether or not the address has access, so the form cannot be used to find out who your clients are.

A portal address only serves the portal

  • On a client’s portal address, every operator page and every operator API answers 404.
  • The portal shows leads and months in plain sentences: no step names, run ids, JSON or error text.
  • A custom domain is only connected after a TXT record proves the domain is the client’s. An unproved claim is released after 72 hours.
  • Health alerts go to you, never to your client.

Proposals keep a record nobody can edit

  • A proposal opens only on the portal address it holds for its client. Accepting needs a six-digit code emailed to the address it was sent to: kept only as a hash, valid 10 minutes, 5 tries, used once.
  • The acceptance keeps who, when, their network address and browser, the exact text shown with its SHA-256, and the agreement PDF. A database trigger refuses any change to it afterwards.
  • A proposal is accepted once: a replayed or simultaneous accept sets nothing up twice. Viewing, accepting and asking for codes are each rate-limited.

Client invoices hold the least they can

  • Billing your clients asks for your own Stripe restricted key, made with write access to invoices and customers only. A full secret key is refused.
  • A client’s own Stripe is connected for their payments with a restricted key too, and every permission is checked before anything is created in their account. Their customers’ money goes straight to them.
  • Each key is encrypted like a client’s other keys and never shown again. No client money passes through the platform.

Suspicious submissions are held for review

  • Every public form, the booking page and the chat carry a hidden trap field, a signed timing check and rate limits. A form sent less than 2 seconds after it loaded is held.
  • A held submission starts no workflow, text, email or CRM record until someone releases it, and releasing it runs its workflows once. Held submissions are deleted after 30 days.
  • More than 10 submissions in 10 minutes from one address to one form are held, and ten times any limit is refused with nothing stored.
  • When 20 or more are held in an hour, the team gets one alert. Cloudflare Turnstile can add a visible check when it is needed; that part is in beta.
The spam view: held submissions waiting for review, each with why it was held, and the button that releases it.The spam view: held submissions waiting for review, each with why it was held, and the button that releases it.

Sign-ups are checked before any email goes

  • Every sign-up route that sends an email, from signing up to asking for a new code or a password reset, checks for a bot first, with Cloudflare Turnstile; that check is in beta.
  • Sign-up refuses addresses from known throwaway email services.
  • A sign-up nobody confirms, and that owns nothing, is deleted after 7 days, and no reminder is ever sent to an unconfirmed address.

Email to people who asked for it

  • Marketing email carries one-click unsubscribe and the client’s postal address, and the unsubscribe pages name the client’s business, never the platform.
  • Each person’s consent records where it came from and the wording they agreed to. Double opt-in is there for the clients who want it.
  • A hard bounce or a spam complaint stops further email to that address. A frequency cap, quiet hours and a warm-up for a new domain apply to every client, and sending pauses itself if bounces or complaints climb.
  • Automated texts wait out quiet hours in the client’s own time zone, and a lead who texts STOP hears nothing more until they text START.

Nothing in a workflow runs as code

  • Values in a workflow are read by name, in double braces. Nothing a visitor types into a form is ever run as code.
  • A run that waits for a callback gets its own random 32-character address, which works once. An approval has no such address: only its approvers or your team can decide it.
  • Payment notifications are checked against their signature before anything is changed.
  • Every web request a workflow makes is checked before it is sent, and again at every redirect: an address inside a private network is refused.
  • What a run records has the client’s keys scrubbed out of it.
  • Step numbers keep counts and durations only. Emails, long numbers and record ids are taken out of error messages before they are counted.

The services it runs on

Who hosts and carries the data, and what each one does.

Vercel
Hosts the app and the portals, and issues custom domain certificates.
Neon
Postgres, where every client’s data lives.
Supabase
Your own sign-in, and form file uploads.
Mailgun
Portal sign-in emails, monthly reports and your alert emails.
Lemon Squeezy
Payments for your plan.
Twilio
Texts, through each client’s own account.
Mailgun, Resend or Amazon SES
Email campaigns and workflow email, through your own account.
Stripe
Client invoices through your account, and payments through each client’s own.
Cloudflare Turnstile
The visible bot check on forms, when one is needed.

What we don’t offer yet

If your clients need either of these today, tell us. Both are on the roadmap.

  • Choose where your data lives

    Regions, and running it on your own servers.

  • SOC 2

    An audit, when a customer with a procurement process asks for one. There is no certification today.

Report a security problem

Email hello@thrum.run with what you found and the steps to see it again.

Thrum is cooking.

Get early access and you’ll be among the first in. Early members get a launch perk.