CLAVITORBlack-box credential issuance
Sign in Get free forever Get started

Credential proxy — technical

How the proxy works.
What it protects against. What it doesn't.

This page is for security reviewers, penetration testers, and engineers evaluating the proxy's threat model. It describes what the proxy does at the protocol level, where credentials exist in memory, and what attack surfaces remain.


Architecture

The proxy is a CONNECT-based HTTPS MITM proxy that runs on 127.0.0.1. An AI agent sets HTTPS_PROXY to point at it. When the agent makes an HTTPS request, the proxy intercepts the TLS connection, inspects request headers for credential references, resolves them against the Clavitor vault, and forwards the request with injected credentials to the upstream API.

Agent                    Proxy (127.0.0.1:1983)              Upstream API
  |                            |                                 |
  |--- CONNECT host:443 ----> |                                 |
  |<-- 200 OK --------------- |                                 |
  |                            |                                 |
  |== TLS (MITM cert) ======= |                                 |
  |                            |                                 |
  |--- GET /v1/models ------> |                                 |
  |    Authorization:          |                                 |
  |    Bearer clavitor://X/key |                                 |
  |                            |--- GET /v1/models ------------>|
  |                            |    Authorization:               |
  |                            |    Bearer sk-proj-abc123...     |
  |                            |                                 |
  |                            |<-- 200 OK --------------------|
  |<-- 200 OK --------------- |                                 |

The proxy is a standalone Go binary. No CGO. All Clavitor protocol cryptography routes through a canonical Rust implementation compiled to WebAssembly and loaded via wazero at startup.


TLS handling

The proxy generates a self-signed ECDSA P-256 root CA on first run, persisted at the binary's directory with mode 0600. For each upstream host, a leaf certificate is minted on-demand, signed by this CA, and cached in memory with bounded eviction (1,000 hosts). Leaf certificates are valid for 24 hours and regenerated transparently at the 23-hour mark to prevent mid-session expiry.

The agent must trust the proxy's CA certificate. Export it with clavitor-proxy ca.

Upstream connections use TLS 1.3 minimum with ALPN negotiation for HTTP/2 and HTTP/1.1. The system certificate pool is used for upstream verification. No certificate pinning — the proxy trusts whatever the OS trusts.


Credential lifecycle

Credentials are never cached, never written to disk, and never held longer than one HTTP request.

PhaseWhere the credential existsDuration
At rest in vaultAES-GCM ciphertext in the vault databaseUntil deleted
In transit to proxyTLS-encrypted JSON response from vault APIOne HTTP round-trip
Decrypted in proxyProcess memory (Go string on heap)One HTTP request
Injected into upstream requestTLS-encrypted bytes on the wire to upstreamOne HTTP request

The proxy holds the agent's L2 key (16 bytes) in memory for its entire runtime. L2 is the decryption key for tier-2 vault fields. It is loaded from the encrypted sidecar config (CLV1 format) at startup and cleared on graceful shutdown. L2 never leaves the process.

The sidecar config is encrypted with AES-128-GCM and HMAC-SHA256 using deterministic keys derived from a static seed. This is obfuscation, not confidentiality — the security boundary is file permissions (0600) and possession of the file. The CLV1 format is shared between the proxy, the CLI, and the browser extension.


Resolution modes

Mode 1 — explicit placeholder

The agent includes a clavitor://Entry/field reference in a request header. The proxy searches the vault for the entry by name, fetches it, decrypts the named field, and substitutes the placeholder with the real value.

If the search returns zero or more than one result, the proxy returns 502 with a stable error code. The placeholder is never stripped and forwarded as-is.

Mode 2 — URL match

When no placeholder is present, the proxy asks the vault for entries whose URL field matches the upstream host. If exactly one match exists with a recognised field shape, the proxy injects credentials automatically.

Zero matches → passthrough (no credential expectation). Multiple matches → 502 with disambiguation guidance. Unknown field shape → 502.

The decision tree is deterministic: placeholder present → resolve or fail. No placeholder → URL match or passthrough. There is no silent fallback path where a failed resolution results in a request going upstream without credentials.


Agent identity

By default, the vault sees the proxy's own agent ID on every request. Rate limits, scope checks, and audit entries are attributed to the proxy.

When multiple agents share one proxy instance, the placeholder can include an agent ID: clavitor://agentid@Entry/field. The proxy sends this agent ID to the vault, which applies that agent's scopes and rate limits and logs the access against it. The agent ID is the 32-char hex value shown on the agent detail page in the vault UI.

# Without agent ID — attributed to the proxy
Authorization: Bearer clavitor://OpenAI/key
# With agent ID — attributed to agent 0102030405060708090a0b0c0d0e0f10
Authorization: Bearer clavitor://0102030405060708090a0b0c0d0e0f10@OpenAI/key
DeploymentIdentity modelIsolation
One proxy per agentProxy ID = agent ID (default)Full — separate binary, config, scope, rate limits
Shared proxy, no agent ID in URLAll agents share proxy's IDShared scope and rate limits
Shared proxy + agentid@ in URLPer-agent identityPer-agent scope, rate limits, and audit

The agent ID in the URL is not an authentication mechanism — the proxy's CVT token authenticates the connection. The agent ID determines attribution: whose scopes apply, whose rate limits count, whose audit trail records the access. The vault rejects unknown agent IDs with a loud failure.


