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

# ProxyConfig

> Network proxy configuration

The proxy system provides domain-filtered, credential-injected network access for sandboxed child processes. The proxy runs in the unsandboxed supervisor process; the sandboxed child connects only to `127.0.0.1` via standard `HTTP_PROXY`/`HTTPS_PROXY` environment variables.

## ProxyConfig

```python theme={null}
ProxyConfig(
    allowed_hosts: list[str] | None = None,
    routes: list[RouteConfig] = [],
    external_proxy: ExternalProxyConfig | None = None,
    bind_addr: str = "127.0.0.1",
    bind_port: int = 0,
    max_connections: int = 256,
    intercept_ca_dir: str | None = None,
    intercept_parent_ca_pems: bytes | None = None,
    allow_all_hosts: bool = False,
)
```

Configuration for the nono network filtering proxy.

| Parameter | Type | Default | Description |
| - | - | - | - |
| `allowed_hosts` | `list[str] \| None` | `None` | Transparent CONNECT host allowlist. `None` or `[]` denies transparent CONNECT except for configured route upstreams needed by reverse proxy forwarding. Supports `*.domain` wildcards. |
| `routes` | `list[RouteConfig]` | `[]` | Reverse proxy credential injection routes. |
| `external_proxy` | `ExternalProxyConfig \| None` | `None` | Enterprise proxy passthrough configuration. |
| `bind_addr` | `str` | `"127.0.0.1"` | Address to bind the proxy to. |
| `bind_port` | `int` | `0` | Port to bind. 0 = OS-assigned ephemeral port. |
| `max_connections` | `int` | `256` | Maximum concurrent connections. 0 = unlimited. |
| `intercept_ca_dir` | `str \| None` | `None` | Directory containing CA certificates for TLS interception (ConnectIntercept mode). |
| `intercept_parent_ca_pems` | `bytes \| None` | `None` | Parent CA PEM bytes for generating interception certificates. |
| `allow_all_hosts` | `bool` | `False` | Explicit opt-in to allow transparent CONNECT to all hosts except the hardcoded metadata deny list. Cannot be combined with `allowed_hosts`. |

### Example

```python theme={null}
from nono_py import ProxyConfig, RouteConfig, InjectMode, start_proxy

config = ProxyConfig(
    allowed_hosts=["api.openai.com", "*.anthropic.com"],
    routes=[
        RouteConfig(
            prefix="/openai",
            upstream="https://api.openai.com",
            credential_key="openai-key",
        ),
    ],
)
proxy = start_proxy(config)
```

***

## RouteConfig

```python theme={null}
RouteConfig(
    prefix: str,
    upstream: str,
    credential_key: str | None = None,
    inject_mode: InjectMode = InjectMode.HEADER,
    inject_header: str = "Authorization",
    credential_format: str | None = None,
    path_pattern: str | None = None,
    path_replacement: str | None = None,
    query_param_name: str | None = None,
    env_var: str | None = None,
    endpoint_rules: list[tuple[str, str]] = [],
    tls_ca: str | None = None,
    tls_client_cert: str | None = None,
    tls_client_key: str | None = None,
)
```

Configuration for a reverse proxy credential injection route. When the sandboxed child sends a request to `http://127.0.0.1:<port>/<prefix>/...`, the proxy forwards it to `upstream` with real credentials injected.

| Parameter | Type | Default | Description |
| - | - | - | - |
| `prefix` | `str` | required | Path prefix for routing (e.g., `"/openai"`) |
| `upstream` | `str` | required | Upstream URL (e.g., `"https://api.openai.com"`) |
| `credential_key` | `str \| None` | `None` | OS keyring account name for the credential |
| `inject_mode` | `InjectMode` | `HEADER` | How to inject the credential |
| `inject_header` | `str` | `"Authorization"` | Header name (for `HEADER` mode) |
| `credential_format` | `str \| None` | `None` | Format string with `{credential}` placeholder (e.g., `"Bearer {credential}"`) |
| `path_pattern` | `str \| None` | `None` | URL path mode: pattern to match in incoming path |
| `path_replacement` | `str \| None` | `None` | URL path mode: replacement pattern for outgoing path |
| `query_param_name` | `str \| None` | `None` | Query param mode: parameter name |
| `env_var` | `str \| None` | `None` | Override env var name for the phantom token |
| `endpoint_rules` | `list[tuple[str, str]]` | `[]` | Per-endpoint method/path allow rules (e.g., `[("POST", "/v1/chat/completions")]`) |
| `tls_ca` | `str \| None` | `None` | Custom CA certificate PEM for upstream TLS verification |
| `tls_client_cert` | `str \| None` | `None` | Client certificate PEM for mutual TLS |
| `tls_client_key` | `str \| None` | `None` | Client private key PEM for mutual TLS |

