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

# Access Control

> Control preview traffic, signed URLs, outbound access, and request host masking.

Nullspace preview ingress is designed around short-lived signed URLs and
create-time network policy. Use `get_host_info(port)` for signed HTTP and
WebSocket URLs, and use `network` on machine creation for public traffic and
outbound rules.

## Signed host URLs

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  from nullspace import Machine

  with Machine.create(template="base") as machine:
      info = machine.get_host_info(8080)
      print(info.host)
      print(info.url)
      print(info.websocket_url)
      print(info.access_token_expires_at)
  ```

  ```typescript TypeScript SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  import { Machine } from "nullspace";

  const machine = await Machine.create({ template: "base" });
  const info = await machine.getHost(8080);
  console.log(info.host);
  console.log(info.url);
  console.log(info.websocketUrl);
  console.log(info.accessTokenExpiresAt);
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace machine host mch_123 8080 --json
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -X POST "${NULLSPACE_API_URL}/v1/machines/mch_123/host" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{ "port": 8080 }'
  ```
</CodeGroup>

| Field                     | Meaning                                             |
| ------------------------- | --------------------------------------------------- |
| `host`                    | Bare host mapping for a machine port.               |
| `url`                     | Signed HTTP URL when public edge ingress is active. |
| `websocket_url`           | Signed WebSocket URL for the same exposed port.     |
| `access_token_expires_at` | Expiration time for signed edge URLs.               |
| `access_token_transport`  | Token transport; currently `query` for signed URLs. |

<Warning>
  Signed URLs are bearer credentials. Anyone with the URL can use it until it
  expires, subject to the machine network policy.
</Warning>

## Require a traffic token

Set `network.allow_public_traffic` to `False` when preview URLs should require
a private traffic credential in addition to any signed edge URL token.

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  machine = Machine.create(
      template="base",
      network={"allow_public_traffic": False},
  )

  token = machine.traffic_access_token
  url = machine.get_url(8080)
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -fsS \
    -H "x-nullspace-traffic-access-token: ${NULLSPACE_TRAFFIC_ACCESS_TOKEN}" \
    "${NULLSPACE_PUBLIC_URL}"
  ```
</CodeGroup>

The `traffic_access_token` is returned only at create time. Store it like an
application secret if you need to make later requests through private public
URLs.

<Note>
  Edge-owned ingress uses signed `edge_token` URLs returned by
  `get_host_info()`. When `allow_public_traffic` is false, send
  `x-nullspace-traffic-access-token` with that SDK-returned URL. Treat both the
  URL token and traffic token as secrets.
</Note>

## Custom preview proxy tokens

Use `machine.create_preview_proxy_target(port)` when your application proxy
should hold the Nullspace credential instead of sending signed URLs to browsers.
The response includes marker-only upstream URLs, `x-nullspace-preview-proxy-token`,
HTTP/WebSocket-specific token values, and the forwarded headers your proxy should
preserve.

If `allow_public_traffic` is false, the proxy target response reports that a
traffic token is required and returns the traffic header name. It does not return
the private traffic token value; use the `traffic_access_token` from machine
creation as a separate upstream header.

## Mask the upstream Host header

Some development servers route by `Host`. Use `mask_request_host` to override
the `Host` header sent to the machine service.

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
machine = Machine.create(
    template="base",
    network={
        "mask_request_host": "localhost:${PORT}",
    },
)
```

`${PORT}` is substituted with the exposed machine port for each proxied
request.

## Control outbound access

Disable all outbound network access with `internet_access=False`, or use
CIDR-based allow/deny rules when the deployment supports network policy.

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
machine = Machine.create(
    template="base",
    internet_access=True,
    network={
        "deny_out": ["0.0.0.0/0"],
        "allow_out": ["10.0.0.0/8"],
    },
)
```

| Setting                 | Effect                                                 |
| ----------------------- | ------------------------------------------------------ |
| `internet_access=False` | No machine network connectivity.                       |
| `deny_out`              | IPv4 CIDRs to block for outbound traffic.              |
| `allow_out`             | IPv4 CIDRs to allow when paired with broad deny rules. |

## WebSockets and SSH relay

Signed WebSocket URLs use the same public edge and access controls as signed
HTTP URLs. Missing edge tokens return `401`, expired tokens return `410`, and
tokens scoped to the wrong machine, port, or transport return `401`.

Default SSH access uses the certificate-backed relay, not a signed edge
`websocket_url`. Use `nullspace ssh` to mint a short-lived OpenSSH user
certificate for a local public key and connect through the relay:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
nullspace ssh mch_123
nullspace machine ssh-access mch_123 --print-command
```

The legacy port-22 WebSocket proxy fallback treats the signed `websocket_url` as
a bearer credential, so prefer the relay whenever it is available.

## Embedding and browser headers

Direct preview cookies are SameSite=Lax and are intended for top-level browser
navigation, not third-party iframe embedding. Machine X-Frame-Options and
Content-Security-Policy headers pass through to the browser; Nullspace preview
edge does not add an iframe-safe frame policy for direct preview links.

Preview CORS remains owned by the machine app or customer-run proxy. No preview
skip-warning, disable-CORS, or skip-activity header is supported by Nullspace
edge in the current launch.

## Related

* [Preview URLs](./preview-urls)
* [Custom preview proxy](./custom-preview-proxy)
* [Token model](./token-model)
* [WebSockets](./websockets)
* [SSH access](../access/ssh)
* [Environment variables](../reference/env-vars)
