Ducto

Security architecture

Server-side, where no prompt can reach

Read-only defaults, caps from the policy row, preview-before-apply, selective approval, hash-chained audit — every mechanism documented below, failure modes included.

The request path

Fixed order, every request

Authentication, scoping, and policy run before any connector code touches a platform. The path is the same for every one of the 226 tools.

  1. 01

    Authenticate

    Bearer token or OAuth 2.1 resolves to your org and workspace. Rate limits apply before anything else runs.

    bearer → org → workspace → rate limit

  2. 02

    Scope

    The agent only sees tools for platforms you've connected and the token allows — no 226-tool context dump.

    tools = connected ∩ token.scopes

  3. 03

    Policy

    The policy row is read from Postgres. No row means read-only — the gateway fails closed, not open.

    policy row absent ⇒ read_only = true

  4. 04

    Cap and preview

    Budget and bid caps come from the policy row, never from the model. Every mutation dry-runs first; apply requires a matching live preview.

    caps ← policy row · preview.fingerprint == mutation

  5. 05

    Approval — when you've flagged it

    waits for a human

    Tool classes you mark high-stakes park here for a human. Everything else executes instantly within its caps.

    status: pending_approval

  6. 06

    Apply and audit

    The write executes and lands in an append-only ledger — each event hash-chained to the last. No role can edit it, including ours.

    hash = sha256(prev_hash + event)

Mechanisms

Each claim, with the thing that enforces it

Fail-closed policy engine

Writes require a policy row. No row, no writes. Caps are numeric columns in Postgres — nothing the model supplies is ever used as a limit.

Tenant isolation by row-level security

Every tenant table keys on the request's org GUC. Unset means zero rows — queries fail empty, not open. The gateway's database role is non-owner, so RLS always applies.

KMS-envelope token vault

Platform OAuth tokens are encrypted with a per-row data key, itself wrapped by Cloud KMS. A database dump alone cannot decrypt a single credential.

Hash-chained, append-only audit

Each audit event carries the previous event's hash, per org, from sequence 1. No role has UPDATE or DELETE, and a trigger blocks even the owner.

Selective human approval

Policy scopes approval to the tool classes you flag — budget-touching writes, structural changes. Parked writes surface in an approval inbox with their preview attached.

Least-privilege database roles

The gateway role can read and append audit events but cannot touch billing or membership; the dashboard role can't rewrite history. Two roles, explicit grant matrix.

Failure modes

What happens when something goes wrong

A security page that only lists happy paths isn't one. These are the designed outcomes.

ScenarioDesigned behavior
No policy row exists for a platformAll writes blocked — read-only, fail-closed
Apply arrives without a matching live previewWrite refused until the exact mutation is previewed
Mutation exceeds a cap in the policy rowBlocked server-side; the model cannot raise the cap
Tool class is flagged for approvalWrite parks as pending; executes only on human approval
Org context missing on a requestRLS returns zero rows — no cross-tenant reads
Anyone attempts to edit an audit eventDatabase trigger raises insufficient_privilege — for every role

Every change your agent makes, in a ledger no one can edit — including us.

Append-only, hash-chained, exportable. Show it to your client, your CFO, or your security team.