# Credential injection

How does a sandbox call an authenticated API without holding the credential?

A `secret` Connection placed as `env` or `files` puts its credential inside the
sandbox. A model with a shell can read it there, print it into a transcript,
and use it against anything the Connection's hosts allow, such as a second
repository on the same git host.

A Connection placed as [`inject`](/reference/core/connection/#injected-credentials)
keeps the credential in the claim's egress proxy instead. The sandbox gets a
placeholder value and a CA to trust. For the Connection's injected hosts, and
only those, the proxy terminates TLS with a certificate from that CA, replaces
whatever `Authorization` the request carries, and sets the real one. The
credential is never in the sandbox's environment, filesystem or memory, so
nothing the model runs can print it.

```mermaid
sequenceDiagram
    participant S as Sandbox
    participant P as Egress proxy
    participant H as api.github.com
    S->>P: CONNECT api.github.com:443
    P-->>S: 200, then TLS with a leaf from the claim CA
    S->>P: GET /repos/acme/widgets<br/>Authorization: token omnia-injected-github
    P->>P: Host matches, a rule allows it,<br/>placeholder replaced
    P->>H: GET /repos/acme/widgets<br/>Authorization: Bearer (from the Secret)
    H-->>P: 200
    P-->>S: 200
```

The model can still *use* the credential: anything it sends to an injected host
goes out authenticated. Request rules bound that use. With
[rules](/reference/core/connection/#request-rules), the proxy forwards only the
methods and path prefixes you list, so a token that GitHub would honour on
every repository the account can see works on one repository through this
sandbox. The proxy matches the path as the upstream receives it, which is why
it refuses a path with `..` or an empty segment rather than tidying it.

Terminating TLS has costs, and the design keeps them to the injected hosts:

- **A CA the sandbox trusts.** The CA is per claim, name-constrained to the
  injected hosts and to no IP address, and its key exists only in a Secret
  mounted into the proxy. A client that enforces name constraints, as Go,
  OpenSSL and browsers do, rejects a certificate it signs for any other name.
  So a stolen key could impersonate those hosts, and only to that one claim's
  sandbox.
- **Trust has to reach every tool.** Go, OpenSSL, curl and Node.js add a CA
  from an environment variable. Python and git replace their roots, so the
  operator gives them a bundle of its own image's roots plus the CA. A root
  that only the sandbox image carries drops out of that bundle.
- **Some protocols cannot be inspected.** The proxy speaks HTTP/1.1 on an
  injected host, so a client that will only speak HTTP/2, as gRPC does, fails
  rather than send traffic the proxy cannot inspect. Request
  signing schemes (AWS SigV4, for example) cannot be injected, because the
  proxy cannot re-sign a request it did not build; those credentials stay
  `env` or `files`.

A Connection cannot mix `inject` with `env` or `files`. Mixing would put the
Secret the proxy keeps back into the sandbox. A service that needs both forms
is two Connections.

## Related pages

- [Connection](/reference/core/connection/#injected-credentials): the
  `inject` fields, the objects the operator creates and the proxy's refusals
- [Keep a credential out of the sandbox](/how-to/tools/keep-a-credential-out-of-the-sandbox/):
  an injecting Connection, step by step
- [Sandbox security](/explanation/security/sandboxes/): what a sandbox can
  reach, and the other layers around it