# IDEA-018: Credential Security Layer — Zero-Exposure Secrets for Agent Tooling

**Date:** 2026-03-04
**Source:** user + research session
**Status:** researched

## Core Problem

iEvo runs AI agents (Claude Code, Codex, Grok) inside Docker containers. These agents need credentials to call external tools — GitHub API, AWS, Stripe, etc. The naive approach is to pass credentials as env vars into Docker. But then the LLM can read them (`echo $GITHUB_PAT_TOKEN`), log them, or leak them through tool outputs.

The goal: **LLM knows credential names, never values.**

---

## Three-Layer Architecture

```
Layer 1: LLM API Keys  →  LiteLLM Proxy (host)
Layer 2: Tool Credentials  →  PreToolUse hook + Vault
Layer 3: Output Scanning  →  PostToolUse leak detector
```

---

## Layer 1: LLM API Keys — LiteLLM Proxy

**Problem:** Claude Code needs `ANTHROPIC_API_KEY` to function. If injected into Docker, LLM sees it.

**Solution:** LiteLLM proxy runs on host. Container gets a fake key + proxy URL.

```
Host:
  LiteLLM proxy :8001
    ├── /anthropic/* → api.anthropic.com + real key
    ├── /openai/*    → api.openai.com    + real key
    └── /ollama/*    → localhost:11434

Docker container:
  ANTHROPIC_BASE_URL=http://host.docker.internal:8001
  ANTHROPIC_API_KEY=ievo-proxy-token  ← бесполезен без proxy
  OPENAI_BASE_URL=http://host.docker.internal:8001
  OPENAI_API_KEY=ievo-proxy-token
```

**Bonus from LiteLLM:** rate limiting, spend tracking, audit log per container — из коробки.

**LiteLLM:** https://github.com/BerriAI/litellm (20k ⭐, MIT, Python)

---

## Layer 2: Tool Credentials — Hook Interception

**What LLM sees in system prompt:**
```yaml
available_credentials:
  - name: GITHUB_PAT_TOKEN
    description: GitHub API — read/write repos
  - name: AWS_ACCESS_KEY
    description: S3 bucket access
  - name: STRIPE_SECRET_KEY
    description: Payments API
# No values — names and descriptions only
```

**Flow:**
```
LLM decides:
  Bash("curl -H 'Authorization: token $GITHUB_PAT_TOKEN' https://api.github.com/repos/...")

PreToolUse hook (before execution):
  1. Parse tool input JSON
  2. Find credential patterns: $VAR_NAME, ${VAR_NAME}, env var references
  3. Resolve name → real value from vault
  4. Substitute in-place in tool args
  5. Return modified call → Bash executes with real token

PostToolUse hook (after execution):
  1. Scan tool output for leaked secrets (regex patterns)
     - GitHub: ghp_*, github_pat_*
     - OpenAI: sk-*
     - AWS: AKIA*, aws_secret_*
     - Generic: long hex/base64 strings matching known formats
  2. Mask or block response if leak detected
  3. Log incident
```

---

## Layer 3: Vault Backends (pluggable)

Vault is an interface — iEvo ships a default implementation, plugins add others.

```python
class VaultBackend(Protocol):
    def list_credentials(self) -> list[CredentialMeta]: ...
    def resolve(self, name: str) -> str: ...
    def exists(self, name: str) -> bool: ...
```

| Backend | When to use | Notes |
|---------|------------|-------|
| `EncryptedFileVault` | Default, personal use | `~/.ievo/credentials.enc`, RSA+AES-256-GCM, already in iEvo architecture |
| `HashiCorpVault` | Enterprise, teams | Plugin, requires Vault server |
| `SOPSVault` | Git-friendly secrets | Mozilla SOPS, age/PGP encryption, good for monorepos |
| `EnvVault` | Dev/CI | Reads from actual env vars — for testing and CI pipelines |
| `AgeVault` | Simple, modern | age encryption, single recipient key |

---

## Hook Adapter by Provider

Not all providers have native hook systems:

| Provider | Native hooks | Credential adapter |
|----------|-------------|-------------------|
| Claude Code | PreToolUse / PostToolUse ✅ | `ClaudeHookAdapter` — uses native hooks API |
| Codex CLI | ❌ Not implemented ([Discussion #2150](https://github.com/openai/codex/discussions/2150)) | `PTYCredentialAdapter` — parses tool calls from PTY stream |
| Grok CLI | ❌ No hook system | `PTYCredentialAdapter` — same |
| Future providers | Unknown | `PTYCredentialAdapter` as universal fallback |

**PTY adapter approach:**
```
Provider stdout → PTY bridge (iEvo) → JSON parser
  Finds: {"type": "tool_call", "name": "bash", "input": {"command": "curl $GITHUB_PAT_TOKEN..."}}
  Substitutes credentials
  Forwards modified stream
  Provider process sees modified input and executes
```

**Challenge:** PTY stream may be chunked mid-JSON. PTY adapter needs a streaming JSON parser with buffer. This is the main implementation complexity.

---

## What's Already in Place

- `~/.ievo/credentials.enc` — already designed in CLAUDE.md architecture
- RSA key pair (`ievo_key` / `ievo_key.pub`) — already in architecture
- PreToolUse/PostToolUse hook infrastructure — being built in REQ-012 (debug capture)
- LiteLLM — already selected as provider routing layer

---

## Inspiration

- **IronClaw** (`nearai/ironclaw`) — Rust implementation with same zero-exposure model. Key insight: credentials injected into WASM tool memory at host boundary, LLM never sees plaintext. Leak detector scans all tool output.
- **IronClaw credential injector:** `tools/wasm/credential_injector.rs` — reference implementation
- **IronClaw leak detector:** `safety/leak_detector.rs` — regex-based pattern matching on tool output

---

## Open Questions

- [ ] PTY stream parsing: how to handle chunked JSON? Use buffered streaming parser (e.g., `ijson` in Python)?
- [ ] What credential reference syntax to support? `$VAR`, `${VAR}`, `env:VAR`, or custom `cred:VAR`?
- [ ] Vault plugin discovery: file-based (like iEvo marketplace) or Python entry points?
- [ ] Audit log format: where to store credential access log? `.ievo/debug/` or separate?
- [ ] Rotation: how does iEvo handle credential rotation mid-session?

---

## Related

- IDEA-017: Provider-agnostic architecture (parent discussion)
- REQ-012: Debug capture hooks (same infrastructure — PreToolUse/PostToolUse)
- PROP-2026-03-04-pty-passthrough-layer.md — PTY bridge implementation
- PROP-2026-03-04-provider-routing.md — LiteLLM routing layer