***

## InjectMode

Credential injection method:

| Value | Description |
| - | - |
| `InjectMode.HEADER` | Inject as HTTP header (default) |
| `InjectMode.URL_PATH` | Replace pattern in URL path |
| `InjectMode.QUERY_PARAM` | Add as query parameter |
| `InjectMode.BASIC_AUTH` | HTTP Basic Authentication |

***

## ProxyHandle

Returned by `start_proxy()`. Not user-constructable.

### Properties

| Property | Type | Description |
| - | - | - |
| `port` | `int` | Port the proxy is listening on |

### Methods

#### `env_vars() -> dict[str, str]`

Environment variables to inject into the sandboxed child: `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, `NONO_PROXY_TOKEN`, and lowercase variants.

#### `credential_env_vars() -> dict[str, str]`

Per-route base URL overrides and phantom tokens (e.g., `OPENAI_BASE_URL`, `OPENAI_API_KEY`). Only includes routes where credentials were loaded from the keyring.

#### `sandbox_env() -> list[tuple[str, str]]`

Convenience method combining `env_vars()` and `credential_env_vars()` into a single list of `(key, value)` tuples, ready to pass directly to `sandboxed_exec(env=...)`.

#### `drain_audit_events() -> list[dict]`

Drain and return collected network audit events. Each dict contains:

| Key | Type | Description |
| - | - | - |
| `timestamp_unix_ms` | `int` | Event timestamp |
| `mode` | `str` | `"connect"`, `"connect_intercept"`, `"reverse"`, `"external"` |
| `decision` | `str` | `"allow"` or `"deny"` |
| `target` | `str` | Hostname or service |
| `port` | `int \| None` | Target port |
| `method` | `str \| None` | HTTP method (reverse proxy) |
| `path` | `str \| None` | Request path (reverse proxy) |
| `status` | `int \| None` | Upstream response status |
| `reason` | `str \| None` | Denial reason |
| `route_id` | `str \| None` | Matched route prefix |
| `auth_mechanism` | `str \| None` | `"proxy_authorization"`, `"phantom_header"`, `"phantom_path"`, `"phantom_query"` |
| `auth_outcome` | `str \| None` | `"succeeded"` or `"failed"` |
| `managed_credential_active` | `bool \| None` | Whether a managed credential was used |
| `injection_mode` | `str \| None` | `"header"`, `"url_path"`, `"query_param"`, `"basic_auth"`, `"oauth2"` |
| `denial_category` | `str \| None` | `"host_denied"`, `"endpoint_policy"`, `"authentication_failed"`, etc. |

#### `shutdown() -> None`

Signal the proxy to shut down gracefully.

### Example

```python theme={null}
proxy = start_proxy(config)

# Pass to sandboxed child
env = proxy.sandbox_env()
result = sandboxed_exec(caps, ["python", "agent.py"], env=env)
# Optional for dedicated sandbox UIDs only:
# result = sandboxed_exec(caps, ["python", "agent.py"], env=env, max_processes=64)

# Review what happened
for event in proxy.drain_audit_events():
    print(f"[{event['decision']}] {event['mode']} -> {event['target']}")

proxy.shutdown()
```

***

## ExternalProxyConfig

Enterprise proxy passthrough for environments behind a corporate proxy.

```python theme={null}
ExternalProxyConfig(
    address: str,
    bypass_hosts: list[str] = [],
)
```

| Parameter | Type | Default | Description |
| - | - | - | - |
| `address` | `str` | required | Proxy address (e.g., `"squid.corp.internal:3128"`) |
| `bypass_hosts` | `list[str]` | `[]` | Hosts that bypass the external proxy. Supports `*.domain` wildcards. |

## Security Properties

* **Cloud metadata deny list**: `169.254.169.254` and equivalents are always blocked
* **DNS rebinding protection**: Resolved IPs are validated against link-local ranges
* **Credential isolation**: Real API keys never reach the sandboxed process
* **Constant-time token comparison**: Prevents timing side-channel attacks
* **Audit logging**: Every request logged, sensitive data excluded

## Related

* [Module Functions](/docs/python/api/functions) - `start_proxy()` function
* [SnapshotManager](/docs/python/api/snapshot-manager) - Filesystem rollback


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.