From fe85b28ddb2701be244fb93d172354486ad9caaf Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 22:09:37 +0000 Subject: [PATCH 1/3] docs: document sandbox egress allowlists --- sandbox/sdk/python/reference.mdx | 1 + sandbox/sdk/typescript/reference.mdx | 1 + sandboxes/networking.mdx | 40 ++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/sandbox/sdk/python/reference.mdx b/sandbox/sdk/python/reference.mdx index 28dd277..92d3420 100644 --- a/sandbox/sdk/python/reference.mdx +++ b/sandbox/sdk/python/reference.mdx @@ -60,6 +60,7 @@ Arguments: | `tags` | `dict[str, str] \| None` | Key-value labels for finding related sandboxes. | | `volume_mounts` | `dict[str, str] \| None` | Volume IDs keyed by absolute mount path inside the sandbox. | | `networking` | `list[SandboxNetworkingSpec] \| None` | Port to expose and, optionally, the domain and visibility to serve it on. Omit to expose nothing. See [Sandbox Networking](/sandboxes/networking). | +| `egress` | `SandboxEgressSpec \| None` | Restricts the sandbox's outbound traffic to a list of allowed destinations (hostnames, wildcards, IP literals, or CIDR ranges). Omit to leave internet access unrestricted. See [Restrict outbound traffic](/sandboxes/networking#restrict-outbound-traffic-with-an-egress-allowlist). | | `ttl_seconds` | `int \| None` | Maximum lifetime in seconds, counted from creation. The sandbox is terminated once it elapses. Omit for no limit. See [sandbox lifetime](/sandboxes/overview#sandbox-lifetime). | Returns a `Sandbox` handle. diff --git a/sandbox/sdk/typescript/reference.mdx b/sandbox/sdk/typescript/reference.mdx index c9e5a62..386bd66 100644 --- a/sandbox/sdk/typescript/reference.mdx +++ b/sandbox/sdk/typescript/reference.mdx @@ -64,6 +64,7 @@ Options: | `tags` | `Record \| undefined` | Key-value labels for finding related sandboxes. | | `volume_mounts` | `Record \| undefined` | Volume IDs keyed by absolute mount path inside the sandbox. | | `networking` | `SandboxNetworkingSpec[] \| undefined` | Port to expose and, optionally, the domain and visibility to serve it on. Omit to expose nothing. See [Sandbox Networking](/sandboxes/networking). | +| `egress` | `SandboxEgressSpec \| undefined` | Restricts the sandbox's outbound traffic to a list of allowed destinations (hostnames, wildcards, IP literals, or CIDR ranges). Omit to leave internet access unrestricted. See [Restrict outbound traffic](/sandboxes/networking#restrict-outbound-traffic-with-an-egress-allowlist). | | `ttl_seconds` | `number \| undefined` | Maximum lifetime in seconds, counted from creation. The sandbox is terminated once it elapses. Omit for no limit. See [sandbox lifetime](/sandboxes/overview#sandbox-lifetime). | Returns a `Sandbox` handle. diff --git a/sandboxes/networking.mdx b/sandboxes/networking.mdx index bad133c..dd03ed8 100644 --- a/sandboxes/networking.mdx +++ b/sandboxes/networking.mdx @@ -35,6 +35,46 @@ Sandboxes run untrusted code, so their outbound traffic is isolated by default, Because private address space is blocked, this includes services behind a private load balancer. If a sandbox needs to call a service you run on the same cluster, expose that service on a public domain and call it by hostname; from inside a sandbox it's reached over the internet like any other endpoint. +## Restrict outbound traffic with an egress allowlist + +By default a sandbox can reach anything on the public internet. Pass `egress` at create time to narrow that to a specific set of destinations; all other outbound traffic is denied. Enforcement happens transparently at the network layer, so any client and protocol works without proxy configuration. + +An entry in `allowed_destinations` is one of: + +| Kind | Example | Matches | +| ---- | ------- | ------- | +| Hostname | `api.stripe.com` | Exactly that host | +| Wildcard | `*.github.com` | Any host one label under the domain, but not the domain itself | +| IP literal | `203.0.113.10` | That address | +| CIDR range | `203.0.113.0/24` | Any address in the range | + + +```python Python +sandbox = porter.sandboxes.create( + image="python:3.12-slim", + name="my-sandbox", + egress={ + "allowed_destinations": [ + "api.stripe.com", + "*.github.com", + ], + }, +) +``` + +```typescript TypeScript +const sandbox = await porter.sandboxes.create({ + image: "python:3.12-slim", + name: "my-sandbox", + egress: { + allowed_destinations: ["api.stripe.com", "*.github.com"], + }, +}); +``` + + +An empty `allowed_destinations` list denies all egress. Omit `egress` entirely to leave the sandbox's internet access unrestricted (the default described above still applies: in-cluster services, the cloud metadata endpoint, and private address space remain blocked either way). + ## Configure networking on the cluster From c58644e0a474fb2fe302cdc0157177db2e18c438 Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:59:33 +0000 Subject: [PATCH 2/3] docs: note cluster-level egress enforcement toggle --- sandboxes/networking.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sandboxes/networking.mdx b/sandboxes/networking.mdx index dd03ed8..15202df 100644 --- a/sandboxes/networking.mdx +++ b/sandboxes/networking.mdx @@ -39,6 +39,8 @@ Because private address space is blocked, this includes services behind a privat By default a sandbox can reach anything on the public internet. Pass `egress` at create time to narrow that to a specific set of destinations; all other outbound traffic is denied. Enforcement happens transparently at the network layer, so any client and protocol works without proxy configuration. +Enforcement is off until you turn it on for the cluster. In the Porter Dashboard, open the **Sandbox** tab for your sandbox-enabled cluster, go to **Settings**, and toggle **Enforce egress allowlists** on under **Egress controls**. Until it's on, `egress` on a sandbox is accepted but not enforced, and sandboxes keep their default unrestricted internet access. + An entry in `allowed_destinations` is one of: | Kind | Example | Matches | From d03fd79e4912ae9ef765c0a4c28c4ba33814c6dc Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:45:58 +0000 Subject: [PATCH 3/3] docs: reconcile egress allowlist docs with in-cluster service entries --- sandbox/sdk/python/reference.mdx | 2 +- sandbox/sdk/typescript/reference.mdx | 2 +- sandboxes/networking.mdx | 44 +--------------------------- 3 files changed, 3 insertions(+), 45 deletions(-) diff --git a/sandbox/sdk/python/reference.mdx b/sandbox/sdk/python/reference.mdx index 92d3420..cda0674 100644 --- a/sandbox/sdk/python/reference.mdx +++ b/sandbox/sdk/python/reference.mdx @@ -60,7 +60,7 @@ Arguments: | `tags` | `dict[str, str] \| None` | Key-value labels for finding related sandboxes. | | `volume_mounts` | `dict[str, str] \| None` | Volume IDs keyed by absolute mount path inside the sandbox. | | `networking` | `list[SandboxNetworkingSpec] \| None` | Port to expose and, optionally, the domain and visibility to serve it on. Omit to expose nothing. See [Sandbox Networking](/sandboxes/networking). | -| `egress` | `SandboxEgressSpec \| None` | Restricts the sandbox's outbound traffic to a list of allowed destinations (hostnames, wildcards, IP literals, or CIDR ranges). Omit to leave internet access unrestricted. See [Restrict outbound traffic](/sandboxes/networking#restrict-outbound-traffic-with-an-egress-allowlist). | +| `egress` | `SandboxEgressSpec \| None` | Restricts the sandbox's outbound traffic to a list of allowed destinations (hostnames, wildcards, IP literals, CIDR ranges, or in-cluster Service DNS names). Omit to leave internet access unrestricted. See [Restrict egress with an allowlist](/sandboxes/networking#restrict-egress-with-an-allowlist). | | `ttl_seconds` | `int \| None` | Maximum lifetime in seconds, counted from creation. The sandbox is terminated once it elapses. Omit for no limit. See [sandbox lifetime](/sandboxes/overview#sandbox-lifetime). | Returns a `Sandbox` handle. diff --git a/sandbox/sdk/typescript/reference.mdx b/sandbox/sdk/typescript/reference.mdx index 386bd66..dff0c72 100644 --- a/sandbox/sdk/typescript/reference.mdx +++ b/sandbox/sdk/typescript/reference.mdx @@ -64,7 +64,7 @@ Options: | `tags` | `Record \| undefined` | Key-value labels for finding related sandboxes. | | `volume_mounts` | `Record \| undefined` | Volume IDs keyed by absolute mount path inside the sandbox. | | `networking` | `SandboxNetworkingSpec[] \| undefined` | Port to expose and, optionally, the domain and visibility to serve it on. Omit to expose nothing. See [Sandbox Networking](/sandboxes/networking). | -| `egress` | `SandboxEgressSpec \| undefined` | Restricts the sandbox's outbound traffic to a list of allowed destinations (hostnames, wildcards, IP literals, or CIDR ranges). Omit to leave internet access unrestricted. See [Restrict outbound traffic](/sandboxes/networking#restrict-outbound-traffic-with-an-egress-allowlist). | +| `egress` | `SandboxEgressSpec \| undefined` | Restricts the sandbox's outbound traffic to a list of allowed destinations (hostnames, wildcards, IP literals, CIDR ranges, or in-cluster Service DNS names). Omit to leave internet access unrestricted. See [Restrict egress with an allowlist](/sandboxes/networking#restrict-egress-with-an-allowlist). | | `ttl_seconds` | `number \| undefined` | Maximum lifetime in seconds, counted from creation. The sandbox is terminated once it elapses. Omit for no limit. See [sandbox lifetime](/sandboxes/overview#sandbox-lifetime). | Returns a `Sandbox` handle. diff --git a/sandboxes/networking.mdx b/sandboxes/networking.mdx index cea4117..bc30a17 100644 --- a/sandboxes/networking.mdx +++ b/sandboxes/networking.mdx @@ -28,7 +28,7 @@ Sandboxes run untrusted code, so their outbound traffic is isolated by default, | ----------- | --------- | | Public internet | Yes | | Cluster DNS | Yes | -| In-cluster services and other pods (including other sandboxes) | No | +| In-cluster services and other pods (including other sandboxes) | No (unless allowlisted, see below) | | Kubernetes API server and the sandbox control plane | No | | Cloud metadata endpoint (`169.254.169.254`) | No | | Private address space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `100.64.0.0/10`) | No | @@ -75,48 +75,6 @@ CIDR entries are honored as written, including private address space, so an allo A Service entry is how a restricted sandbox reaches another workload on its own cluster: an entry of the form `name.namespace.svc.cluster.local` allows traffic to that Service's backing pods and tracks them as they change, so a rolling deploy of the Service doesn't interrupt connectivity. Service entries aren't scoped to a port; any port on the backing pods is allowed. -## Restrict outbound traffic with an egress allowlist - -By default a sandbox can reach anything on the public internet. Pass `egress` at create time to narrow that to a specific set of destinations; all other outbound traffic is denied. Enforcement happens transparently at the network layer, so any client and protocol works without proxy configuration. - -Enforcement is off until you turn it on for the cluster. In the Porter Dashboard, open the **Sandbox** tab for your sandbox-enabled cluster, go to **Settings**, and toggle **Enforce egress allowlists** on under **Egress controls**. Until it's on, `egress` on a sandbox is accepted but not enforced, and sandboxes keep their default unrestricted internet access. - -An entry in `allowed_destinations` is one of: - -| Kind | Example | Matches | -| ---- | ------- | ------- | -| Hostname | `api.stripe.com` | Exactly that host | -| Wildcard | `*.github.com` | Any host one label under the domain, but not the domain itself | -| IP literal | `203.0.113.10` | That address | -| CIDR range | `203.0.113.0/24` | Any address in the range | - - -```python Python -sandbox = porter.sandboxes.create( - image="python:3.12-slim", - name="my-sandbox", - egress={ - "allowed_destinations": [ - "api.stripe.com", - "*.github.com", - ], - }, -) -``` - -```typescript TypeScript -const sandbox = await porter.sandboxes.create({ - image: "python:3.12-slim", - name: "my-sandbox", - egress: { - allowed_destinations: ["api.stripe.com", "*.github.com"], - }, -}); -``` - - -An empty `allowed_destinations` list denies all egress. Omit `egress` entirely to leave the sandbox's internet access unrestricted (the default described above still applies: in-cluster services, the cloud metadata endpoint, and private address space remain blocked either way). - ## Configure networking on the cluster