> ## Documentation Index
> Fetch the complete documentation index at: https://keyring.docs.composio.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Security model

> Trust boundaries, customer controls, cryptography, fail-closed behavior, and residual risks.

export const TrustBoundaryDiagram = () => <figure className="keyring-diagram simple-request-flow boundary-flow">
    <div className="simple-request-flow__stage" role="img" aria-label="A sealed credential leaves the Composio database and travels to Keyring. Your KMS unseals it inside Keyring, where the plaintext exists only in memory. It leaves Keyring only inside the approved provider request, and the provider's response returns to Composio without the credential.">
      <div className="simple-request-flow__track" aria-hidden="true" />

      <div className="simple-request-flow__nodes">
        <div className="simple-request-flow__node boundary-flow__node--composio">
          <span className="simple-request-flow__node-mark simple-request-flow__node-mark--composio" aria-hidden="true" />
          <b>Composio</b>
          <span className="simple-request-flow__node-caption">Ciphertext only</span>
        </div>
        <div className="simple-request-flow__node boundary-flow__node--keyring">
          <span className="simple-request-flow__node-mark simple-request-flow__node-mark--keyring">
            <svg viewBox="0 0 24 24" aria-hidden="true">
                <path d="M12 3 5 6v5c0 4.5 2.8 7.5 7 9.5 4.2-2 7-5 7-9.5V6L12 3Z" />
                <circle cx="12" cy="11" r="2" />
                <path d="M12 13v3" />
              </svg>
          </span>
          <b>Keyring</b>
          <span className="simple-request-flow__node-caption">Plaintext · memory only</span>
        </div>
        <div className="simple-request-flow__node boundary-flow__node--provider">
          <span className="simple-request-flow__node-mark boundary-flow__node-mark--provider">
            <svg viewBox="0 0 24 24" aria-hidden="true">
                <circle cx="12" cy="12" r="8" />
                <ellipse cx="12" cy="12" rx="3.4" ry="8" />
                <path d="M4 12h16" />
              </svg>
          </span>
          <b>Provider</b>
          <span className="simple-request-flow__node-caption">Approved destination</span>
        </div>
      </div>

      <div className="simple-request-flow__kms-link boundary-flow__db-link" aria-hidden="true" />
      <span className="boundary-flow__db-envelope" aria-hidden="true">
        <svg viewBox="0 0 24 24">
              <rect x="3" y="5.5" width="18" height="13" rx="2" />
              <path d="M4.5 7.5 12 13l7.5-5.5" />
              <circle cx="12" cy="15.4" r="1.4" />
            </svg>
      </span>
      <div className="simple-request-flow__kms boundary-flow__db">
        <span className="simple-request-flow__node-mark boundary-flow__node-mark--db">
          <svg viewBox="0 0 24 24" aria-hidden="true">
              <ellipse cx="12" cy="6" rx="7" ry="2.7" />
              <path d="M5 6v12c0 1.5 3.1 2.7 7 2.7s7-1.2 7-2.7V6" />
              <path d="M5 12c0 1.5 3.1 2.7 7 2.7s7-1.2 7-2.7" />
            </svg>
        </span>
        <b>Composio DB</b>
        <span className="simple-request-flow__node-caption">Sealed envelopes</span>
      </div>

      <div className="simple-request-flow__kms-link boundary-flow__kms-link" aria-hidden="true" />
      <div className="simple-request-flow__kms-packet boundary-flow__kms-packet" aria-hidden="true" />
      <div className="simple-request-flow__kms boundary-flow__kms">
        <span className="simple-request-flow__node-mark simple-request-flow__node-mark--kms">
          <svg viewBox="0 0 24 24" aria-hidden="true">
              <circle cx="7.5" cy="12" r="3.5" />
              <path d="M11 12h10m-3 0v3m-3-3v2" />
            </svg>
        </span>
        <b>KMS</b>
      </div>

      <div className="boundary-flow__packet" aria-hidden="true">
        <div className="boundary-flow__payload boundary-flow__payload--sealed">
          <svg viewBox="0 0 24 24" aria-hidden="true">
              <rect x="5.5" y="10.5" width="13" height="8.5" rx="2" />
              <path d="M8.5 10.5V8.2a3.5 3.5 0 0 1 7 0v2.3" />
              <path d="M12 13.6v2" />
            </svg>
          <span>Credential</span>
          <small>sealed · Composio can't read it</small>
        </div>
        <div className="boundary-flow__payload boundary-flow__payload--plain">
          <svg viewBox="0 0 24 24" aria-hidden="true">
              <path d="M12 4.5 13.2 8l3.5 1.2-3.5 1.2L12 13.9l-1.2-3.5L7.3 9.2l3.5-1.2Z" />
              <path d="M18.5 14.2l.7 1.9 1.9.7-1.9.7-.7 1.9-.7-1.9-1.9-.7 1.9-.7Z" />
              <path d="M6.2 15.7l.6 1.5 1.5.6-1.5.6-.6 1.5-.6-1.5-1.5-.6 1.5-.6Z" />
            </svg>
          <span>Credential</span>
          <small>plaintext · inside Keyring only</small>
        </div>
        <div className="boundary-flow__payload boundary-flow__payload--request">
          <svg viewBox="0 0 24 24" aria-hidden="true">
              <circle cx="8" cy="12" r="3.2" />
              <path d="M11.2 12h9.3m-3 0v2.8m-3-2.8v2" />
            </svg>
          <span>Provider request</span>
          <small>credential travels inside it</small>
        </div>
        <div className="boundary-flow__payload boundary-flow__payload--response">
          <svg viewBox="0 0 24 24" aria-hidden="true">
              <circle cx="12" cy="12" r="8" />
              <path d="m8.5 12.2 2.4 2.4 4.6-4.8" />
            </svg>
          <span>Response</span>
          <small>no credential inside</small>
        </div>
      </div>
    </div>
  </figure>;

