# Security and tenancy

## Workspace isolation

Every workspace's data is separated by **row-level security in the database**, not by
application code remembering to filter.

Each request opens a transaction that first declares which workspace it is acting for.
The database applies that declaration to every table it touches for the lifetime of the
transaction. A query missing its `WHERE` clause returns nothing from another workspace,
because the database will not serve those rows to that connection.

What this rules out is the usual cause of cross-tenant leaks: a missing filter on one
query out of thousands. Here, that mistake returns no rows rather than someone else's,
because isolation does not depend on every query being written correctly.

The workspace boundary is the outer limit, not the whole access story. Inside a workspace,
row ACLs decide who reaches an individual record, and share links decide what reaches
someone with no account at all. Both are covered in
[sharing and access](/sharing-and-access).

## What a key can reach

A key is bound to one workspace and acts as the user who created it. Its permissions are
the **intersection** of three things:

1. the roles its owner holds,
2. what the workspace's plan entitles,
3. the `key_permissions` scope set on the key.

Narrowing any of the three narrows the key immediately, without reissuing it. A
permission granted by a role the plan does not include has no effect.

## Every endpoint declares its permission

Authorization is not documentation that can drift from the code. The permission shown on
each operation in the [reference](/reference) is read out of the middleware that enforces
it when the spec is generated. If the two disagree, the spec build is wrong rather than
the gate.

The API also refuses to start if any route declares neither a permission nor an explicit
decision to be open, so "nobody added a check" is not a state this service can boot in.

## What the AI can see

A model sees what the request puts in front of it: the prompt, whatever the caller
attached, and the passages retrieval pulled from the knowledge bases the request names.
Retrieval is a query like any other and runs under the same isolation, so a knowledge
base cannot return another workspace's content no matter how a prompt is worded.

Embeddings are workspace data. They are derived from your content, stored in your
workspace, and covered by the same row-level security as the content itself. Deleting a
file deletes the embeddings built from it in the same operation, so a deleted document
does not go on being retrievable.

**Your content is not used to train models.** Nothing in the platform fine-tunes on
customer data, and requests to the model provider run under commercial terms that
prohibit training on what is sent to it.

Content leaves the platform in one other place, and it is deliberate rather than
incidental: an agent calling a connected service sends that service whatever the tool call
carries. What each connector can reach is listed with it in
[external data](/external-data).

## Credentials

Keys are stored as sha256 digests. A key is readable exactly once, in the response that
creates it. We cannot recover one for you, and neither can anyone who reaches the stored
data.

Keys support IP allowlists and expiry. Set both for an integration that runs from known
infrastructure.

## Data at rest

The database and object storage encrypt what they hold at rest. That is a property of the
storage layer rather than something the application opts into per feature, so there is no
table or bucket where somebody forgot.

Beyond that, two kinds of secret are never stored anywhere they could be replayed from.
API keys exist only as digests, as above. Credentials for connected external services are
held by the connector service rather than by this API, which never sees them again after
handing them over. See [external data](/external-data).

## Transport and boundaries

All traffic is HTTPS, and requests pass through a rate limiter at the edge before reaching
the API. File uploads and downloads use signed, expiring URLs issued directly against
object storage: the bytes travel between your client and storage without passing through
the API, which is also why the 10 MB body limit does not apply to them.

## Audit trail

Every create, update and delete records who did it, which record it touched, and the
changeset itself. Previous values are stored rather than inferred, so *"what did this say
before it was changed"* has an exact answer rather than a reconstruction. Requests are
logged separately, which covers the reads that changed nothing but still matter.

Both are ordinary workspace data, written through the same isolation as everything else,
and both are readable over the API, at `/admin/history` and `/admin/access-logs`, so a
compliance export runs on a schedule instead of being assembled by hand. They are
operational logs on a bounded retention window, not an archive: pull anything you need to
keep past that window while it is still there.

## Deleting data

Deleting a file deletes the stored object, the thumbnails and previews derived from it,
its saved versions, and its embeddings. The bytes stay recoverable from the workspace's
trash for a short window, and after that they are gone for us as well as for you.

Most configuration (an agent, a prompt, a knowledge record) is deactivated instead of
erased. It leaves listings and stops being reachable, while the row survives so that
history entries and usage records pointing at it still resolve to something rather than to
a missing id.

## Report a security issue

If you believe you have found a security problem, write to
[security@genuinehq.com](mailto:security@genuinehq.com) before disclosing it anywhere else.
Include the `errorId` from any response involved, which locates the exact request in our
logs.

Research it in good faith and we will not pursue legal action over it. Good faith means
testing against your own workspace, not somebody else's; no denial-of-service or load
testing; and taking no more data than it takes to demonstrate the problem. If you reach
another workspace's data, that *is* the finding. Stop there and tell us.

We reply to every report with what we found, whether or not we end up agreeing it is a
vulnerability.
