> ## 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 created by your user, so
  that Sail can identify the creator. A service key, which belongs to the
  organization rather than to a member, cannot create or operate one.

Code running inside a Sailbox can read that Sailbox, sleep it, schedule its
wake, and manage its cron jobs without an API key. See
[guest identity](/sailbox-sdk#guest-identity).

<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()
            },
        )
        .await?;
    ```
  </CodeGroup>
</div>

## Make a web server public

A Sailbox does not accept 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()
            },
        )
        .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>

Pass the same allowlist on the port when you create the Sailbox:
`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 you can use the same
command to tighten or relax access. An app name does not have to exist yet,
and names from other organizations never match.

An app-name allowlist matches on the identity of the calling Sailbox. When one
Sailbox calls another Sailbox's listener, Sail adds the calling Sailbox's
identity to the request, so a request from an allowlisted Sailbox is accepted.

## 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 have no app identity, so a TCP allowlist can contain only
addresses and ranges. 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

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