> ## 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)

# 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](/python/api/functions) - `start_proxy()` function
* [SnapshotManager](/python/api/snapshot-manager) - Filesystem rollback
