Architecture
This page is the reference behind How your keys are protected. It names the AWS services and the checks in the order the code runs them. Everything on it is in the Sydney region (ap-southeast-2).
The shape
Section titled “The shape”A model can be talked into things. The design starts from that: the environment where the model runs is never given a credential, and the one component that can read a credential does not run a model.
Where a credential lives
Section titled “Where a credential lives”- One secret per organisation and provider in AWS Secrets Manager. The secret holds the pasted key, or the OAuth token document, as JSON.
- The grants live on the secret, as one resource tag per granted operation. Reading the grant list is a describe call on the secret, so the grant check and the credential are governed by the same policy.
- Read under a role assumed per call. The proxy assumes a role with an inline session policy that names one organisation’s secret prefix. The policy allows get, describe and put (put is for the OAuth refresh). A bug in the proxy cannot reach another organisation’s secret, because the credentials it holds for that call cannot name one.
- Safe Integrations is applied in the app, where grants are chosen. With it on, the Write and Irreversible lists are locked, and turning it on strips every Write and Irreversible grant from every stored credential in the organisation. The proxy does not read the switch; it enforces the grant tags the switch has already shaped.
The working session
Section titled “The working session”- One ECS Fargate task per session. It is started for that session and stopped when the session ends or goes idle.
- Temporary STS credentials, scoped to the S3 prefixes the session may use: its own artifacts, the Stori’s files, the organisation’s uploads. Cross-Stori access within the organisation is a second, read-only credential. There is no Secrets Manager permission in any of them.
- A session token, issued at start and sent on every proxy call, so a call can be tied to one live session.
- The proxy is invoked by Lambda ARN. The session’s client sends
session_id,session_token,provider,operationandparams, and reads back a result. It makes no retries, because a timeout cannot be told apart from a call the provider already ran. Any organisation identifier the session sends is ignored.
One call, in order
Section titled “One call, in order”The proxy runs these checks in this order and stops at the first that fails. Refusals that must be indistinguishable to the caller use byte-identical messages.
- Authenticate. Load the session record from DynamoDB by id, verify the token, and require a live status.
- Rejection budget. If the session has had ten calls refused in a row, refuse this one too. A successful call resets the count.
- Resolve the operation against the connector registry. An unknown provider or operation is refused as not permitted.
- Assume the secrets role for this organisation and read the grant tags. No secret means no credential; an operation not in the tags is not permitted.
- Validate parameters against the operation’s definition. Path and query parameters are typed and checked; a parameter the definition does not name is not sent.
- Resolve file parameters for upload operations, from the session’s own S3 prefix.
- Load the credential, last. Validate its shape against the provider.
- Refresh an OAuth token if it has expired, writing the new one back under the same secret. A provider that will not refresh returns a refusal that says the system must be connected again.
- Call the provider. The base URL and path come from the registry, the credential is applied in the provider’s auth style and merged last so nothing in the parameters can shadow it. Connect timeout 5 seconds, read timeout 20.
- Return the response, cut at 256 KB. The only error body ever read is a 4xx answering a write, so the model can correct itself; it is clamped to 400 characters.
- Always: move the rejection counter, and write an
integration.callaudit record with the outcome (success, failure or denied), the organisation, the operation, and the parameter names. Parameter values are not recorded.
What this does not cover
Section titled “What this does not cover”- The provider’s own controls. What a key can reach at Xero or HubSpot is set when you create it or approve the OAuth consent, and StoriBot asks only for the scopes the ticked operations need.
- Data the session reads legitimately and then writes into a Stori. That is governed by the share settings on the Stori, described under Sharing.
- Inference. Which model provider sees a prompt, and under what retention terms, is on stori.bot/trust.