From ddea068186a1175631be968fad5e1d06ccd63395 Mon Sep 17 00:00:00 2001
From: hiroTamada <88675973+hiroTamada@users.noreply.github.com>
Date: Wed, 12 Aug 2026 17:34:40 +0000
Subject: [PATCH 1/2] Document private browser networking
---
browsers/pools.mdx | 2 +-
browsers/private-networking.mdx | 178 ++++++++++++++++++++++++++++++++
docs.json | 1 +
proxies/overview.mdx | 6 +-
4 files changed, 185 insertions(+), 2 deletions(-)
create mode 100644 browsers/private-networking.mdx
diff --git a/browsers/pools.mdx b/browsers/pools.mdx
index 0eace54..37670f1 100644
--- a/browsers/pools.mdx
+++ b/browsers/pools.mdx
@@ -3,7 +3,7 @@ title: "Browser Pools"
description: "Configure a pool of ready-to-use browsers for instant acquisition"
---
-A browser pool is a fixed set of identical browsers that Kernel keeps running for you. Configure it once — stealth, proxies, extensions, viewport, a [profile](#profiles-with-browser-pools) — then acquire a browser whenever a task needs one and release it when you're done.
+A browser pool is a fixed set of identical browsers that Kernel keeps running for you. Configure it once — stealth, proxies, [private networking](/browsers/private-networking), extensions, viewport, a [profile](#profiles-with-browser-pools) — then acquire a browser whenever a task needs one and release it when you're done.
Acquiring is faster than creating an on-demand browser because the browser is already running: you skip start-up, including the [Chromium restart](/browsers/performance#troubleshooting-latency) that some settings trigger, and you aren't subject to the [rate limit](/info/pricing#rate-limiting) on browser creation.
diff --git a/browsers/private-networking.mdx b/browsers/private-networking.mdx
new file mode 100644
index 0000000..fa443b8
--- /dev/null
+++ b/browsers/private-networking.mdx
@@ -0,0 +1,178 @@
+---
+title: "Private Networking"
+description: "Route browser traffic to private services through a VPN or tunnel in the browser session"
+---
+
+Use `network.private_hosts` when a browser session joins a VPN or tunnel and must reach private services through that connection. Matching destinations bypass Kernel-managed egress and use the session's network routes and DNS instead.
+
+This is useful for services reachable through Tailscale, a corporate VPN, or another tunnel running inside the browser session.
+
+
+`network.private_hosts` is different from a proxy's [`bypass_hosts`](/proxies/overview#bypass-hosts). Proxy bypass rules choose between your upstream proxy and Kernel-managed direct egress. Private hosts bypass Kernel-managed egress so traffic can follow routes inside the browser session, including VPN and tunnel routes.
+
+
+## Configure a browser
+
+Set private hosts when you create the browser. You can't change the network configuration after creation.
+
+
+```typescript TypeScript
+import Kernel from '@onkernel/sdk';
+
+const kernel = new Kernel();
+
+const browser = await kernel.browsers.create({
+ network: {
+ private_hosts: [
+ '*.services.example.ts.net',
+ '100.64.0.0/10',
+ ],
+ },
+});
+
+console.log(browser.session_id);
+```
+
+```python Python
+from kernel import Kernel
+
+kernel = Kernel()
+
+browser = kernel.browsers.create(
+ network={
+ "private_hosts": [
+ "*.services.example.ts.net",
+ "100.64.0.0/10",
+ ]
+ }
+)
+
+print(browser.session_id)
+```
+
+```go Go
+package main
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/kernel/kernel-go-sdk"
+)
+
+func main() {
+ client := kernel.NewClient()
+
+ browser, err := client.Browsers.New(context.Background(), kernel.BrowserNewParams{
+ Network: kernel.BrowserNetworkConfigParam{
+ PrivateHosts: []string{
+ "*.services.example.ts.net",
+ "100.64.0.0/10",
+ },
+ },
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(browser.SessionID)
+}
+```
+
+```bash CLI
+kernel browsers create \
+ --private-host '*.services.example.ts.net' \
+ --private-host '100.64.0.0/10'
+```
+
+
+## Default private routes
+
+When you omit `network.private_hosts`, Kernel routes these private IP ranges through the session network by default:
+
+- RFC1918: `10.0.0.0/8`, `172.16.0.0/12`, and `192.168.0.0/16`
+- CGNAT and Tailscale: `100.64.0.0/10`
+- IPv6 unique local addresses: `fc00::/7`
+
+These CIDR rules only match URLs that use literal IP addresses. They don't match a hostname after DNS resolution. Add private DNS names explicitly, even when they resolve to an address in a default range:
+
+```json
+{
+ "network": {
+ "private_hosts": ["api.services.example.ts.net"]
+ }
+}
+```
+
+
+Providing `private_hosts` replaces the default list; it doesn't add to it. Include any default CIDRs you still need alongside your hostname rules.
+
+
+To disable direct private routing and send all traffic through Kernel-managed egress, provide an explicit empty list:
+
+
+```typescript TypeScript
+const browser = await kernel.browsers.create({
+ network: { private_hosts: [] },
+});
+```
+
+```python Python
+browser = kernel.browsers.create(
+ network={"private_hosts": []}
+)
+```
+
+```bash CLI
+kernel browsers create --disable-private-hosts
+```
+
+
+## Supported entries
+
+You can provide up to 32 entries, each no longer than 255 characters:
+
+- Exact hostnames: `api.services.example.ts.net`
+- A single leading wildcard: `*.services.example.ts.net`
+- Private IPv4 addresses: `10.1.30.63`
+- Bracketed private IPv6 addresses: `[fd00::1]`
+- Canonical private CIDRs: `100.64.0.0/10` or `fd00::/8`
+- Hostnames or exact IP addresses with ports: `api.services.example.ts.net:8443`
+
+Kernel rejects public IP ranges, loopback and link-local ranges, URL schemes, paths, catch-all wildcards, ports on CIDRs, and non-canonical CIDRs. Hostnames aren't resolved during validation, so only add names that identify private destinations.
+
+## Configure a browser pool
+
+Put the network configuration on a browser pool when every browser in the pool needs the same private routes.
+
+
+```typescript TypeScript
+const pool = await kernel.browserPools.create({
+ name: 'private-services',
+ size: 5,
+ network: {
+ private_hosts: ['*.services.example.ts.net'],
+ },
+});
+```
+
+```python Python
+pool = kernel.browser_pools.create(
+ name="private-services",
+ size=5,
+ network={
+ "private_hosts": ["*.services.example.ts.net"],
+ },
+)
+```
+
+```bash CLI
+kernel browser-pools create private-services \
+ --size 5 \
+ --private-host '*.services.example.ts.net'
+```
+
+
+A browser-pool update applies only to browsers created after the update. Pass `discard_all_idle: true` in an SDK request, or `--discard-all-idle` in the CLI, to immediately replace idle browsers with the new configuration. Acquired browsers keep their original configuration until you release them with reuse disabled.
+
+Use `kernel browser-pools update private-services --clear-private-hosts --discard-all-idle` to remove a pool override and restore the default private IP ranges. Use `--disable-private-hosts` instead when you want an explicit empty list that sends all traffic through Kernel-managed egress.
diff --git a/docs.json b/docs.json
index 77a43e5..19f2a64 100644
--- a/docs.json
+++ b/docs.json
@@ -159,6 +159,7 @@
]
},
"browsers/extensions",
+ "browsers/private-networking",
"browsers/chrome-policies",
{
"group": "Telemetry",
diff --git a/proxies/overview.mdx b/proxies/overview.mdx
index 49f6e4f..575a41d 100644
--- a/proxies/overview.mdx
+++ b/proxies/overview.mdx
@@ -241,7 +241,11 @@ For ISP and datacenter proxies the exit IP is stable, so a successful check agai
## Bypass hosts
-Configure specific hostnames to bypass the proxy and connect directly. This is useful for accessing internal services, metadata endpoints, or reducing latency for trusted domains.
+Configure specific hostnames to bypass the proxy and connect through Kernel-managed direct egress. This is useful for metadata endpoints or reducing latency for trusted domains.
+
+
+To reach a private service through a VPN or tunnel inside the browser session, use [`network.private_hosts`](/browsers/private-networking) instead. Proxy bypass rules don't route traffic into the session's private network.
+
```typescript Typescript/Javascript
From 129ccd456a0a25a940db777c80a8de935d071c5e Mon Sep 17 00:00:00 2001
From: hiroTamada <88675973+hiroTamada@users.noreply.github.com>
Date: Wed, 12 Aug 2026 17:59:46 +0000
Subject: [PATCH 2/2] Keep private host disable behavior SDK-only
---
browsers/private-networking.mdx | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/browsers/private-networking.mdx b/browsers/private-networking.mdx
index fa443b8..596d3f5 100644
--- a/browsers/private-networking.mdx
+++ b/browsers/private-networking.mdx
@@ -122,10 +122,6 @@ browser = kernel.browsers.create(
network={"private_hosts": []}
)
```
-
-```bash CLI
-kernel browsers create --disable-private-hosts
-```
## Supported entries
@@ -175,4 +171,4 @@ kernel browser-pools create private-services \
A browser-pool update applies only to browsers created after the update. Pass `discard_all_idle: true` in an SDK request, or `--discard-all-idle` in the CLI, to immediately replace idle browsers with the new configuration. Acquired browsers keep their original configuration until you release them with reuse disabled.
-Use `kernel browser-pools update private-services --clear-private-hosts --discard-all-idle` to remove a pool override and restore the default private IP ranges. Use `--disable-private-hosts` instead when you want an explicit empty list that sends all traffic through Kernel-managed egress.
+Use `kernel browser-pools update private-services --clear-private-hosts --discard-all-idle` to remove a pool override and restore the default private IP ranges. To configure an explicit empty list, use an SDK request with `network.private_hosts: []`.