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

# Access Control

> Who can operate a Sailbox, and who can reach what it serves

There are two things to control: who can operate a Sailbox, and who can reach
the services it exposes.

## Choose who can operate the Sailbox

Every Sailbox has a visibility, fixed for its life:

* **Org** (the default) lets anyone in your organization run commands, copy
  files, SSH in, and pause, sleep, checkpoint, or terminate it.
* **Private** restricts all of that to you. Your organization can still see
  the Sailbox in listings, but cannot act on it; an org admin can override that
  for commands and lifecycle operations by giving a reason, which is recorded
  in the audit log. Creating one requires an API
  key minted by you, so that Sail knows who the creator is. A service key,
  which belongs to the organization rather than to a member, cannot create or
  operate one.

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail box create --app my-app --name mybox --visibility private
    ```

    ```python Python theme={null}
    sb = sail.Sailbox.create(app=app, name="mybox", visibility="private")
    ```

    ```typescript TypeScript theme={null}
    const sb = await Sailbox.create({ app, name: "mybox", visibility: "private" });
    ```

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

    let sb = client
        .create_sailbox(
            &CreateSailboxRequest {
                app_id: app.id,
                name: "mybox".into(),
                visibility: Visibility::Private,
                ..Default::default()
            },
            /* timeout */ None,
        )
        .await?;
    ```
  </CodeGroup>
</div>

## Make a web server public

A Sailbox accepts no inbound traffic until you expose a port. Exposing an HTTP
port gives it a public HTTPS URL that anyone can reach, with TLS handled for
you. Nothing inside the Sailbox needs to know about certificates or hostnames.

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail box create --app my-app --name web --port 8000
    sail box address <id> 8000
    ```

    ```python Python theme={null}
    sb = sail.Sailbox.create(app=app, name="web", ingress_ports=[8000])
    print(sb.wait_for_listener(8000, timeout=60).endpoint.url)
    ```

    ```typescript TypeScript theme={null}
    const sb = await Sailbox.create({
      app,
      name: "web",
      ingressPorts: [{ guestPort: 8000, protocol: "http" }],
    });
    const listener = await sb.waitForListener(8000, { timeoutSeconds: 60 });
    if (listener.endpoint?.kind === "http") {
      console.log(listener.endpoint.url);
    }
    ```

    ```rust Rust theme={null}
    use sail::{
        CreateSailboxRequest, IngressPort, IngressProtocol, ListenerEndpoint,
        WaitForListenerOptions,
    };

    let sb = client
        .create_sailbox(
            &CreateSailboxRequest {
                app_id: app.id,
                name: "web".into(),
                ingress_ports: vec![IngressPort {
                    guest_port: 8000,
                    protocol: IngressProtocol::Http,
                    allowlist: Vec::new(),
                }],
                ..Default::default()
            },
            /* timeout */ None,
        )
        .await?;
    let listener = sb
        .wait_for_listener(8000, WaitForListenerOptions::default())
        .await?;
    if let Some(ListenerEndpoint::Http { url }) = listener.endpoint() {
        println!("{url}");
    }
    ```
  </CodeGroup>
</div>

In the SDKs, `expose` and `unexpose` add and remove ports on a running
Sailbox: `sb.expose(8080)` in Python, `await sb.expose(8080)` in TypeScript,
`sb.expose(8080, IngressProtocol::Http, &[]).await?` in Rust.

Add or remove ports on a running Sailbox with `sail box expose <id> <port>`
and `sail box unexpose <id> <port>`. `sail box listeners <id>` shows what is
exposed. HTTP and WebSocket traffic both work, and a sleeping Sailbox wakes
when a request arrives. To serve on your own hostname, see
[Custom Domains](/sailboxes-custom-domains).

## Restrict who can reach it

Pass an allowlist when you expose a port. An entry is an address or range, or
the name of a Sail app, which admits authenticated requests from Sailboxes in
that app. Anything else fails before it reaches the Sailbox.

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail box expose <id> 8080 --allowlist 203.0.113.0/24 --allowlist admin-tools
    ```

    ```python Python theme={null}
    sb.expose(8080, allowlist=["203.0.113.0/24", "admin-tools"])
    ```

    ```typescript TypeScript theme={null}
    await sb.expose(8080, { allowlist: ["203.0.113.0/24", "admin-tools"] });
    ```

    ```rust Rust theme={null}
    use sail::IngressProtocol;

    sb.expose(
        8080,
        IngressProtocol::Http,
        &["203.0.113.0/24".to_string(), "admin-tools".to_string()],
    )
    .await?;
    ```
  </CodeGroup>
