K0KEYZERO
← All posts

Blind Mode Explained: Secrets Your AI Agent Never Sees

How KeyZero's blind mode uses a local MITM proxy to give AI agents opaque tokens instead of real secrets, swapping credentials at the network edge.

·blind-mode, architecture, ai-agents

Blind mode is KeyZero's core mechanism for running untrusted workloads — particularly AI agents — with access to authenticated APIs without exposing raw credentials. The agent process receives opaque masked tokens instead of real secret values. A local MITM proxy intercepts outbound HTTP/HTTPS requests and swaps the tokens for real credentials at the network boundary.

Why Blind Mode Exists

Traditional secret injection hands the real credential to the consuming process. This works when the consumer is a trusted server application with controlled I/O. AI agents break this model in three ways:

  1. Context window exposure: LLM-based agents serialize their full state — including environment variables — into prompts sent to external LLM APIs
  2. Verbose logging: Agent frameworks log tool calls, parameters, and responses, capturing secrets in observability pipelines
  3. Autonomous tool use: Agents construct HTTP requests dynamically, placing secrets in parameters visible to the LLM's reasoning chain

For a detailed breakdown of each leak vector, see why AI agents leak secrets.

Blind mode eliminates all three vectors by ensuring the agent process never holds a real secret value.

How the MITM Proxy Works

Step-by-Step Flow

When you run kz run --blind -- node agent.js, KeyZero performs these steps:

  1. Config loading: Reads .keyzero.toml and identifies all secrets to resolve
  2. Secret resolution: Fetches real secret values from configured providers (Keychain, 1Password, AWS Secrets Manager, HashiCorp Vault, etc.)
  3. Token generation: For each secret, generates a masked token in the format kz_masked_<hash>
  4. Token mapping: Builds an internal mapping table: kz_masked_7f3a9b... → sk-proj-abc123...
  5. Proxy startup: Launches a local MITM proxy on a random port, configured with the token mapping and an ephemeral CA certificate
  6. Environment injection: Sets environment variables on the subprocess with masked tokens instead of real values, plus proxy configuration variables
  7. Subprocess launch: Starts the agent process with the modified environment

Architecture Overview

┌─────────────────────────────────────────────────────┐
│  kz run --blind                                     │
│                                                     │
│  ┌──────────────┐         ┌──────────────────────┐  │
│  │  Agent        │  HTTP   │  MITM Proxy          │  │
│  │  Process      │────────▶│                      │  │
│  │              │         │  1. Intercept request │  │
│  │  Sees:       │         │  2. Find kz_masked_*  │  │
│  │  kz_masked_* │         │  3. Swap with real    │  │
│  │              │         │     secret value      │  │
│  └──────────────┘         │  4. Forward request   │  │
│                           └──────────┬───────────┘  │
│                                      │              │
└──────────────────────────────────────┼──────────────┘
                                       │
                                       ▼
                              ┌─────────────────┐
                              │  Upstream API    │
                              │  Receives real   │
                              │  credentials     │
                              └─────────────────┘

What the Agent Sees vs. What Hits the Wire

Agent's environment:

API_KEY=kz_masked_7f3a9b2e4d...
HTTP_PROXY=http://127.0.0.1:54321
HTTPS_PROXY=http://127.0.0.1:54321
SSL_CERT_FILE=/tmp/kz_ca_xyz.pem
NODE_EXTRA_CA_CERTS=/tmp/kz_ca_xyz.pem

Agent's outbound request:

GET /v1/models HTTP/1.1
Host: api.openai.com
Authorization: Bearer kz_masked_7f3a9b2e4d...

What the upstream API receives:

GET /v1/models HTTP/1.1
Host: api.openai.com
Authorization: Bearer sk-proj-abc123def456...

The proxy scans the entire outbound request — headers, query parameters, and body — for any kz_masked_* tokens and replaces them with the corresponding real values. For a full walkthrough of the proxy architecture and how it fits into KeyZero's resolution pipeline, see the architecture deep dive.

TLS Handling

The MITM proxy generates an ephemeral CA certificate at startup. This CA is used to sign certificates for intercepted HTTPS connections. KeyZero automatically sets SSL_CERT_FILE, NODE_EXTRA_CA_CERTS, and REQUESTS_CA_BUNDLE so that common HTTP clients (OpenSSL, Node.js, Python requests) trust the ephemeral CA without manual configuration.

The ephemeral CA exists only for the lifetime of the kz run process and is deleted on exit.

Connection Control

Blind mode includes network-level access control. In .keyzero.toml, you can restrict which hosts the agent process is allowed to contact:

[blind]
[[blind.connections]]
allow = true
host = "api.openai.com"

[[blind.connections]]
allow = true
host = "api.anthropic.com"

[[blind.connections]]
allow = false
host = "*"

This prevents the agent from exfiltrating tokens to arbitrary endpoints. Even though the tokens are masked, connection control adds defense in depth.

When to Use Blind Mode vs. Direct Resolution

ScenarioRecommended ModeReason
AI agent (LLM-based)Blind modeAgent's context window and logs must not contain real secrets
Untrusted plugin or extensionBlind modePlugin code is not fully audited; limit its access to raw credentials
Trusted server applicationDirect resolutionThe process is controlled; no context window or LLM exposure risk
CI/CD pipelineDirect resolutionShort-lived runner with controlled I/O; blind mode adds unnecessary complexity
Local developmentDirect resolutionDeveloper is trusted; blind mode proxy may interfere with debugging
Shared development machineBlind modeMultiple users or processes; limit blast radius of any single process

Rule of thumb: If the process communicates with an LLM API or runs code you do not fully control, use blind mode.

Comparison: Blind Mode vs. Traditional Secret Injection

CategoryTraditional InjectionKeyZero Blind Mode
Secret visibility to processFull plaintextMasked token only
Log/trace exposureReal secrets in logsOnly masked tokens in logs
Context window safetySecrets enter LLM contextNo real secrets in context
Network-level controlNone (process calls any host)Connection allowlist per host
Rotation impactProcess must restartProxy resolves fresh values; process unchanged
Setup complexityLow (set env vars)Low (--blind flag + .keyzero.toml)
Runtime overheadNoneMinimal (local proxy adds < 1ms per request)
TLS interceptionNot applicableEphemeral CA, auto-configured for Node.js/Python/OpenSSL

Getting Started with Blind Mode

  1. Create a .keyzero.toml with your secret mappings:
[secrets]
OPENAI_API_KEY = { provider = "keychain", ref = "openai-key" }
STRIPE_KEY     = { provider = "1password", ref = "op://Vault/stripe/api-key" }
  1. Store the secrets in your provider (if not already present):
kz put --missing
  1. Run your agent with blind mode:
kz run --blind -- node agent.js

The agent starts with masked tokens. Every outbound HTTP request passes through the local proxy, which swaps tokens for real values transparently. When the process exits, the proxy shuts down and the ephemeral CA is deleted.

Blind mode works seamlessly with AI coding tools like Claude Code and with MCP servers that need authenticated access to external APIs.