Skip to content

security

Your keys, your machines, your rules.

Brio runs agents that write code, spend money and touch your repositories, so every part of it is built to fail closed: nothing runs outside a sandbox, nothing is spent past a budget, nothing irreversible happens without a person, and everything is on the record.

01 · Official routes only

No scraping, no borrowed sign-ins.

Brio reaches AI vendors only through the channels they publish for programmatic use. This is a hard rule in the code, not a setting: adapters for the routes on the right must not exist.

allowednever
Official APIs with your own keysDriving consumer chat apps: chatgpt.com, claude.ai, gemini.google.com, grok.com
Official CLIs, run with their documented headless flagsReading or reusing the sign-in tokens any CLI or app stored
Official SDKs and hosted agent APIsUnofficial or reverse-engineered APIs
A2A agents and MCP servers you addBypassing CAPTCHAs or bot detection in browser agents

Using a subscription (rather than an API key) is possible only on your own machine, through the local runner, where the vendor's own CLI runs under your own sign-in, and only for vendors whose terms allow it: Antigravity, and Claude Code for your own work. Each vendor's terms are recorded in the registry.

02 · Where things run

No Brio cloud. No account with us.

Brio is self-hosted. Your data stays in your Postgres; the only calls that leave your machines go to the vendors you connected, with your keys, billed to you directly.

  1. You

    • the web app
    • brio CLI
    • Claude Code, Cursor (MCP)
    • any A2A agent
  2. Your Brio

    • API and orchestrator
    • Postgres
    • the key vault
    • the audit ledger
  3. Your runners

    • a Docker sandbox per attempt
    • or your laptop, under your sign-ins
  4. Vendors you chose

    • called with your keys
    • billed to you directly

03 · The vault

Keys are sealed, used once, and never shown again.

Bring your own keys. Each one is envelope-encrypted and decrypted only inside the runner, for the attempt that needs it.

  1. 01

    you

    paste a vendor key once

  2. 02

    vault

    a fresh data key encrypts it (XChaCha20-Poly1305)

  3. 03

    KEK

    the data key is wrapped by a master key (KMS or a key file)

  4. 04

    database

    only ciphertext is stored; the key is never shown again

  5. 05

    runner

    unwrapped in memory for one attempt, sent as a header

  6. 06

    after

    zeroed; logs and events are redacted

  • every logger and event writer runs a redactor: known secret values plus patterns (sk-, xai-, bearer tokens, cloud keys)
  • secrets never go into prompts, handoff packets, memory or artifacts
  • an output that contains a secret is quarantined
  • the master key comes from a KMS or a key file, and can be rotated
  • connectors show whether a key works, never the key
  • a contract test asserts that no secret leaks into logs or events

04 · Sandboxes

One container per attempt, deny by default.

Agents that run code never run on the API's host. Each attempt gets its own container, its own worktree, its own limits and an allowlist of hosts it may reach.

runner host · Docker

container · one per attempt

  • non-root user, no capabilities
  • read-only base image
  • writable: the worktree and /tmp only
  • no Docker socket, no host mounts
  • CPU, memory, disk and PID limits
  • a wall-clock limit, then killed
/work · branch brio/<mission>/<task>/<attempt>
.brio/brief.md · .brio/contract.json · .brio/handoff.json

egress: default deny, through a proxy

api.vendor.com✓
registry.npmjs.org✓
github.com✓
anything else✗

Outbound requests to addresses people type in (a model endpoint, an A2A agent) are checked inside the connection's own DNS lookup, so a name cannot be re-pointed at a private address between the check and the call. Private addresses are refused unless an operator allows them.

05 · Local runners

Your laptop connects out; nothing connects in.

A local runner opens one connection to your Brio with a token that is shown once, stored hashed and revocable. It holds no database credentials and never receives a vault key.

Docker by default

Every sandbox rule above applies on your machine too.

Host mode is opt-in

Only after you consent to its exact terms (a fresh worktree, a cut-down environment, process-group kill, a time limit, but no network allowlist), recorded with the time.

Only your own work

Work reaches your runner only for missions you created, following a plan you approved and started.

06 · Data policies

Confidential work goes only where you allow.

Every task carries a sensitivity: public, internal or confidential. Policies map each to the connectors it may reach.

  • with no policy, confidential work goes only to local models
  • connector allow and deny lists apply to everything; deny wins
  • network policies widen or narrow a sandbox's allowlist
  • policy changes are entries in the ledger