</div>

At create time, pass the same allowlist on the port:
`ingress_ports=[sail.IngressPort(8080, allowlist=[...])]` in Python,
`ingressPorts: [{ guestPort: 8080, protocol: "http", allowlist: [...] }]` in
TypeScript, and the `allowlist` field of `IngressPort` in Rust.

Re-exposing a port replaces its whole allowlist, so the same command tightens
or relaxes access. An app name does not have to exist yet, and names from
other organizations never match.

For a Sailbox to pass an app-name allowlist, its request must carry the
identity headers the SDK provides. Inside the calling Sailbox:

```python theme={null}
import requests
import sail

requests.get(url, headers=sail.ingress_auth_headers())
```

From outside, such as a test driving several Sailboxes, fetch the headers for
a Sailbox you own with `source_sb.ingress_auth_headers()`
(`sourceSb.ingressAuthHeaders()` in TypeScript).

## Raw TCP ports

Expose a port as raw TCP for protocols other than HTTP, such as Postgres or a
custom server. You get a public host and port.

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail box expose <id> 5432 --tcp --allowlist 203.0.113.0/24
    ```

    ```python Python theme={null}
    listener = sb.expose(5432, protocol="tcp", allowlist=["203.0.113.0/24"])
    ```

    ```typescript TypeScript theme={null}
    const listener = await sb.expose(5432, {
      protocol: "tcp",
      allowlist: ["203.0.113.0/24"],
    });
    ```

    ```rust Rust theme={null}
    use sail::IngressProtocol;

    let listener = sb
        .expose(5432, IngressProtocol::Tcp, &["203.0.113.0/24".to_string()])
        .await?;
    ```
  </CodeGroup>
</div>

<Warning>
  A raw TCP port has no platform-side authentication. Whatever is listening
  inside the Sailbox is the only access control, so make sure it requires
  credentials.
</Warning>

Raw TCP connections carry no app identity, so a TCP allowlist holds addresses
and ranges only. Exposing a well-known unauthenticated port such as Postgres,
MySQL, or Redis without an allowlist is rejected; pass `--allowlist 0.0.0.0/0 --allowlist ::/0` to confirm you want it open to everyone.

## Connect with a shell

The quickest way into a Sailbox is `sail box shell`. It opens an interactive
terminal over the same channel the CLI uses to run commands.

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail box shell <id>
    ```

    ```python Python theme={null}
    sb.shell()
    ```

    ```typescript TypeScript theme={null}
    await sb.shell();
    ```
  </CodeGroup>
</div>

<Tip>
  While the shell is open, servers the Sailbox runs on localhost and links it
  opens are forwarded to your machine, so you can develop against it without
  exposing anything. Pass `--no-forward` to turn that off.
</Tip>

## SSH access

Reach for SSH when you need a real SSH endpoint rather than a terminal: `scp`
and `rsync`, an editor's remote mode, or port forwarding you control. Enabling
it exposes port 22, which counts against your organization's raw TCP limit.

SSH is organization-scoped. Enabling it exposes port 22, and the Sailbox
trusts your organization's certificate authority, so anyone in the org can
connect with a short-lived certificate for their own key. There are no
per-Sailbox keys to hand out. A private Sailbox is the exception: its SSH
server accepts only its creator's certificates.

<div className="sail-prompt-cli">
  <CodeGroup>
    ```bash CLI theme={null}
    sail box ssh enable <id> --allowlist 203.0.113.0/24
    ssh <name>.sail
    ```

    ```python Python theme={null}
    sb.enable_ssh(allowlist=["203.0.113.0/24"])
    ```

    ```typescript TypeScript theme={null}
    await sb.enableSsh({ allowlist: ["203.0.113.0/24"] });
    ```

    ```rust Rust theme={null}
    use sail::EnableSshOptions;

    sb.enable_ssh(EnableSshOptions {
        allowlist: vec!["203.0.113.0/24".to_string()],
        ..Default::default()
    })
    .await?;
    ```
  </CodeGroup>
</div>

The SDK call enables SSH on the Sailbox. To connect from a machine, run
`sail box ssh alias <id>` there once; the CLI's `enable` does that for you.
