Skip to content

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).

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.

The working session calls the integration proxy, which checks the session record and the organisation's secret before calling the provider. The session never holds the credential. Working session Fargate task, one per session STS credentials, S3 prefixes only No Secrets Manager access Integration proxy AWS Lambda, no model Verify, grant, decrypt, call One organisation per call Provider Xero, HubSpot, Stripe, … Session record DynamoDB: token, status, organisation Credential Secrets Manager, grants as tags Audit log Every call, with its outcome operation + params answer signed request response, capped 1. verify 2. grant, 3. decrypt 4. record
One call. The credential moves only between Secrets Manager and the proxy, and from the proxy to the provider.
  • 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.
  • 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, operation and params, 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.

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.

  1. Authenticate. Load the session record from DynamoDB by id, verify the token, and require a live status.
  2. Rejection budget. If the session has had ten calls refused in a row, refuse this one too. A successful call resets the count.
  3. Resolve the operation against the connector registry. An unknown provider or operation is refused as not permitted.
  4. 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.
  5. 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.
  6. Resolve file parameters for upload operations, from the session’s own S3 prefix.
  7. Load the credential, last. Validate its shape against the provider.
  8. 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.
  9. 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.
  10. 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.
  11. Always: move the rejection counter, and write an integration.call audit record with the outcome (success, failure or denied), the organisation, the operation, and the parameter names. Parameter values are not recorded.
  • 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.