> ## Documentation Index
> Fetch the complete documentation index at: https://docs.teamduo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Security model

> How read-only access is enforced, and where the limits are.

## Read-only, three independent ways

Deleting any one of these still leaves a working product. They are not redundant belts on
the same braces — each fails differently.

<Steps>
  <Step title="Your own credential">
    The credential you create grants `SELECT` and nothing else. TeamDuo generates the
    command; you run it. This is the only layer outside TeamDuo's code, and the only one
    that survives a total compromise of it.
  </Step>

  <Step title="The execution envelope">
    **PostgreSQL** — `SET TRANSACTION READ ONLY` plus a statement timeout. The server itself
    refuses a write.

    **BigQuery** — no legacy SQL, a job timeout, and a cap on bytes billed.

    **MongoDB** — weaker, and worth saying so: there is no read-only transaction. What
    stands in its place is that the driver issues only `aggregate`, and the stage allowlist
    contains neither `$out` nor `$merge`. That rests on TeamDuo's code being right in a way
    the other two don't, which is why layer 1 matters most there.
  </Step>

  <Step title="The exposure gate">
    Layers 1 and 2 stop writes, but would happily read a table you never agreed to share.
    This is the layer that enforces *exposure*:

    * **PostgreSQL** — the query is parsed with the real PostgreSQL grammar.
    * **BigQuery** — the decision comes from a dry run, so the referenced tables and the
      cost come from BigQuery's own planner.
    * **MongoDB** — a structured pipeline is walked stage by stage. There is no string to
      parse.
  </Step>
</Steps>

## Denials say what was denied

A refusal always names what was blocked — "table `public.salaries` is not exposed", "this
query would scan 42 GB, the limit is 1 GB". An agent that is told why corrects itself; an
agent given a generic error retries blind.

## The one honest gap

A BigQuery dry run reports which **tables** a query reads, never which **columns**. Column
level exposure is therefore not enforceable on BigQuery, and TeamDuo refuses a
column-restricted BigQuery table outright rather than approximating the rule. It generates
a `CREATE VIEW` for your own project instead, where BigQuery enforces it.

An approximation would be worse than an absence, because only the approximation gets
mistaken for enforcement.

## What is never logged

The audit ledger records the statement, the row count, the duration, and who ran it —
**never a cell of your data**. Connection secrets are encrypted per row and never appear in
logs.

## Limits

| Limit              | Behaviour                                                              |
| ------------------ | ---------------------------------------------------------------------- |
| Rows per query     | Capped per team; results say when they were truncated                  |
| Queries per minute | Budgeted per team, so a retry loop can't load your production database |
| Query duration     | Statement timeout on PostgreSQL, job timeout on BigQuery               |
| BigQuery cost      | Capped by bytes billed; a query over budget is refused with its size   |

## Agent authorization

An agent acts as the person who authorized it. It reaches exactly the teams and datasources
that account can reach. It cannot connect a database, change what's exposed, or grant
itself more access — those are not tools it has.

API keys narrow this further through [scopes](/reference/api-keys).
