How the Caveman Proxy Preserves Credentials While Routing Through Different Provider Adapters

Caveman isolates proxy authentication data from LLM provider requests by attaching credentials only to the proxy hop via a Proxy-Authorization header that is explicitly stripped before forwarding to downstream adapters.

JuliusBrussee/caveman implements a secure credential preservation system that ensures usernames and passwords embedded in proxy URLs never reach the actual LLM providers. The architecture achieves this through a strict separation between proxy connection logic and provider routing logic, implemented across the packages/cli/src/proxy-fetch.ts and packages/pi-extension/src/provider.ts modules.

Core Architectural Mechanisms

Caveman employs two cooperating mechanisms to ensure credentials remain confined to the proxy layer.

Proxy-Only Authorization Headers

When resolving proxy configurations, the proxyAuthHeader function in packages/cli/src/proxy-fetch.ts creates a Proxy-Authorization header containing the base-64 encoded credentials extracted from the proxy URL. This header is attached only to the connection established with the proxy server itself.

The implementation checks for credentials at lines 107-110:

// From packages/cli/src/proxy-fetch.ts#L107-L110
function proxyAuthHeader(proxyUrl: URL): Record<string, string> {
  if (!proxyUrl.username) return {};
  const cred = `${decodeURIComponent(proxyUrl.username)}:${decodeURIComponent(proxyUrl.password)}`;
  return { "proxy-authorization": `Basic ${Buffer.from(cred).toString("base64")}` };
}

Explicit Header Sanitization

The createProxyAwareFetch wrapper explicitly filters incoming proxy-authorization headers to prevent client injection. At lines 22-28 in packages/cli/src/proxy-fetch.ts, the code skips copying any caller-supplied proxy authorization into the outgoing request:

// Sanitization logic from createProxyAwareFetch
const headers: Record<string, string> = {};
for (const [key, value] of Object.entries(init?.headers || {})) {
  if (key.toLowerCase() === 'proxy-authorization') continue; // Strip client-injected credentials
  headers[key] = value;
}

This ensures that clients cannot accidentally or maliciously leak proxy credentials to the target LLM service.

The Routing Pipeline

When forwarding requests to different provider adapters, Caveman executes a five-step pipeline that keeps credentials isolated:

  1. Resolve proxy – The resolveProxyUrl function inspects http_proxy, https_proxy, and all_proxy environment variables to construct a URL object for the proxy connection (packages/cli/src/proxy-fetch.ts#L95-L104).

  2. Attach proxy credentials – The proxyAuthHeader function generates the Proxy-Authorization header if the proxy URL contains a username and password (packages/cli/src/proxy-fetch.ts#L107-L110).

  3. Strip inbound proxy-auth – The request wrapper removes any existing proxy-authorization headers from the incoming request before processing (packages/cli/src/proxy-fetch.ts#L22-L28).

  4. Register routed provider – The ProviderRouter.apply method records the original provider base URL in an internal originals map, computes the routed endpoint via routeForApi from packages/pi-extension/src/protocol.ts, and registers the provider with the new proxy-routed base URL (packages/pi-extension/src/provider.ts#L70-L84).

  5. Send request – The proxy either opens a CONNECT tunnel for HTTPS traffic via openTunnel or forwards the raw HTTP request via sendThroughProxy, attaching the proxy authentication header exclusively to the proxy connection (packages/cli/src/proxy-fetch.ts#L84-L94, L125-L135).

Key Implementation Files

packages/cli/src/proxy-fetch.ts

This module implements environment-variable proxy resolution, CONNECT tunneling for HTTPS, and the credential isolation logic. The installProxyAwareFetch function wraps the global fetch to intercept requests and inject proxy handling:

import { installProxyAwareFetch } from "@caveman/cli";

// Install proxy-aware fetch that respects credentials in http_proxy env vars
installProxyAwareFetch(process.env);

packages/pi-extension/src/provider.ts

The ProviderRouter class manages provider overrides and stores original URLs to prevent credential re-exposure. Lines 17-25 define the originals map that tracks upstream endpoints:

// From packages/pi-extension/src/provider.ts
export class ProviderRouter {
  private originals = new Map<string, string>();
  
  // Stores original provider URLs to maintain routing consistency
  async apply(model: string, ctx: Context) {
    // Registration logic preserves credential isolation
  }
}

packages/pi-extension/src/protocol.ts

This file provides routing helpers like routeForApi that compute the correct upstream URL for a given provider without exposing proxy credentials in the resulting endpoint.

Practical Usage Examples

Installing the Proxy-Aware Fetch

Enable credential-preserving proxy routing in your Caveman CLI setup:

import { installProxyAwareFetch } from "@caveman/cli";

// The wrapper automatically detects http_proxy environment variables
// and ensures credentials stay on the proxy hop only
installProxyAwareFetch(process.env);

Routing Through Provider Adapters

Use ProviderRouter to route model requests while maintaining credential isolation:

import { ProviderRouter } from "@caveman/pi-extension";

const router = new ProviderRouter(piApi, (msg, kind) => console.warn(msg));
await router.openGate("http://127.0.0.1:8787", ctx, compatUpstreams);

// Routes to the provider while keeping proxy credentials confined
await router.apply(ctx.model, ctx);

Manual Proxy Authentication Construction

The following illustrates the base-64 encoding logic Caveman uses internally for the Proxy-Authorization header:

function makeProxyAuthHeader(proxyUrl: string): Record<string, string> {
  const url = new URL(proxyUrl);
  if (!url.username) return {};
  const cred = `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`;
  return { "proxy-authorization": `Basic ${Buffer.from(cred).toString("base64")}` };
}

Summary

  • Caveman preserves credentials while routing through different provider adapters by isolating proxy authentication to the connection hop only.
  • The proxyAuthHeader function in packages/cli/src/proxy-fetch.ts generates Proxy-Authorization headers exclusively for the proxy connection.
  • Incoming proxy-authorization headers are stripped by createProxyAwareFetch to prevent credential leakage to LLM providers.
  • The ProviderRouter class in packages/pi-extension/src/provider.ts maintains original provider URLs separately from proxy routing logic.
  • Credentials embedded in http_proxy or https_proxy environment variables never reach downstream provider adapters.

Frequently Asked Questions

Can clients inject their own proxy credentials into the request?

No. The createProxyAwareFetch wrapper in packages/cli/src/proxy-fetch.ts explicitly filters out any proxy-authorization headers from incoming requests at lines 22-28. This prevents clients from accidentally or maliciously forwarding proxy credentials to the target LLM service.

How does Caveman handle HTTPS traffic through the proxy?

For HTTPS destinations, Caveman opens a CONNECT tunnel via the openTunnel function in packages/cli/src/proxy-fetch.ts (lines 125-135). The proxy authentication header is attached only during the tunnel establishment phase, ensuring encrypted traffic between the client and final provider remains opaque to the proxy credentials.

Where are the original provider URLs stored during routing?

Original provider URLs are stored in the private originals Map within the ProviderRouter class defined in packages/pi-extension/src/provider.ts (lines 17-25). This map maintains the mapping between model names and their upstream endpoints without exposing proxy authentication details.

Is the Proxy-Authorization header sent to the LLM provider?

No. The Proxy-Authorization header is strictly confined to the proxy connection hop. According to the implementation in packages/cli/src/proxy-fetch.ts, this header is either generated from the proxy URL credentials or stripped from incoming requests, but never forwarded to the downstream provider adapter during routing.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →