What we defend, and what we do not

Every claim in the first section corresponds to something an automated test asserts before each release, so it goes red before this page does. The second half of the page is the part most products leave out: the threats this design does not address, including the ones that are gaps rather than decisions.

Five things that are true because the code makes them true

Claims a reader could check, rather than adjectives.

Isolation is applied by the client, not by developers

Every workspace-owned row carries a workspace id, and application code cannot issue a query without it — the filter is added by the database client extension itself and learned from the schema rather than from a hand-kept list. Foreign keys carry the workspace too, so one workspace’s drawing cannot even reference another’s layout. A test sets up two workspaces and tries to read across, on every release.

Passwords are hashed with scrypt, sessions are signed cookies

Passwords are never stored or logged in any recoverable form, and the same function that hashes on sign-up is the one that verifies on sign-in. Session cookies are HTTP-only, same-site and signed with a server key; signing out invalidates the session rather than merely clearing the cookie.

Webhook targets are checked before we ever call them

A webhook URL you supply is resolved and refused if it points inside a private network, at loopback, or at a cloud metadata address — checked at the resolved address rather than at the hostname, because a name can resolve somewhere else the second time. Payloads are signed with your secret so you can verify that a POST came from us.

Read-only means read-only, and nothing is deleted

A suspended workspace, or one whose trial has ended, can read everything it ever created and write nothing. There is no path in this product that deletes a customer’s layouts, drawings, watchlists or alerts because of a plan change.

We are not a broker and hold no funds

No order ever leaves the building and there is no broker integration on the roadmap, which also means we take on none of the registration, suitability or best-execution obligations that come with routing one.

Isolation is applied one layer below the code that could forget it

Every multi-tenant product says it filters by tenant. What decides whether that survives two years of feature work is where the filter lives.

application codeasks for “this user’s layouts”the database clientadds the workspace filter itself, from the schemaPostgresforeign keys carry the workspace tooa developerrememberingnot in the pathlearned from theschema, not froma hand-kept list

The application cannot opt out

A query issued without the workspace filter is not a bug waiting to be reviewed — it is not a query this code can construct. A guard in the test suite fails the build on any module that reaches for the unscoped client without being on a short, written allowlist.

The database agrees

Foreign keys carry the workspace, so one workspace’s drawing cannot reference another’s layout even if something above went wrong. Postgres refuses the row rather than trusting the code.

It is re-proved every release

A test creates two workspaces on one database and attempts to read, count and write across the boundary in both directions. It ships with the code and runs before every deploy.

The threat model, with the third column filled in

The column that says what a defence does not cover is the one that makes the other two worth reading.

ThreatWhat stops itWhat it does not cover
One workspace reading another’s layouts, drawings, watchlists or alertsThe workspace filter is applied by the database client rather than by each query, and foreign keys carry the workspace so a cross-workspace reference cannot even be stored. A test sets up two workspaces and tries to read, count and write across, on every release.Market data itself is public and deliberately unscoped: instruments and candles are identical for every workspace and are not workspace-owned rows.
A stolen database dump becoming a set of live credentialsPasswords are scrypt hashes. Session tokens, invitations and API keys are stored as SHA-256 digests — the key itself is shown once, at creation, and never stored. Provider keys you supply are encrypted with a server key that is not in the database.A dump still contains your email address, your workspace’s contents and your instrument choices. Hashing protects credentials, not data.
A password-guessing script against the sign-in formPer-IP throttling on the authentication endpoints, and scrypt’s deliberate cost, which makes offline guessing expensive even if hashes leak.The throttle is in-process and per replica, so it is sized for a script and not for a distributed attack. It is not a web application firewall and nothing important is defended by it alone.
A webhook target used to reach something inside our networkA URL you supply is resolved first, and refused if the resolved address is private, loopback, link-local or a cloud metadata endpoint — checked at the address rather than at the hostname, because a name can resolve somewhere else the second time it is asked.We cannot vouch for what happens at your endpoint. Payloads are signed with your secret so you can verify the sender; verifying is your side of that.
A runaway integration spending a whole workspace’s API allowanceThe request budget is per key rather than per workspace, so one looping cron cannot take the other integrations down with it. Keys are revocable individually.The per-minute budget is an in-memory throttle per replica, honest about its weight. The durable usage number is metered separately and is the one a plan would ever be argued over.
A member of your workspace doing something expensiveRoles gate writing: settings, billing, members and provider credentials are admin-only, and a viewer seat can read everything and change nothing.Reading is workspace-wide by design — anyone in a workspace can open any layout in it. And a role check cannot stop a member spending a shared provider budget, so that is bounded by a counter on the key instead.

Out of scope, stated in the same detail as the rest

Some of these are deliberate boundaries and some are genuinely missing. They are in one list because from a reader’s point of view the distinction does not change what is true today.

No third-party audit, certification or penetration test

There is no SOC 2 report, no ISO certification and no external test to show you. What there is: an isolation test, a scoping guard that fails the build on an unscoped query, and the code decisions on this page. If your procurement process requires a certification, we do not have one.

No second factor yet

Sign-in is email and password, with signed, HTTP-only, same-site session cookies that are invalidated on sign-out rather than merely cleared from the browser. There is no TOTP, no passkey and no SSO. This is a gap rather than a boundary, and it is listed here because a security page that omitted it would be misleading.

A share or embed link is a bearer capability

Anyone holding the URL can read that snapshot, and there is no per-viewer authentication on it — that is what makes it shareable. The slug is unguessable, the snapshot is read-only, and it exposes only the chart it was made from. Revoke it by deleting the snapshot.

Market data is public and not protected

Candles and instruments are global rows, identical for every workspace, ingested from public venue endpoints. Nothing about them is confidential and nothing here is trying to keep them secret. What is scoped is everything you create.

We are not a broker and hold no money

No order routing, no custody, no funds, no card details, no bank credentials — there is no payment integration in this build at all. That removes an entire class of risk from this product, and it is worth being explicit that it was removed by not building it rather than by defending it.

Availability is not a security guarantee

Volumetric denial of service is handled, to the extent it is handled, by the platform in front of us and not by anything we wrote. A provider outage degrades charts to last-known bars with a label saying so, which is a correctness decision rather than a security one.

There is no security inbox or bug bounty yet

We would rather say that than print an address that bounces. If you find something, the most useful thing you can do is stop, avoid touching any workspace that is not yours, and reach us through whatever support channel you already have with us. A published disclosure address is on the list of things this product needs, and it is not built yet.

What we hold, and what we never had

The shortest way to reason about a product’s risk is to know what is in its database.

What we store

  • Your email address and a scrypt hash of your password.
  • Your workspace: layouts, chart panels, indicator settings, drawings, watchlists, alerts and their delivery history.
  • Session records, as digests, so that signing out can invalidate them.
  • Any provider key you supply, encrypted with a server key held outside the database.
  • Counters: how many API requests and alert notifications a workspace has used this month.

What we never collect

  • Card or bank details. There is no payment integration in this build.
  • Brokerage credentials, positions, balances or order history.
  • Anything that would let us place an order anywhere, because we route none.
  • Third-party tracking or advertising identifiers on the public pages.

A lapsed plan makes a workspace read-only and deletes nothing — that is a data-handling promise as much as a billing one, and the pricing page states it as a mechanism.

If you think you have found something

Please stop before you go further, avoid touching any workspace that is not yours, and do not run automated scanning against the production service. Reach us through whatever support channel you already have with us, with enough detail to reproduce it. We do not yet publish a dedicated disclosure address, and printing one that nobody reads would be worse than admitting that.