What actually holds
- Credentials for apps you connect through the interface stay in Nango, the integration provider behind Receipt Connect. Receipt stores a pointer, not the secret. Two import-only connection kinds are the exception, and they are covered below.
- MCP client configs are token-free by construction. The generated config is checked before it is written, and the check refuses anything containing the session token or credential material.
- A sandbox worker never receives the provider keys behind your connected apps. It gets generated CLI configuration and a Receipt credential helper.
- Writes need two independent gates — the token’s scope and the connection’s own allowlist. See Tools and permissions.
- A token is bound to one organization and one workspace, and workspace membership is re-checked on every call.
The tokens
Every call into the gateway carries a Receipt Connect token: an HS256 JWT with an issuer ofreceipt, an audience of receipt-connect, one organization id, one workspace id, a scope list and an expiry. Nothing else authenticates a gateway call, with one exception: POST /connect/nango/webhook, which the integration provider calls, is verified by an HMAC-SHA256 signature over the raw request body instead.
Three scopes
connect:credential is the important one. All four execution routes require it, so a token without it can inspect the gateway but can never run a tool.
Which surface carries which token
The minimum lifetime any token can be issued with is 60 seconds; the default, where a caller names none, is 12 hours.
Web tokens are minted fresh on every server call and never written down, so their lifetime is close to academic: the organization-level one expires in 10 minutes, and the workspace token it is exchanged for carries the 12-hour default. The CLI’s 12-hour token is written to disk and reused until it expires — which is why the two are asymmetric.
Why a browser session cannot run a tool
The web token carries onlyconnect:read and connect:write — never connect:credential. A signed-in browser can therefore connect an app, edit a connection’s allowlist, manage workspace members and read activity — each subject to a second check on your workspace role — but it cannot list gateway tools, call one, or materialize a provider credential. Tool execution belongs to CLI, MCP-bridge and sandbox tokens only.
What a stolen CLI session file can do
receipt setup writes ~/.receipt/session.json with mode 0600. It holds a live 12-hour bearer token in plaintext, with all three scopes, bound to one workspace.
Do not copy the file between machines, commit it, or attach it to a bug report. If you believe one has leaked, disconnect the affected connections or remove that account from the token’s workspace — those are the workspace-scoped remedies — or ask your operator to rotate the signing secret, which signs everyone out of the gateway at once.
What a stolen server environment can do
The honest form of the claim is narrow: an agent’s environment, an MCP client config and a sandbox never contain a Receipt Connect provider credential. The server’s own environment is a different matter, and it is not harmless.
These are server-side values. They are never sent to a browser, a CLI, a client config or a sandbox.
Where credentials actually live
A connection row stores an encrypted payload whose contents depend on its kind, and Receipt Connect connections sets out all four kinds. Only two of them hold a real secret:aws-credential-process and github-token.
Both are explicit, user-initiated imports. Neither is created from the web interface — they arrive through POST /connect/credential/github/import and POST /connect/credential/aws/import — and neither ever publishes a tool to the aggregate MCP server, which only ever lists nango-reference connections in the valid state. Everything you connect through the interface is a nango-reference: the provider plus Nango’s integration and connection ids, a pointer rather than a secret.
The encryption
Both the references and the two imports live in theorg_connection_secret table as ciphertext, iv, auth_tag and key_version, encrypted with AES-256-GCM using a 12-byte IV and key version 1. The wrapping key comes from deployment configuration (RECEIPT_CONNECTION_ENCRYPTION_KEY_B64) and must decode to exactly 32 bytes, so a database-only leak does not yield plaintext.
Organization provider API keys for models are a separate store with a separate key (
BYOK_ENCRYPTION_KEY_B64), described in Bring your own key. Rotating either wrapping key after material is stored cannot be undone — LLM Gateway configuration has the rule and the errors it produces. For what the deployment stores and sends beyond the gateway, see Data handling and security.What the browser is allowed to replicate
The browser replication layer does not mirror the connection table. The publication declares an explicit column list fororg_connection_secret, so ciphertext, IV, auth tag, key version and internal ownership metadata are never replicated to a client, even though connection status, which the interface does need, lives in the same table. Data handling and security names the eleven columns that are replicated.
What never reaches a sandbox or a client config
- The provider credentials Receipt Connect holds: raw provider API keys, OAuth access and refresh tokens, and provider client secrets. A worker receives generated CLI configuration plus a Receipt credential helper instead.
- The integration provider’s environment secret key, which the server uses to authenticate Receipt’s own calls to it.
- The integrations database URL. Startup hands it to the Receipt server process only; it is not written into CLI artifacts, and it is not one of the variables a sandbox worker’s environment is built from.
- A token inside a generated MCP client config. Before writing one, the CLI serializes it and refuses it if it contains the session token —
refusing to install an MCP client config containing the Receipt session token— or matches a credential-material pattern —refusing to install an MCP client config containing credential material. The bridge is a launcher that reads the saved session at run time instead. - Ambient AWS instance credentials. Sandbox workers run with
AWS_EC2_METADATA_DISABLED=true, so EC2 or ECS instance metadata cannot stand in for a Receipt Connect credential.
A credential does materialize inside a sandbox, briefly. The helper the worker runs asks the gateway for the credential at the moment a command needs it, writes the response to a temporary file created under
umask 077, and deletes that file when the helper exits. None of it is written into the worker’s image, its environment or its task packet.Separately from Receipt Connect, a Factory worker is given the host machine’s own GitHub CLI login and Codex profile so that git and the agent work inside the sandbox. Those come from the machine running the runtime, not from a connected app.The gates on a single call
Four checks run before an action executes, and every one of them runs on each call rather than once per session.1
Scope
Every execution route requires
connect:credential. The aggregate MCP server answers a token without it with 401 and unauthorized. The tool-list and call routes are more specific: a token that is valid for discovery but missing the execution scope gets 403 with the code receipt_connect_execution_scope_missing and the message Receipt could not authorize this task to use the connection. This is a task permission problem; reconnecting the account will not fix it. A write additionally requires connect:write, and without it the call fails with integration action '<tool>' requires connect:write.2
Workspace membership
The caller must still be a member of the token’s workspace. If membership was revoked after the token was issued, the call returns
403 with workspace_membership_required — this is the one revocation-like control that works mid-token.3
Connection lookup
Organization and workspace are part of the same query as the connection selector, so a valid connection id from another organization can never be used as a credential lookup. The opaque tool aliases the aggregate server publishes resolve only against the bindings composed from the caller’s own organization and workspace, so an alias minted anywhere else matches nothing and comes back as
Unknown or unavailable tool — the aggregate server explains how an alias is built.4
Allowlist
The action must be enabled in that connection’s policy, and unknown actions and disabled actions return the same message —
integration action not found or disabled — so a caller cannot probe for hidden tools. Tools and permissions covers how that allowlist is set.What is recorded
Calls throughPOST /connect/call write a tool.called receipt and, on success, a tool.observed receipt into a per-workspace stream, with tool output truncated to 2,000 characters. Receipts are tamper-evident, SHA-256 hash-chained records verified on replay.