
Every security questionnaire that lands on our desk asks some version of the same two questions:
- Can Composio staff read our users' tokens?
- Can another customer reach our data?
I like those questions. They're the right ones to ask anyone who holds OAuth tokens and takes actions with them on your users' behalf, and if I were on the other side of the table I'd ask them too.
I'll answer both questions by tracing a single tool call from your API key to the third-party app. The useful details are where credentials are used and what controls access to them. Then I'll cover a third question I think security teams should ask about prompt injection.
If you'd rather skip the prose and read the policies, trust.composio.dev has the control list, the SOC 2 report, and the pen test summary. This post is the explanation that goes with them.
One tool call, start to finish
Whether it comes from the SDK, an MCP client, or a raw HTTP request, every call takes the same path.
Your agent or application
| API key, scoped to one project
v
Composio edge (TLS, rate limiting, authentication)
| request resolves to org + project + user
v
Orchestration (policy check, token refresh, routing)
| authorized tool call
v
Isolated execution runtime (one container per execution)
| credential attached, HTTPS
v
The third-party application
Question one: can your staff read our tokens?
Start with how a token is stored. OAuth access and refresh tokens sit at rest under AES-256-GCM with envelope encryption. A key-encryption key wraps a per-project key, and the key-encryption key lives in AWS KMS and rotates on its own schedule. The practical effect is that no single key sitting in a config file unlocks everything, which is the property you want from any system that stores other people's credentials.
During execution, the credential is decrypted for that call and used to authenticate the request to the third-party app. The execution runtime exists only for the call and retains no credential afterward. Your application never receives the token, the API never returns it, and the language model never has it in its context.
Connected-account tokens don't come back in API responses. Access tokens, refresh tokens, request tokens, and client secrets for custom auth configs are all withheld. If you need to hit an endpoint we don't already expose as a tool, Proxy Execute makes the call for you with the credential attached, and you get the response back without ever holding the token.
Some developers would rather retrieve the raw token and call the API themselves. This design prevents that, and removes a token-read endpoint as a way to extract credentials. Calls made directly with a raw token would bypass Composio's execution logs and policies. Proxy Execute lets developers call those endpoints while keeping execution on the platform.
For Composio API keys and session keys, we store only hashes. There's no recoverable copy. Even with our own key material, nobody at Composio can reconstruct your key. You see it once, at creation, and that's it.
Tokens are encrypted at rest and decrypted only in the execution path, for the duration of a call. Support and engineering hold no standing route to customer data; privileged access is gated behind approval and logging where it exists at all. Internal services authenticate to each other with OIDC under a zero-trust model, with no shared static secrets between build, hosting, and monitoring. The internal access policies behind those sentences are published on the trust center.
If your team needs to control the encryption keys, we also offer a customer-managed key deployment: a stateless proxy in your cloud, credentials encrypted under keys in your own KMS (AWS KMS, Google Cloud KMS, and HashiCorp Vault Transit are supported), and the proxy as the only component that ever handles plaintext. Composio keeps the control plane but does not hold the key material required to decrypt those credentials. Revoke your master key and every credential goes dead immediately, without waiting on us. It has shipped, but it's set up with you rather than from a toggle, so email us if that's what your team needs.
Question two: can another customer reach our data?
At the edge, the request is bound to an organization, a project, and a user before execution. That binding determines which connected accounts the request can use.
The project is the isolation boundary. Connected accounts, OAuth tokens, auth configs, and triggers each belong to exactly one project, and every database query carries a project filter.
Inside a project, each connected account belongs to one of your end users. Your application sets the user ID when it creates a session. That ID selects the credential at execution time. The model's tool arguments do not determine which user's token the session uses.
Each tool call runs in a container that exists for that call and is gone when it returns. A malicious response from a third-party API is handled somewhere with no durable credentials and no neighbors.
If you serve your own customers, we recommend one project per downstream tenant. That puts a hard boundary between them and lets you set policy for each separately. And if a logical boundary isn't enough for your compliance story, the platform can be self-hosted in your own cloud account and region, single tenant by construction, because nothing is shared with anyone else. Composio Cloud runs in the United States. For EU residency, use a self-hosted deployment in an EU region.
Question three: what stops a prompt injection?
This is the one I'd add to every questionnaire. An agent constantly reads content it didn't write: a web page, an inbound email, a support ticket. If that content carries instructions, the model may follow them using the access you've granted it. The classic name is the confused deputy. Injected content can cause the agent to misuse access it already has.
We don't rely on model instructions to enforce permissions. An instruction to ignore injected content can fail, so Composio enforces permissions in the request and execution layers.
The credential is injected at the execution step, never shown to the agent, so an injection can't leak what it can't read. Permissions are set administratively and evaluated in the request path as code. An administrator sets a ceiling for the organization, a session gets a subset, and you can narrow further with toolkit filters, per-tool enable and disable lists, and behavior tags like readOnlyHint and destructiveHint. If you've said an agent may not delete, it cannot delete, regardless of what a prompt, a tool output, or the model's own reasoning suggests. And a session can be changed while it's running or deleted outright, which kills it and its MCP URL immediately.
A successful injection is still subject to the session's permissions. Grant each session only the tools it needs to limit what an attacker could do with that access.
Data handling and application safeguards
Composio controls which tools an agent can call and which connected accounts it can use. Your application is responsible for content-level safeguards, including sensitive-data filtering and redaction before tool results reach the model. Restrict each session to the tools it needs, and apply any required data loss prevention checks in your application.
By default, execution logs include full request and response payloads and are retained for up to one year. Select "Don't store data" in your project's log storage settings to stop storing payloads for new calls. The audit record remains, including which tool ran, when, whether it succeeded, the supplied user ID, and associated connection and auth-config IDs. Changing the setting does not delete existing payload logs; those remain until they expire.
Temporary files have a separate lifecycle. Files staged for tool execution are deleted after 24 hours, even if their download links expire sooner. Disabling payload logging also does not control what the destination provider retains. The data retention reference explains these settings and lifecycles.
Human approval is yours to build. For anything irreversible or outward-facing, put a checkpoint in your agent's flow before the action fires. Composio gives you the per-action switch; the approval step runs in your agent, not on our platform.
Deleting a connected account immediately stops further API calls through that connection. To also request revocation at the provider, pass ?revoke_on_delete=true. This defaults to false, and upstream revocation depends on provider support. Where necessary, revoke or rotate the credential directly at the provider.
Who's checking
The platform is SOC 2 Type II attested, and the report is available through the trust center. AppSecure Security ran a penetration test of the web app, the developer APIs, and both SDKs against the OWASP Top 10 for web, API, and LLM applications; every critical and high finding was remediated and re-verified, and the executive summary is available on request. Latacora runs our security program with us as virtual CISO, covering architecture review, secure development, and threat modeling across our cloud and source control. And anyone can report a vulnerability through our public disclosure program. We pay bounties.
If your security team has more questions, security@composio.dev goes to a person, and most questionnaires come back inside a day. In the next post I'll cover the other side of this: the settings and code I'd put in place before letting an agent anywhere near a real account.
Get new posts in your inbox
Subscribe for the latest from the Composio blog.
Blog updates only. Privacy policy.