export const AuthGateDiagram = ({scenario}) => {
  const AUTH_GATE_CHECKS = [{
    key: "signature",
    label: "Signature · Composio JWKS"
  }, {
    key: "issuer",
    label: "Issuer · backend.composio.dev"
  }, {
    key: "audience",
    label: "Audience · your organization ID"
  }, {
    key: "expiry",
    label: "Expiry · under one hour"
  }, {
    key: "jti",
    label: "Token ID · not seen before"
  }, {
    key: "policy",
    label: "Toolkit + destination policy"
  }];
  return <figure className={`keyring-diagram simple-request-flow auth-gate auth-gate--auth-${scenario}`}>
      <div className="simple-request-flow__stage" role="img" aria-label={scenario === "valid" ? "A signed Composio request reaches Keyring and passes six checks in order: signature, issuer, organization audience, expiry, unused token ID, and toolkit plus destination policy. Only then does Keyring ask your KMS to unwrap the credential." : "A request carrying a token for another organization reaches Keyring. It passes the signature and issuer checks, fails the audience check, and is denied before any KMS call or decryption."}>
        <div className="dek-flow__track auth-gate__track" aria-hidden="true" />

        <div className="simple-request-flow__nodes auth-gate__nodes">
          <div className="simple-request-flow__node auth-gate__node--composio">
            <span className="simple-request-flow__node-mark simple-request-flow__node-mark--composio" aria-hidden="true" />
            <b>Composio</b>
          </div>
          <div className="simple-request-flow__node auth-gate__node--keyring">
            <span className="simple-request-flow__node-mark simple-request-flow__node-mark--keyring">
              <svg viewBox="0 0 24 24" aria-hidden="true">
              <path d="M12 3 5 6v5c0 4.5 2.8 7.5 7 9.5 4.2-2 7-5 7-9.5V6L12 3Z" />
              <circle cx="12" cy="11" r="2" />
              <path d="M12 13v3" />
            </svg>
            </span>
            <b>Keyring</b>
            <span className="simple-request-flow__node-caption">Your Cloud</span>
          </div>
        </div>

        <ul className="auth-gate__checks" aria-hidden="true">
          {AUTH_GATE_CHECKS.map(check => <li key={check.key} className={`auth-gate__check auth-gate__check--${check.key}`}>
              <span className="auth-gate__mark auth-gate__mark--ok" aria-hidden="true">
                <svg viewBox="0 0 24 24">
                  <path d="m6 12.5 4 4 8-9" />
                </svg>
              </span>
              <span className="auth-gate__mark auth-gate__mark--bad" aria-hidden="true">
                <svg viewBox="0 0 24 24">
                  <path d="m7 7 10 10m0-10L7 17" />
                </svg>
              </span>
              {check.label}
            </li>)}
        </ul>

        <div className="simple-request-flow__kms-link auth-gate__kms-link" aria-hidden="true" />
        <div className="simple-request-flow__kms-packet auth-gate__kms-packet" aria-hidden="true" />
        <div className="simple-request-flow__kms auth-gate__kms">
          <span className="simple-request-flow__node-mark simple-request-flow__node-mark--kms">
            <svg viewBox="0 0 24 24" aria-hidden="true">
            <circle cx="7.5" cy="12" r="3.5" />
            <path d="M11 12h10m-3 0v3m-3-3v2" />
          </svg>
          </span>
          <b>KMS</b>
        </div>

        <div className="auth-gate__packet" aria-hidden="true">
          <div className="auth-gate__payload auth-gate__payload--request">
            <svg viewBox="0 0 24 24" aria-hidden="true">
              <path d="M12 3 5 6v5c0 4.5 2.8 7.5 7 9.5 4.2-2 7-5 7-9.5V6L12 3Z" />
              <path d="m9 11.5 2 2 4-4.5" />
            </svg>
            <span>Tool call</span>
            <small>{scenario === "valid" ? "signed Composio token" : "token for another organization"}</small>
          </div>
          <div className="auth-gate__payload auth-gate__payload--allowed">
            <svg viewBox="0 0 24 24" aria-hidden="true">
              <circle cx="8" cy="12" r="3.2" />
              <path d="M11.2 12h9.3m-3 0v2.8m-3-2.8v2" />
            </svg>
            <span>Decrypt allowed</span>
            <small>every check passed first</small>
          </div>
          <div className="auth-gate__payload auth-gate__payload--denied">
            <svg viewBox="0 0 24 24" aria-hidden="true">
              <circle cx="12" cy="12" r="8" />
              <path d="m9 9 6 6m0-6-6 6" />
            </svg>
            <span>Denied</span>
            <small>stopped before decryption</small>
          </div>
        </div>
      </div>
    </figure>;
};

