Security

You are deciding whether to put your customers' licences on somebody else's platform. This page is what that means in practice, including the parts we have not built yet.

Passwords and keys

  • Passwords are hashed with scrypt, which is memory-hard, and never stored in a form anybody here can read. We cannot tell you your password because we do not have it.
  • An API key is shown once, when it is made, and stored only as a hash. If you lose it we cannot show it to you again: you rotate it, which is one click and does not break the key it replaces until you say so.
  • Two-step sign-in is available on every account, with recovery codes, and it is checked on every sign-in rather than only the first.

Who can reach what

  • Every request into a workspace goes through one permission check, in one place, against the role you hold in that workspace. There is no path that skips it and no endpoint that decides for itself.
  • An API key is scoped to what it may do. A key baked into a downloadable build so it can issue a licence on first run cannot read your customers, and a key that validates licences cannot issue them.
  • Webhooks you configure are checked against private and internal addresses both when they are saved and again when they are sent to, and redirects are not followed. A URL that answers publicly today cannot be pointed at something inside our network tomorrow.

What is written down

  • An audit log records who changed what in your workspace, including anything Bloom staff do in it and the reason they gave. It is yours to read and to export.
  • Licence checks are recorded as events: what was asked, what was answered and why. That is what the analytics are built from, and it is how you find out a key is being shared before somebody tells you.
  • Sessions are httpOnly cookies, Secure in production, and you can see every device signed in to your account and end any of them.

Money

  • Card details never reach Bloom. Checkout and the billing portal are Stripe's own screens, and what we store is an identifier for the subscription.
  • A plan is granted on Stripe's signed webhook, not on the browser coming back from it. Anybody can open a success URL; only Stripe can sign a webhook.

The machine it runs on

  • The application publishes no port to the internet. It is reached only through a Cloudflare tunnel, runs as a non-root user, and its filesystem is read-only apart from the one directory that holds uploads.
  • The database is not reachable from outside the machine at all, and the application talks to it over a private network between containers.
  • Migrations run when the app starts, and it refuses to serve if one fails, rather than answering requests against a schema it was not written for.

Found something

Tell us before you tell anybody else and we will fix it. There is no bounty programme yet, so what we can offer is that you will be answered by somebody who can change the code, and credited if you want to be.

Report it on Discord