> ## 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.

# Egress policy

> Choose what a Sailbox may connect to, and what happens to the HTTPS requests it sends

An egress policy limits which hosts a Sailbox can connect to, and can add
credentials to the HTTPS requests it sends. Connections into the Sailbox are
covered by [Access control](/sailboxes-access-control). The one setting on
this page that reaches them is `no_network`, which stops every connection in
both directions.

A Sailbox gets its policy when it is created and can be given a new one at
any time. Without one, it can reach any host. Changing one Sailbox's policy
does not affect any other.

This page shows what a policy can say and how to set one. The
[reference](/sailboxes-egress-policy-reference) has the exact rules for each
field.

## The document

A policy is a JSON document with these optional fields:

| Field          | What it does                                                                                                                                                                               |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `allowlist`    | The hosts the Sailbox may reach. Leave it out to allow every host. An empty list allows none.                                                                                              |
| `blocked`      | Hosts or addresses to remove from what the allowlist, or the open internet, allows.                                                                                                        |
| `rules`        | Changes to the HTTPS requests sent to a host, such as adding a credential. Rules do not affect which hosts are reachable.                                                                  |
| `no_network`   | No outbound connections, no name lookups, and no exposed ports or SSH. Cannot be combined with the other fields.                                                                           |
| `missing_alpn` | Rarely needed: for a client that does not say which HTTP version it speaks when it connects to a host under `rules`. See [the reference](/sailboxes-egress-policy-reference#missing-alpn). |

An entry in `allowlist` or `blocked` is a hostname (`api.github.com`), a
wildcard for a host's direct subdomains (`*.github.com` matches
`api.github.com` but not `github.com` or `a.b.github.com`), an IPv4 address,
or an IPv4 range (`203.0.113.0/24`). A hostname entry allows every kind of
connection to that host, including SSH and plain HTTP.

Let the Sailbox reach only package registries and GitHub:

```json theme={null}
{
  "allowlist": [
    "pypi.org",
    "files.pythonhosted.org",
    "github.com",
    "api.github.com"
  ]
}
```

Reach GitHub and its direct subdomains, except Gist:

```json theme={null}
{ "allowlist": ["github.com", "*.github.com"], "blocked": ["gist.github.com"] }
```

Reach GitHub and add a token to every request sent to its API:

```json theme={null}
{
  "allowlist": ["github.com", "api.github.com"],
  "rules": {
    "api.github.com": [
      {
        "request": {
          "set": {
            "headers": { "authorization": "Bearer ${secrets.GITHUB_TOKEN}" }
          }
        }
      }
    ]
  }
}
```