Keyring's security objective is narrow: a reusable plaintext credential may exist only inside your
Keyring process and in the approved provider request that consumes it. Composio stores encrypted
credential data, not plaintext credentials or plaintext data-encryption keys.

## Trust boundary

<TrustBoundaryDiagram />

Keyring authenticates Composio with its published signing keys, uses your KMS for root-key operations,
and applies a reviewed toolkit policy to provider destinations. It still treats every requested URL,
encrypted credential, request instruction, and provider response as untrusted input.

## What this guarantees

<Columns cols={2}>
  <Card title="No reusable plaintext at Composio" icon="lock-keyhole">
    Composio stores encrypted credentials that are unusable without your Keyring and KMS permission.
  </Card>

  <Card title="A kill switch you own" icon="power">
    Stop Keyring or revoke its KMS access to stop all use. Reviewed policy can disable selected
    toolkits or operation classes while leaving others available.
  </Card>

  <Card title="Signed, time-limited requests" icon="badge-check">
    Keyring verifies every request against Composio's published keys and your organization ID.
    Tokens are valid for no more than one hour.
  </Card>

  <Card title="Credentials stay toolkit-bound" icon="link">
    A credential sealed for one toolkit cannot be relabeled and used for another provider.
  </Card>

  <Card title="A failed check stops the call" icon="circle-stop">
    Authentication and provider policy run before decryption. A denial never falls back to broader
    access.
  </Card>

  <Card title="Stateless and replaceable" icon="refresh-cw">
    Keyring stores no credential database. Instances can restart or scale without moving plaintext
    credential state.
  </Card>
