Networking and egress
The default-deny egress path, the proxy, inbound exposed ports, and private connectivity to customer networks.
Network reachability is the primary exfiltration path for a compromised or prompt-injected agent. The platform's position: a session has no network until a policy grants one, and every granted byte goes through a chokepoint that logs and meters it.
Egress path
There is no second route. The guest has no default gateway other than the tap device, cannot open
raw sockets (CAP_NET_RAW is dropped at every tier), and cannot resolve DNS except through the
proxy's resolver.
Egress policy
# policy: pol_scrape_restricted
egress:
mode: allowlist # allowlist | blocklist | open | none
allow:
- host: "api.customer.com"
ports: [443]
- host: "*.githubusercontent.com"
ports: [443]
- cidr: "203.0.113.0/24"
ports: [443, 8443]
- host: "registry.npmjs.org"
ports: [443]
note: "package installs"
deny:
- host: "*.pastebin.com" # deny wins over any allow
dns:
resolvers: platform # platform | custom
block_dns_over_https: true # prevent resolver bypass
tls:
inspect: false # true enables full MITM, see below
require_sni_match: true # SNI must match the allowed host
limits:
bandwidth_mbps: 50
connections_per_s: 100
max_bytes_per_session_gb: 5
Modes
| Mode | Behaviour | Use for |
|---|---|---|
none | No egress at all | Untrusted code evaluation, the safest default for tier 1 |
allowlist | Only enumerated destinations | Production default for tiers 2–4 |
blocklist | Everything except enumerated destinations | Development, exploration — not for untrusted input |
open | Everything, still logged and metered | Explicit opt-in, requires an org-level setting |
deny always wins over allow, and the platform floor always wins over both.
The platform floor
Non-overridable denies, enforced regardless of customer policy:
169.254.0.0/16 link-local, cloud metadata endpoints
fd00::/8, fe80::/10 link-local and unique-local IPv6
10.x / 172.16.x / 192.168.x platform-internal CIDRs (customer networks reach
sessions through private connectivity, not these)
*.internal.workspaces.build platform control-plane names
known-malicious feed refreshed hourly
SMTP ports 25, 465, 587 anti-abuse; outbound mail goes through a tool
Cloud instance metadata services hand out host credentials to anything that asks from the right IP. A session reaching one is a direct path from an agent prompt to platform infrastructure credentials. The block is at the namespace, the proxy and the host firewall — three layers, because one misconfiguration should not be enough.
DNS
DNS is where egress controls are usually bypassed, so it gets its own enforcement.
- The guest's only resolver is the proxy's. A hardcoded
8.8.8.8fails — outbound UDP/53 is dropped at the namespace. - The resolver answers only for names the policy allows, and returns
NXDOMAINotherwise. This also catches DNS tunnelling, since the tunnel's nameserver is not on the allowlist. - DNS-over-HTTPS and DNS-over-TLS to known public resolvers are blocked by destination, so a browser cannot bring its own resolver.
- Responses are pinned: the IP the proxy resolved is the IP the connection is allowed to reach, which closes DNS-rebinding against internal addresses.
TLS inspection
Off by default. When a policy enables it, the proxy terminates TLS, inspects, and re-originates using a CA the guest trusts.
| Inspection off | Inspection on | |
|---|---|---|
| Enforcement granularity | Host and port, from SNI | Full URL, method, headers, body |
| Payload visibility to the platform | None | Full |
| Logging | Connection-level | Request-level |
| Breaks certificate pinning | No | Yes |
| Typical use | Default | Regulated environments requiring content inspection |
Enabling inspection means the platform sees plaintext, including any credentials the session sends. It is an organization-level decision with an audit entry and an explicit acknowledgement, not a per-session convenience.
Inbound: exposed ports
A session can publish a listening port to a public HTTPS URL.
curl -sS "$WB_BASE_URL/sessions/$SESSION/ports" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{ "port": 5173, "visibility": "signed", "ttl_s": 3600 }'
{
"port": 5173,
"url": "https://p5173-sess2kq9xw.run.workspaces.build/?sig=…",
"visibility": "signed",
"expires_at": "2026-10-06T10:21:04Z"
}
| Visibility | Who can reach it | Notes |
|---|---|---|
org | Members of the organization, authenticated | Default |
signed | Anyone with the signed URL, until it expires | For sharing a preview |
public | Anyone | Requires an org-level opt-in and writes an audit entry |
Inbound traffic terminates at edge-router, which resolves the hostname to a session, checks
visibility, and proxies to the guest port. Inbound bytes are metered as ingress_gb at a lower rate
than egress.
Exposed ports are a separate domain from the platform's own, with its own reputation boundary, because the content is agent-controlled.
Private connectivity
For sessions that must reach a customer's private network — an internal API, a database behind a VPC — there are three options, in increasing order of strength.
| Option | Shape | Isolation | Setup |
|---|---|---|---|
| Allowlisted public endpoint | Policy allows the customer's public host | Standard egress path | Minutes |
| Upstream proxy | Session egress chains through a customer-operated proxy | Customer controls the final hop and sees all traffic | Hours |
| Private link | Dedicated connectivity from a region into the customer VPC | No public internet transit at all | Enterprise, days |
Private link is scoped per project. Sessions in that project resolve the customer's private names and reach private addresses; sessions elsewhere in the same organization cannot.
Metering
| Meter | Where measured | Note |
|---|---|---|
egress_gb | Proxy, bytes leaving toward the internet | The usual largest network line |
ingress_gb | edge-router and proxy, bytes toward the session | Lower rate |
intra_region_gb | Between sessions in the same region | Free up to a threshold |
private_link_gb | Over a private link | Separate rate |
Measurement is at the proxy, not inside the guest, so a guest cannot under-report. Each sample carries the session and end-user tags, which makes per-customer bandwidth attribution exact.
Observability
Every connection a session makes appears in its trace:
{
"ts": "2026-10-06T09:18:41.402Z",
"session_id": "sess_2kQ9xwR4",
"end_user_id": "eu_customer_4821",
"event": "egress.connect",
"destination": { "host": "api.customer.com", "ip": "203.0.113.17", "port": 443 },
"rule": "allow:host=api.customer.com",
"bytes_out": 1842,
"bytes_in": 20114,
"duration_ms": 212,
"tls": { "sni": "api.customer.com", "inspected": false }
}
Denied attempts are logged just as carefully, with the rule that denied them. A sudden rise in
egress.denied for one session is one of the strongest available signals that an agent has been
prompt-injected, and it is surfaced as an alert in the infra console.