`${secrets.GITHUB_TOKEN}` names a secret stored with Sail. The Sailbox never
sees its value, and a document that references a secret must be
[saved](#saved-policies) before a Sailbox can use it.
[Credential injection](/sailboxes-credentials) walks through that flow.

<h2 id="no-network">
  No egress and no network
</h2>

`{"allowlist": []}` stops outbound connections. `{"no_network": true}` also
stops inbound ones.

* No egress, `{"allowlist": []}`, also stops name lookups. You can still
  expose a port or use SSH.
* No network, `{"no_network": true}`, is for work that should have no
  network at all. It cannot be combined with other fields or set on a Sailbox
  that exposes a port or SSH. Running commands, the shell, and mounted
  volumes keep working.

<CodeGroup>
  ```python Python theme={null}
  box = sail.Sailbox.create(app=app, name="offline-job", egress_policy=sail.EgressPolicy.no_egress())
  box = sail.Sailbox.create(app=app, name="sealed-job", egress_policy=sail.EgressPolicy.no_network())
  ```

  ```typescript TypeScript theme={null}
  const offline = await Sailbox.create({
    app,
    name: "offline-job",
    egressPolicy: EgressPolicy.noEgress(),
  });
  const sealed = await Sailbox.create({
    app,
    name: "sealed-job",
    egressPolicy: EgressPolicy.noNetwork(),
  });
  ```

  ```rust Rust theme={null}
  let offline = client
      .create_sailbox(
          &CreateSailboxRequest {
              app_id: app.id.clone(),
              name: "offline-job".into(),
              egress_policy: Some(EgressPolicyDocument::no_egress().into()),
              ..Default::default()
          },
          /* timeout */ None,
      )
      .await?;
  let sealed = client
      .create_sailbox(
          &CreateSailboxRequest {
              app_id: app.id,
              name: "sealed-job".into(),
              egress_policy: Some(EgressPolicyDocument::no_network().into()),
              ..Default::default()
          },
          /* timeout */ None,
      )
      .await?;
  ```

  ```bash CLI theme={null}
  sail box create --app web-demo --name offline-job --no-egress
  sail box create --app web-demo --name sealed-job --no-network
  ```
</CodeGroup>

## Setting a policy

Pass a document, or a [saved policy](#saved-policies), as `egress_policy`
when you create a Sailbox (`egressPolicy` in TypeScript). Each SDK builds the
common documents for you: `allow_all()` is `{}`, `allow_only(...)` is a
document with only an `allowlist`, `no_egress()` is `{"allowlist": []}`, and
`no_network()` is `{"no_network": true}`.

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

  app = sail.App.find(name="web-demo", mint_if_missing=True)
  box = sail.Sailbox.create(
      app=app,
      name="agent",
      egress_policy={"allowlist": ["pypi.org", "files.pythonhosted.org", "github.com", "api.github.com"]},
  )

  # The same allowlist, built for you:
  box = sail.Sailbox.create(
      app=app,
      name="agent",
      egress_policy=sail.EgressPolicy.allow_only("pypi.org", "files.pythonhosted.org", "github.com", "api.github.com"),
  )
  ```

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

  const app = await App.find("web-demo", { mintIfMissing: true });
  const box = await Sailbox.create({
    app,
    name: "agent",
    egressPolicy: {
      allowlist: [
        "pypi.org",
        "files.pythonhosted.org",
        "github.com",
        "api.github.com",
      ],
    },
  });

  // The same allowlist, built for you:
  const sameBox = await Sailbox.create({
    app,
    name: "agent",
    egressPolicy: EgressPolicy.allowOnly(
      "pypi.org",
      "files.pythonhosted.org",
      "github.com",
      "api.github.com",
    ),
  });
  ```

  ```rust Rust theme={null}
  use sail::{CreateSailboxRequest, EgressPolicyDocument};

  let app = client.find_app("web-demo", /* mint_if_missing */ true).await?;
  let sb = client
      .create_sailbox(
          &CreateSailboxRequest {
              app_id: app.id,
              name: "agent".into(),
              egress_policy: Some(
                  EgressPolicyDocument::allow_only(["pypi.org", "files.pythonhosted.org", "github.com", "api.github.com"])?.into(),
              ),
              ..Default::default()
          },
          /* timeout */ None,
      )
      .await?;
  ```

  ```bash CLI theme={null}
  sail box create --app web-demo --name agent \
    --egress-allow-only pypi.org,files.pythonhosted.org,github.com,api.github.com

  # Any document, from a file:
  sail box create --app web-demo --name agent --egress-policy-file policy.json
  ```
</CodeGroup>

* A document that breaks a rule in the reference fails the call, naming the
  entry to fix, before a Sailbox is created.
* `no_network` cannot be combined with `ingress_ports`; the other documents
  can.
* A Sailbox created from a checkpoint starts with the policy its source has
  at that moment. After that the two are independent.

### Replacing the policy

`set_egress_policy` replaces a Sailbox's policy and returns the new one.
`clear_egress_policy` removes all restrictions and rules.

<CodeGroup>
  ```python Python theme={null}
  box.set_egress_policy(sail.EgressPolicy.allow_only("pypi.org"))
  box.set_egress_policy(policy)  # a saved policy, see below
  box.clear_egress_policy()
  ```

  ```typescript TypeScript theme={null}
  await box.setEgressPolicy(EgressPolicy.allowOnly("pypi.org"));
  await box.setEgressPolicy(policy); // a saved policy, see below
  await box.clearEgressPolicy();
  ```

  ```rust Rust theme={null}
  sb.set_egress_policy(EgressPolicyDocument::allow_only(["pypi.org"])?).await?;
  sb.set_egress_policy(&policy).await?; // a saved policy, see below
  sb.clear_egress_policy().await?;
  ```

  ```bash CLI theme={null}
  sail box egress-policy set <sailbox-id> --egress-allow-only pypi.org
  sail box egress-policy set <sailbox-id> --egress-policy <policy-id>
  sail box egress-policy clear <sailbox-id>
  ```
</CodeGroup>

* The new policy applies to connections opened after the call. Connections
  that are already open keep the previous policy until they close, except
  under `no_network`, which stalls them until a later policy restores
  network access.
* A sleeping or paused Sailbox takes the new policy when it next runs.
* `no_network` is refused while the Sailbox exposes ports or SSH.

### Reading it back

Every Sailbox reports the policy it runs under: the document in force, plus
the id and name of the saved policy it came from. The handle `create` returns
does not carry the policy; fetch the Sailbox to read it.

<CodeGroup>
  ```python Python theme={null}
  box = sail.Sailbox.get(box.sailbox_id)
  box.egress_policy.document  # {"allowlist": ["pypi.org"]}, or {} for no restrictions
  box.egress_policy.name  # the saved policy's name, or None for an inline document
  ```

  ```typescript TypeScript theme={null}
  const sb = await Sailbox.get(box.sailboxId);
  sb.egressPolicy?.document; // { allowlist: ["pypi.org"] }, or {} for no restrictions
  sb.egressPolicy?.name; // the saved policy's name, or undefined for an inline document
  ```

  ```rust Rust theme={null}
  let policy = sb.egress_policy().await?;
  policy.document.allowlist; // Some(["pypi.org"]), or None for every host
  policy.name; // the saved policy's name, or None for an inline document
  ```

  ```bash CLI theme={null}
  sail box egress-policy show <sailbox-id>
  ```
</CodeGroup>

## Saved policies

A document passed straight to a Sailbox belongs to that Sailbox alone. A
saved policy is a document stored under a name for your whole organization.
Save a policy to reuse it across Sailboxes or to use secrets in its rules.
Only a saved policy can reference a secret, and the secret must exist first.

<CodeGroup>
  ```python Python theme={null}
  policy = sail.EgressPolicy.create(
      "github",
      {
          "allowlist": ["github.com", "api.github.com"],
          "rules": {
              "api.github.com": [
                  {"request": {"set": {"headers": {"authorization": "Bearer ${secrets.GITHUB_TOKEN}"}}}}
              ]
          },
      },
  )
  box = sail.Sailbox.create(app=app, name="agent", egress_policy=policy)

  policy = sail.EgressPolicy.get(policy.id)
  for row in sail.EgressPolicy.list():
      print(row.id, row.name)
  policy.rename("github-readonly")
  box.terminate()
  policy.delete()
  ```

  ```typescript TypeScript theme={null}
  const policy = await EgressPolicy.create("github", {
    allowlist: ["github.com", "api.github.com"],
    rules: {
      "api.github.com": [
        {
          request: {
            set: { headers: { authorization: "Bearer ${secrets.GITHUB_TOKEN}" } },
          },
        },
      ],
    },
  });
  const box = await Sailbox.create({ app, name: "agent", egressPolicy: policy });

  const same = await EgressPolicy.get(policy.id);
  for (const row of await EgressPolicy.list()) console.log(row.id, row.name);
  await policy.rename("github-readonly");
  await box.terminate();
  await policy.delete();
  ```

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

  let document: EgressPolicyDocument = serde_json::from_value(json!({
      "allowlist": ["github.com", "api.github.com"],
      "rules": {
          "api.github.com": [
              { "request": { "set": { "headers": { "authorization": "Bearer ${secrets.GITHUB_TOKEN}" } } } }
          ]
      }
  }))?;
  let policy = client.create_egress_policy("github", &document).await?;
  let sb = client
      .create_sailbox(
          &CreateSailboxRequest {
              app_id: app.id,
              name: "agent".into(),
              egress_policy: Some((&policy).into()),
              ..Default::default()
          },
          /* timeout */ None,
      )
      .await?;

  let same = client.get_egress_policy(policy.id()).await?;
  client.rename_egress_policy(policy.id(), "github-readonly").await?;
  sb.terminate().await?;
  client.delete_egress_policy(policy.id()).await?;
  ```

  ```bash CLI theme={null}
  sail egress-policy create github --file github.json   # the document from the other tabs; --file - reads standard input
  sail box create --app web-demo --name agent --egress-policy <policy-id>

  sail egress-policy show <policy-id>
  sail egress-policy list
  sail egress-policy rename <policy-id> github-readonly
  sail box terminate <sailbox-id>
  sail egress-policy delete <policy-id>
  ```
</CodeGroup>

* A saved policy's document cannot be edited, only its name. To change
  behavior, save a new policy and set it.
* Deleting a policy fails while a Sailbox that is not terminated uses it.
  Give those Sailboxes another policy first. A terminated Sailbox that used
  the deleted policy keeps only its `allowlist` and `blocked` entries, so a
  Sailbox later created from one of its checkpoints starts without the
  policy's rules.
* The listing shows how many Sailboxes use each policy and which secrets it
  names.

## Blocked

`blocked` removes destinations from what the Sailbox may otherwise reach.
Each entry must be narrower than what covers it: a hostname under a `*.`
wildcard in the allowlist, or an address inside an allowed range. Without an
allowlist, only addresses and ranges can be blocked. The
[reference](/sailboxes-egress-policy-reference#blocked) has every accepted
form.

```json theme={null}
{ "allowlist": ["*.github.com"], "blocked": ["gist.github.com"] }
```

## Rules

`rules` maps a host to the rules for the HTTPS requests the Sailbox sends to
it. A rule can change the request, forward it to another host, or answer it.
Plain HTTP and other traffic pass through untouched.

```json theme={null}
{
  "rules": {
    "api.example.com": [
      {
        "match": { "method": "POST", "path": { "prefix": "/v1/" } },
        "request": {
          "set": {
            "headers": { "authorization": "Bearer ${secrets.EXAMPLE_KEY}" }
          }
        }
      },
      { "request": { "set": { "headers": { "x-api-version": "2" } } } }
    ]
  }
}
```

* Hosts are exact (`api.example.com`), a direct-subdomain wildcard
  (`*.example.com`), or `*` for every host not named elsewhere. Use an exact
  host whenever a rule adds a credential.
* A rule does not make its host reachable. With an allowlist, every host
  that has rules must also be in the allowlist.
* `match` narrows a rule by `method`, `path`, `headers`, or `query`. A
  rule without `match` covers every request and must be last in its list.

Each action's fields, templates, forwarding failures, `missing_alpn`, and
the other details are in
[the reference](/sailboxes-egress-policy-reference#rules).

## Harbor

A Harbor task's network allowlist or no-network setting becomes the
Sailbox's egress policy. A task that changes its policy mid-run changes the
Sailbox's policy at that point. See [Harbor](/harbor) for the task features
Sailboxes support.