Checked twice

The planner, the router and the choice of reviewer all filter by the same policy engine, and dispatch checks again. A pinned or hand-picked agent cannot slip past: violations are impossible, not merely warned about.

07 · Budgets

Checked before you spend, not after the bill.

Monthly limits per workspace and per agent, and each mission's and task's own budget. Spend is the live cost of running attempts, so work in flight counts.

  1. before

    every scope that applies must have room; the attempt's cap becomes what is left

  2. 80%

    a budget.warn event, once per period, and a notification

  3. 100%

    budget.stop: the attempt is cancelled within seconds

  4. after

    the task waits, blocked, until a person raises the budget

Already using Mandate to govern agents' spending? Connect it, and every paid attempt also asks Mandate for a hold after Brio's own budgets pass. A decline, or an unreachable Mandate, blocks the task.

08 · Approvals

Irreversible actions wait for a person.

Pushes, deploys, sends and purchases need a yes, by policy. You decide in the app, by email, by push, in Slack, or from your editor over MCP.

actiondefault
approve a planrequired if it costs more than $5 or has more than 10 tasks
push a branch / open a pull requestrequired; a person decides, never an API token
deploy, publish, send email or a message, purchaserequired, per action
a browser agent visiting a new domainrequired
spend over the mission's budgetrequired
delete files outside the task's footprintalways denied

A Slack button counts only with a valid signature less than five minutes old, from an account whose email is a member of the workspace. MCP clients decide only with a scope a person grants on the consent page.

09 · Roles

Spending power apart from day-to-day work.

Owners and admins administer; members act; viewers read. A workspace always keeps an owner, and every role change is in the ledger.

owneradminmemberviewer
read missions, boards, scores, memory✓✓✓✓
start missions, edit plans, run agents, approve✓✓✓
connectors, budgets, policies, ledger, Slack✓✓
make owners and admins; delete the workspace✓

10 · Prompt injection

Other agents' words are data, never instructions.

Handoffs, attachments, issue text, other agents' messages, project memory, check output and results under review all reach a prompt only inside a labelled, fenced block that the text cannot close from inside.

Every result is scanned for

  • attempts to override instructions
  • chat role markers and jailbreak personas
  • requests to send secrets
  • notes addressed to the AI reading this
  • hidden Unicode
  • piping a download into a shell

# a result that tries it

research-agent → t2 · report written

brio → injection_scan · held for a person

  “ignore your previous instructions and…” at line 41

you → reviewed · passed on, quoted

A finding holds the result for you instead of failing it, so legitimate security work is not lost.

11 · The ledger

An audit trail that proves it was not edited.

Dispatches, spend, approvals, external side effects, policy changes, key changes, memory edits and role changes are entries in an append-only, hash-chained ledger.

  1. seq 41

    dispatch

    codex → t3

    prev 3a8e…c4

    hash 9f2c…a1

  2. seq 42

    spend

    $0.31 · t3

    prev 9f2c…a1

    hash 4be0…7d

  3. seq 43

    approval

    PR #42 · approved

    prev 4be0…7d

    hash c71a…02

  4. seq 44

    side effect

    pushed brio/health

    prev c71a…02

    hash e5d9…3f

hash = sha256(prev_hash ‖ canonical_json(entry)) · triggers refuse every update and delete

Written with the action

An entry is written in the same transaction as what it records.

Verifiable

Owners and admins verify the chain; the first broken entry is named.

Exportable

As JSONL or CSV, to keep chain heads somewhere else.

12 · Accounts

Only the people you choose get in.

Sign in with an emailed link, Google or a passkey. On a server anyone can reach, one setting decides who may create an account.

A sign-up allowlist

BRIO_SIGNUP_ALLOW names the addresses and @domains that may join; invited people always can. Nobody else is even sent a sign-in link.

Single-use links

Each emailed link works once, for fifteen minutes, and sign-in attempts are rate-limited.

API tokens with limits

Tokens act for your code, but can never approve plans, pushes or spending.

13 · The review

Reviewed, with the findings published.

Before release, Brio had a security review with a written threat model. Every finding, what was fixed and what remains, is in the repository.

Found a vulnerability? Please tell the maintainer privately before opening a public issue: the owner of the repository is listed on GitHub.

Safe by default, on your own machines.

Run Brio where you control the data, with the keys you already have.