Operations
Security model
Tether sits between a language model and a production system. This page states what it guarantees, what it delegates to you, and what it does not do yet.
The architectural boundary
Tether never holds your database credentials. It reaches your data only by calling an HTTPS endpoint you control, and it can only call tools you registered.
That single decision determines the blast radius of everything else. A total compromise of the service yields conversations, staged actions, telemetry, spend rows and hashed API keys. It does not yield your database.
What does leave your systems
The credential claim above is often heard as a broader one, so it is worth stating plainly.
On the second door, tool results pass through Tether to the model provider. That is what a tool call is for. Your endpoint returns rows; Tether puts them in the model's context so it can answer from them; the model provider therefore sees them. So does Tether, in transit, and in the conversation history it stores for the session.
On the first door, they do not. When you bring your own AI, Tether calls no model. Your agent holds the provider key and the conversation; Tether sits between your agent and your tools, scoping, staging and recording. Results go back to your agent, which puts them in whichever model context you chose. Tether still sees them in transit and records the audit — but there is no Tether-side model call, so there is no Tether-chosen provider receiving your rows.
That difference is the reason to prefer the first door if your data is sensitive, and it is a real difference rather than a configuration one.
| Reaches Tether | Reaches a model provider via Tether | |
|---|---|---|
| Database credentials | No | No |
| Your schema | No | No |
| Rows your tools return | Yes | Only on the second door |
| Prompts and conversation history | Yes | Only on the second door |
| Knowledge you indexed | Yes | Only on the second door |
| Arguments on a staged write | Yes | Only on the second door |
On the first door every row in that right-hand column reaches the provider you chose, through your agent, on your key — which is the same exposure you already had before Tether was in the picture.
Two consequences worth planning for. Choose what your tools return with the model provider's retention and jurisdiction in mind — a tool that returns a whole customer record when the question was about a delivery date sends the whole record. And where a field must not leave your systems at all, do not return it: filtering at your endpoint is the only place that can be enforced, because everything past it is already out.
Tether does not train on your data and does not sell it, and the provider it calls is configured per deployment — but neither of those is the same as the data not leaving, and procurement will be asking about the leaving.
The boundaries it enforces
Tenant identity comes from the credential
Set exactly once, from the API key, and nothing downstream can change it. Not the request body, not a model's tool arguments, not a header other than the ones documented.
A request body carrying organizationId is accepted and ignored rather than honoured. This is
the entire multi-tenant boundary: a bug that lets the body override it is a cross-tenant breach,
not a validation slip.
API keys are stored as hashes
SHA-256, with a 12-character non-secret prefix for identification. The plaintext is shown once at issuance and never stored, so a database disclosure does not hand anyone the ability to spend a customer's budget.
Unknown and revoked keys return an identical 401. Distinguishing them tells an attacker which
half of a guess was right.
Outbound tool calls are signed
HMAC-SHA256 over {timestamp}.{body} with the tenant's signing secret, in
X-Tether-Signature, with the timestamp in the signed payload so it cannot be moved. Your
endpoint verifies before parsing. See
Verifying signatures.
Endpoints are checked before they are called
HTTPS required; loopback, RFC 1918, link-local and cloud-metadata addresses refused; every resolved address checked, not just the first; unresolvable hosts refused rather than attempted.
The check happens on the connection, not on the URL string. A hostname is resolved once and the connection is made to that validated set of addresses, so a name that answers with a public address for a check and a metadata address for the connection cannot get through the gap between the two. See Endpoint requirements.
The model cannot write
A tool declared is_write is never dispatched during a turn. It is staged, and your endpoint
sees it only after a separate authenticated confirmation.
Not calling out at all — rather than calling out with a "this is only a preview" flag — is the point. A flag is exactly as safe as your handling of the flag, and getting that wrong means the model silently wrote to production.
Confirmation re-checks the tenant
The action was staged against one tenant; the credential confirming it must be that tenant. This is the one check only Tether can make — without it, a valid key for one customer could confirm another customer's staged write.
Execution is claimed with a compare-and-set, so two confirms racing produce one execution, with a unique index on tenant, user and idempotency key underneath as a second layer.
Untrusted content is fenced
Everything that is not the system prompt or the end user's own message — tool results, retrieved
knowledge, stored preferences, document and image text — is wrapped in explicit
UNTRUSTED_DATA fences, and every turn carries a standing policy telling the model to treat
fenced content as data rather than instructions.
Keys are scoped, and can be revoked
A tenant's secret key reaches that tenant's whole API. A publishable key reaches
/v1/chat, /v1/chat/stream and POST /v1/chat/feedback, and reads GET /v1/chat/brand, and
nothing else, which is what makes it safe to ship in a page:
it cannot read your catalog, register one, list actions, or read the audit. An action token
is minted with one staged write and confirms that write alone. Keys are issued and revoked by
prefix, so rotating one does not mean reissuing all of them.
The audit trail is readable
GET /v1/audit returns the life of every staged write for your tenant — proposed, approved,
rejected, expired, executed, failed, superseded — with who did each, from which surface, and a
hash of the payload rather than the payload. Accept: text/csv returns the same rows for an
auditor who wants a file. It is not a Postgres table you have to be given access to.
There is a request ceiling
A fixed-window limit per tenant, separate from the spend breaker: 120 requests a minute on the
paths that call a model or your endpoint, 600 on the rest, and 10 a minute per address on the
paths that answer without a credential. Over the ceiling is 429 with Retry-After. A tenant
can be inside their budget and still be everyone else's outage, which is why this is not the
same control as the spend cap.
Turns are bounded and metered
Tool turns, output tokens, tool timeouts, response size and concurrency are all capped, and the spend breaker reads an append-only ledger before any model call. A runaway loop trips a daily cap within the cache window rather than at month end.
The child-safety floor
Two layers, because the doors differ.
In the prompt. On the second door, where Tether calls the model, every turn carries a child-safety policy alongside the prompt-injection guard. Both are unconditional — not behind a capability mode, not behind a flag, and not replaceable by a tenant's domain pack, which can only supply a name, domains and a tone line.
In the execution layer. A staged write is inspected before it is created and again before it is confirmed, ahead of the permission guard, the approval gate and the execution claim. A refusal there is terminal: no approver, administrator or operator credential clears it, and the action never becomes executable. That layer covers both doors, because every write passes through Tether's confirm-and-execute path whoever generated it — including an agent you brought yourself.
It surfaces as 451 action_prohibited and is recorded in the audit as prohibited, distinct
from a person's rejected. The response states the principle and not the trigger.
Which tenants are moderated
Behind the deterministic check sits the provider's own moderation classifier, run on staged writes only — a write is already a rare, human-in-the-loop moment, so the cost and latency that would be intolerable on every turn are not a factor here.
It does not run on the first door by default. Sending your staged write arguments to a provider is exactly the exposure you chose that door to avoid, and a global setting must not quietly take it away. A first-door tenant is excluded until they ask to be included; second-door tenants, who are already on Tether's provider, are included. Either can be set explicitly, and only an operator can set it — turning it on for someone is a conversation, not a toggle.
When the provider is unreachable the check fails open and logs at error level. Failing closed would turn one provider outage into every customer's approval queue freezing at once. The deterministic gate has already run either way, so an unmoderated write is not an unchecked one — but it is a weaker check than was promised, and that belongs in your logs.
What it delegates to you
| Boundary | Why it is yours |
|---|---|
| Your permission model | Tether does not know that shift leads may void under $50 |
| Which data a tool returns | Everything you return enters the model's context |
| Scope and user assertions | Tether authenticates the tenant and takes your word for the rest |
| Idempotency at execution | Only your side can check whether a write already landed |
| Protecting the API key | It is a bearer credential for your whole tenant |
The X-Tether-Scope-Id and X-Tether-User-Id headers deserve repeating: they are asserted, not
authenticated. Set them server-side from your own session. A browser that can choose its own
user id can attribute a write to somebody else.
What is not built yet
Threats worth reasoning about
A stolen API key. Full access to that tenant: read the catalog, replace it, stage and confirm writes, spend the budget. Mitigations are the spend caps, and the fact that a write still has to pass your endpoint's own permission checks. Keep the key server-side.
A stolen signing secret. Lets an attacker forge tool calls to your endpoint. Your timestamp window bounds replay; nothing bounds forgery. Treat it like a webhook secret.
A malicious tool result. Covered by the fencing above, and bounded by the write gate.
A compromised model provider. It sees prompts, conversation history, retrieved knowledge and tool results — everything in a turn's context, as described above. It cannot call your endpoint: tool calls are dispatched by Tether, to the URL on the tenant record, and a tool name not in the catalog is refused. What bounds this is what your tools return, which is the one control that sits entirely on your side.
An insider at the operator. Sees conversations, telemetry, the arguments of staged writes and the results of executed ones — which is to say, whatever business data passed through a turn. They cannot query your database and there is no bulk copy of it to read, but "cannot read your business data" would be too strong, and it is what this page used to say.
A disclosure of Tether's database. Tether's own API keys are stored as SHA-256 and are not recoverable from it. Two other secrets are, because they have to be presented in the clear to be useful and so cannot be hashed: the bearer credential you gave us for your upstream, and the shared secret that signs tool calls to your endpoint. Both are encrypted at rest with AES-256-GCM under a key held outside the database, so reading the database alone does not yield them — reading the database and the deployment's key material does. Rotate both if you have reason to think either has happened.