Network security

SSRF protection

By default, the proxy blocks upstream connections to private networks (RFC 1918), cloud instance metadata (169.254.169.254), loopback, link-local, and carrier-grade NAT ranges. DNS is resolved first; all returned IPs are validated before the TCP connection is made, closing the DNS rebinding TOCTOU window.

Override with CLAVITOR_PROXY_ALLOW_PRIVATE=true for agents that legitimately reach private APIs.

Target pinning

The CONNECT target host is captured at tunnel establishment and used for the entire tunnel lifetime. Subsequent requests within the tunnel cannot redirect to a different host by manipulating the Host header. A mismatch results in 502.

This prevents an agent from establishing a tunnel to api.openai.com and then sending requests to internal-service.corp.


Header handling

Hop-by-hop headers are stripped from both requests and responses per RFC 7230 §6.1: Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection, TE, Trailers, Transfer-Encoding, Upgrade.

Set-Cookie is stripped from upstream responses to prevent upstreams from planting cookies in the agent's HTTP client.

Request and response bodies stream through without buffering. Request bodies are capped at 64 MB by default (CLAVITOR_PROXY_MAX_BODY_MB). Response bodies stream without a hard cap; a log warning is emitted when Content-Length exceeds 100 MB.


Field-to-header mapping

In URL-match mode, the proxy maps vault field labels to HTTP headers:

Field labelInjected header
key, apikey, api_key, token, secret, bearer, access_tokenAuthorization: Bearer <value>
x-api-key, api-keyX-API-Key: <value>
username + password (paired)Authorization: Basic base64(user:pass)
Anything elseRejected — ERR-PROXY-052

In placeholder mode, the agent controls which field is resolved and where it goes. The mapping above only applies to URL-match mode.


Error codes

Every failure produces a stable ERR-PROXY-NNN code. These codes are part of the proxy's public interface — agents and operators can match on them for alerting and debugging.

RangeCategory
001–019Setup (config, init, CA generation, WASM)
020–029Daemon lifecycle
030–049Placeholder resolution (clavitor:// URIs)
050–069URL-match injection
070–089Upstream / TLS

Tier-3 fields are unreachable

Vault entries support three encryption tiers. Tier-1 fields are plaintext metadata. Tier-2 fields are encrypted with L2 (the agent's key). Tier-3 fields (identity fields) are encrypted with L3 — a key the server and the proxy have never seen. Only the vault owner, via their hardware security key, can decrypt tier-3 fields.

If a placeholder references a tier-3 field, the proxy returns ERR-PROXY-035. No fallback, no partial result. The field is architecturally unreachable from the proxy.


What the proxy does not protect against

A compromised host

The proxy runs on the same machine as the agent. An attacker with root access can read process memory, attach a debugger, or intercept loopback traffic. The proxy is a credential-injection layer, not a hardware security boundary.

Credential exfiltration via the API response

If the upstream API echoes the credential back in its response (e.g., a "whoami" endpoint), the agent sees it. The proxy injects credentials into requests, not responses. It does not filter what comes back.

The proxy's threat model is a compromised skill or prompt injection that makes an authenticated agent harvest credentials. The vault's per-agent rate limits, unique-entries quotas, and two-strike lockdown are the primary defenses. The proxy adds a network-layer enforcement point where credentials are resolved per-request and never held by the agent.


Logging

The proxy logs one line per accepted CONNECT and emits error lines for failures. It never logs:

When the proxy detects that a 400 response from upstream contains auth-related keywords (unauthorized, invalid token, etc.), it logs a diagnostic hint suggesting the injected credential may be stale. The response is forwarded unchanged.


Cryptography

All Clavitor protocol cryptography — AES-GCM field decryption, HKDF key derivation, base62 encoding, CVT token minting, CLV1 config pack/unpack — executes inside a single WebAssembly module (clavis_crypto.wasm) loaded via wazero, a pure-Go WASM runtime. No CGO. No Go reimplementation of Clavitor primitives.

The WASM module is compiled from the same Rust crate (clavis-crypto) used by the browser, the CLI, and the browser extensions. One source of truth, one binary, one audit surface.

The proxy uses Go's crypto/tls for the TLS wire and crypto/ecdsa for MITM certificate generation. These are transport concerns, not Clavitor protocol operations.


Configuration

Secrets (L2 key, agent ID, device ID, vault URL) live in the encrypted CLV1 sidecar config, written once during clavitor-proxy init. Operational knobs live in environment variables:

VariableDefaultPurpose
CLAVITOR_PROXY_PORT1983Listen port
CLAVITOR_PROXY_ALLOW_PRIVATEfalseAllow connections to RFC-1918 / private networks
CLAVITOR_PROXY_MAX_BODY_MB64Request body size cap
CLAVITOR_PROXY_WRITE_TIMEOUT300Response write timeout in seconds
CLAVITOR_CONFIG(exe dir)Override sidecar config path

Operational knobs are not secrets. They don't belong in the encrypted config. They belong where deployment tools already manage them — the environment.


Review this yourself.

The cryptography is a single auditable WASM artifact. The threat model is documented. If you find something we missed, we want to hear about it.