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

# Credential injection

> Let a Sailbox use an API key it can never read

Credential injection lets a Sailbox call an API with a key it never sees.
You store an API key with Sail, write an [egress policy](/sailboxes-egress-policy)
rule that says which HTTPS host gets it, and give the policy to a Sailbox. Code
inside sends a normal request with no credential, and Sail adds it on the way
out. Nothing on the Sailbox, including an agent running there, can read the
key.

## Try it

This stores a secret, injects it as a bearer token on requests to
`httpbin.org`, and asks httpbin to echo the request back. Save the policy as
`demo.json`:

```json demo.json theme={null}
{
  "rules": {
    "httpbin.org": [
      {
        "request": {
          "set": {
            "headers": {
              "authorization": "Bearer ${secrets.DEMO_TOKEN}"
            }
          }
        }
      }
    ]
  }
}
```

Then store the secret, save the policy, give it to a Sailbox, and make a
request from inside:

<div className="sail-prompt-shell">
  ```bash theme={null}
  DEMO_TOKEN=me sail secret set DEMO_TOKEN --from-env DEMO_TOKEN
  sail egress-policy create demo --file demo.json
  sail box egress-policy set <sailbox-id> --egress-policy <policy-id>
  sail box exec <sailbox-id> -- curl -s https://httpbin.org/anything -H foo:bar
  ```
</div>

```json Output theme={null}
{
  "headers": {
    "Accept": "*/*",
    "Authorization": "Bearer me",
    "Foo": "bar",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.88.1"
  },
  "method": "GET",
  "url": "https://httpbin.org/anything"
}
```

The `curl` inside the Sailbox never saw the token. Only the request that
httpbin received included it.

The same flow from the SDKs, with a GitHub token:

<CodeGroup>
  ```python Python theme={null}
  import os

  import sail

  sail.Secret.set("GITHUB_TOKEN", os.environ["GITHUB_TOKEN"])

  policy = sail.EgressPolicy.create(
      "github",
      {
          "rules": {
              "api.github.com": [
                  {
                      "request": {
                          "set": {
                              "headers": {
                                  "authorization": "Bearer ${secrets.GITHUB_TOKEN}",
                              },
                          },
                      },
                  },
              ],
          },
      },
  )

  sailbox = sail.Sailbox.get("<sailbox-id>")
  sailbox.set_egress_policy(policy)
  ```

  ```typescript TypeScript theme={null}
  import { EgressPolicy, Sailbox, Secret } from "@sailresearch/sdk";

  await Secret.set("GITHUB_TOKEN", process.env.GITHUB_TOKEN!);

  const policy = await EgressPolicy.create("github", {
    rules: {
      "api.github.com": [
        {
          request: {
            set: {
              headers: {
                authorization: "Bearer ${secrets.GITHUB_TOKEN}",
              },
            },
          },
        },
      ],
    },
  });

  const sailbox = await Sailbox.get("<sailbox-id>");
  await sailbox.setEgressPolicy(policy);
  ```

  ```rust Rust theme={null}
  use sail::{Client, EgressPolicyDocument};
  use serde_json::json;

  let client = Client::from_env()?;

  client
      .set_secret("GITHUB_TOKEN", &std::env::var("GITHUB_TOKEN")?)
      .await?;

  let document: EgressPolicyDocument = serde_json::from_value(json!({
      "rules": {
          "api.github.com": [{
              "request": {
                  "set": {
                      "headers": {
                          "authorization": "Bearer ${secrets.GITHUB_TOKEN}",
                      },
                  },
              },
          }],
      },
  }))?;
  let policy = client.create_egress_policy("github", &document).await?;

  let sailbox = client.sailbox("<sailbox-id>");
  sailbox.set_egress_policy(&policy).await?;
  ```
</CodeGroup>

## Secrets

A secret is a named value that belongs to your organization.

<div className="sail-prompt-shell">
  ```bash theme={null}
  sail secret set OPENAI_API_KEY                       # type the value at a hidden prompt
  sail secret set OPENAI_API_KEY --from-env OPENAI_API_KEY
  op read "op://vault/openai/key" | sail secret set OPENAI_API_KEY   # or pipe it in
  sail secret list
  sail secret delete OPENAI_API_KEY
  ```
</div>

Setting a name that already exists replaces its value. The next matching
request from any Sailbox whose policy uses it gets the new value. Names start
with a letter or digit and may contain letters, digits, `_`, and `-`, up to
128 characters. A value is one non-empty line of text up to 64 KiB, with no tabs, line
breaks, or other control characters.

## Policies

A credential is added by a rule in an egress policy. The rule specifies the
host, optionally narrows the requests it applies to, and sets a header or
query parameter to `${secrets.NAME}`. The
[reference](/reference/egress-policy#rules) has the full document
format.

* Only a saved policy may reference a secret, and the secret must exist before
  the policy is saved. A document passed straight to a Sailbox cannot use one.
* Use an exact host. A wildcard such as `*.example.com` sends the credential
  to every host it matches.

A saved policy can also include an `allowlist`, so one policy limits a Sailbox
to the hosts it needs and adds their credentials. A saved policy's document
cannot be edited. To rotate which hosts get a credential, save a new policy
and set it. To rotate the credential itself, set the secret again.

## Giving a Sailbox the policy

Give the same saved policy to as many Sailboxes as you like.

<div className="sail-prompt-shell">
  ```bash theme={null}
  sail box egress-policy set <sailbox-id> --egress-policy <policy-id>   # replace the whole policy
  sail box egress-policy show <sailbox-id>
  sail box egress-policy clear <sailbox-id>                             # remove all restrictions and rules
  ```
</div>

A set replaces the Sailbox's whole policy and applies to connections opened
after the call. See
[Replacing the policy](/sailboxes-egress-policy#replacing-the-policy) for what
already-open connections see.

## Where secrets live

Secrets are stored by Sail and added as the request leaves the Sailbox.
Nothing inside the Sailbox ever sees the value, and no Sail API returns it:
`sail secret show` and `sail secret list` print names and timestamps only.

<div className="sail-prompt-shell">
  ```bash theme={null}
  sail secret show DEMO_TOKEN
  ```
</div>

```text Output theme={null}
name:        DEMO_TOKEN
created_at:  2026-09-03T01:55:06.18426Z
updated_at:  2026-09-03T01:55:06.18426Z
```

To remove a secret, replace the policy on every Sailbox that uses it, delete
every saved policy that names it, then delete the secret. Sail refuses the
other orders. `sail egress-policy list` shows how many Sailboxes use each
policy and which secrets it names.

## Limitations

Rules apply to HTTPS only and cannot read or change request bodies or
responses. The [reference](/reference/egress-policy#rules) lists
every limit.