</Columns>

## Authentication

Configure the production Composio issuer and your organization ID:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
AUTH_JWKS_URL=https://backend.composio.dev/.well-known/jwks.json
AUTH_ISSUER=https://backend.composio.dev
AUTH_AUDIENCE=<COMPOSIO_ORG_ID>
AUTH_JWT_ALGORITHMS=RS256
```

<AuthGateDiagram scenario="valid" />

Keyring verifies the signature, issuer, organization audience, allowed algorithm, unique token ID, and
time window on every authenticated request. Invalid or expired tokens receive a generic denial; tokens
and decoded token payloads are not logged.

<Info>
  A valid Composio token identifies the caller and organization. It does not authorize every
  provider destination. Toolkit, destination, operation, redirect, and secret-placement policy are
  checked separately for each request.
</Info>

### A failed check stops the call

<AuthGateDiagram scenario="denied" />

A token issued for another organization passes the signature and issuer checks but fails the audience
check. Keyring returns a generic denial, makes no KMS call, and never creates plaintext.

## Cryptographic controls

| Control            | Protection                                                              |
| ------------------ | ----------------------------------------------------------------------- |
| Stored credentials | AES-256-GCM envelope encryption                                         |
| Data keys          | Wrapped by customer-owned AWS KMS, GCP Cloud KMS, or Vault Transit keys |
| New credential     | Browser-side JWE with RSA-OAEP-256 and A256GCM                          |
| Context binding    | Credential purpose and toolkit are cryptographically bound              |
| Key selection      | Explicit key allowlists; unknown or retired key references fail closed  |
| Key separation     | Credential, authorization-gate, and transfer keys must remain distinct  |

## Customer controls

* **Global stop:** stop the Keyring service or revoke its KMS permission.
* **Selective stop:** use a reviewed policy to disable a toolkit, destination, or operation class.
* **Credential scope:** connect Keyring only to the Composio projects that should use it.
* **Evidence:** export every credential use and token operation to your audit collector.

With `AUDIT_DURABILITY=required`, a sensitive operation cannot report success unless your collector
acknowledges its audit event. Composio therefore cannot successfully use or refresh a credential through
Keyring without producing customer-visible evidence.

## Process and container hardening

The official container runs as a non-root user in a distroless image with no shell or package manager.
The Helm chart defaults to a read-only root filesystem, drops Linux capabilities, blocks privilege
escalation, and applies the runtime-default seccomp profile.

These defaults complement—but do not replace—your network policy, image controls, KMS IAM, TLS
termination, and host or cluster hardening.

## Residual risks

<AccordionGroup>
  <Accordion title="Provider error bodies can contain unexpected sensitive data">
    Keyring protects known credential fields in provider errors, but a provider could place a secret
    in an unexpected field. Treat provider error bodies and protected operational logs as sensitive.
  </Accordion>

  <Accordion title="Destination validation is not DNS pinning">
    Keyring blocks direct local and private destinations and requires a toolkit-approved origin. Add
    DNS and egress controls appropriate to your environment for defense in depth.
  </Accordion>

  <Accordion title="Opaque streaming bodies are not inspected">
    Streaming provider payloads pass through without body inspection. Credentials are injected only
    through approved structured request locations, not arbitrary streaming bodies.
  </Accordion>
</AccordionGroup>
