Proxy Configuration | Docs | TitaniumGuard

Proxy Configuration

Proxy Configuration Reference

Proxy has two components: a QUIC relay and a local desktop client. Each process accepts an optional JSON config file with --config; when omitted, each starts with a loopback-only local demo profile. Explicit config files remain strict and must contain valid TLS and authentication settings.

Local demo without config

Start the relay first, then run the client in another terminal:

bazelisk run //products/proxy/relay:relay
bazelisk run //products/proxy/client:client

The relay listens at 127.0.0.1:9443; the client provides HTTP at 127.0.0.1:8080 and SOCKS5 at 127.0.0.1:1080. Both processes must run as the same user and share XDG_STATE_HOME. If it is unset, credentials are stored under ~/.local/state/titaniumguard/proxy-demo. The relay creates a self-signed localhost certificate and random bearer token on first run with private file permissions. This profile is for local testing; use explicit configuration for a remotely reachable relay.

Relay

{
  "listen": "0.0.0.0:9443",
  "tls": {
    "cert_path": "/etc/titaniumguard/proxy-relay.crt",
    "key_path": "/etc/titaniumguard/proxy-relay.key"
  },
  "auth": {
    "bearer_token": "replace-me",
    "tokens": [
      {
        "id": "laptop-a",
        "bearer_token": "replace-me-too"
      }
    ]
  },
  "hostname_filter": {
    "blacklist": ["*.example.com", "bad.*", "tracker.example"]
  },
  "egress_policy": {
    "allow_private_networks": false,
    "allow_loopback": false,
    "allow_public_internet": true
  },
  "limits": {
    "max_connections": 1024,
    "max_flows": 4096,
    "read_timeout_secs": 10,
    "max_flow_duration_secs": 3600,
    "drain_timeout_secs": 30
  },
  "health_listen": "127.0.0.1:19090",
  "connect_timeout_secs": 10
}

The relay listens for QUIC client sessions, authenticates the bearer token, applies the optional hostname blacklist, resolves target hostnames, and opens outbound TCP connections.

hostname_filter.blacklist accepts case-insensitive wildcard hostname patterns. Trailing dots are ignored, ports are not part of matching, and blocked flows return 403 before relay-side DNS resolution.

egress_policy is applied after DNS resolution and to IP literals. Private networks require allow_private_networks; loopback requires the separate allow_loopback opt-in. Link-local, metadata-style, multicast, documentation, unspecified, IPv4-mapped, and NAT64 special-use ranges remain denied.

Client

{
  "relay_address": "proxy-relay.example.com:9443",
  "relay_server_name": "proxy-relay.example.com",
  "relay_cert_path": "/etc/titaniumguard/proxy-relay.crt",
  "auth": {
    "bearer_token": "replace-me"
  },
  "http_listen": "127.0.0.1:8080",
  "socks_listen": "127.0.0.1:1080",
  "limits": {
    "max_local_clients": 1024,
    "read_timeout_secs": 10,
    "max_flow_duration_secs": 3600,
    "drain_timeout_secs": 30
  },
  "health_listen": "127.0.0.1:19091",
  "connect_timeout_secs": 10
}

The client opens one authenticated QUIC session to the relay and creates one bidirectional QUIC stream per proxied flow.

The client accepts unauthenticated HTTP and SOCKS5 listeners only on loopback addresses. health_listen also must bind to loopback.

Health and Metrics

When health_listen is configured, both processes expose GET /live, GET /ready, and GET /metrics. Readiness returns unavailable while the process is draining or before startup is complete.

Traffic Behavior

  • HTTP proxy requests use absolute-form HTTP targets or the Host header.
  • HTTPS uses standard HTTP CONNECT tunneling.
  • SOCKS5 supports unauthenticated local CONNECT requests for IPv4, IPv6, and domain targets.
  • DNS resolution happens at the relay.
  • Relay hostname blacklist matches become HTTP 403 Forbidden responses or SOCKS5 connection-not-allowed replies.
  • Every proxied QUIC stream receives a UUID v7 interaction ID from the relay. HTTP responses include it as x-tg-proxy-id.