🔐 Custodian Paladin · Credential Broker

The agent never
holds the key.

Every other agent stack eventually puts the real API key in the process the model is driving. Paladin never does. The agent holds paladin://stripe_sk — safe to log, safe in context — and the secret resolves only at egress. With sandboxed egress, the key never enters the tool process at all: Paladin makes the authenticated call itself.

The model

A reference in. A governed call out.

Secrets live in one encrypted vault. The agent only ever receives a paladin:// pointer. Every resolution is gated by an explicit grant and written to a tamper-evident log — deny-by-default, always audited.

1 agent asks for paladin://stripe_sk
2 broker checks the grant — deny by default
3 secret injected at egress only
4 sandboxed call — agent never sees the value
5 every resolve is hash-chain audited

Think password manager + .env manager for agents — but the agent gets a reference, never the secret. A human manages the vault like 1Password; the agent gets exactly one verb, through the broker, gated by a grant that names precisely who may resolve what, up to which authority band, optionally expiring.

NEW · SANDBOXED EGRESS

The credential never enters the tool process

The ordinary path injects a secret into a child process's environment — which any code in that child, including a prompt-injected payload, can read. Sandboxed egress closes that gap entirely. The tool runs under bwrap --unshare-allno network at all, the vault masked, a rebuilt minimal environment — and its only way out is a Unix socket to Paladin. The tool sends an unauthenticated request; Paladin attaches the credential host-side and returns only the response. The key is never in an env var, never in memory, never in a *_FILE — verified by tests that confirm the secret is absent from the child's environment and /proc/self/environ, and that direct network egress is unreachable.

Honest scope: covers HTTP(S)-shaped secrets today; needs Linux + unprivileged user namespaces, and fails closed when that isn't available (it refuses rather than silently leaking to an env var). It confines the credential, not the data a call returns.

What's inside

Built like a credential system, not a config file

🗄️

Encrypted at rest

One AES-256-GCM vault, scrypt-derived key. Names, values, metadata, and the grant table all live inside the ciphertext — an attacker with the file learns only its size.

🎟️

Deny-by-default grants

No requester resolves anything without an explicit grant naming it. Scope by host, method, and path; ceiling by authority band; expire on a TTL. The grant narrows, never widens.

📤

Egress-only injection

The only way a value leaves the vault is into a child process Paladin launches — never in argv, never in this process, never printed by any CLI command.

⛓️

Tamper-evident audit

Every resolve, deny, and grant lands in a hash-chained, HMAC-signed log. Editing, reordering, or truncating it breaks the chain — and forging one needs the vault key.

🧯

Leak tripwire

Every released value registers its hash. If a secret comes back in a tool result — even one the agent never saw go out — the guard trips and redacts before the model sees it.

📦

Standalone

pip install custodian-kernel[paladin] and you have a working vault with zero AI-agent framework installed. Paladin depends on nothing but cryptography.

Humans manage it like a password manager

Values in. Never out.

No paladin command ever prints a secret — not list, not show, not an error. The only way a value leaves is egress into a child process you launch.

paladin — credential broker CLI
# store a secret (value prompted, never echoed)
paladin add stripe_sk --profile prod --env-var STRIPE_SECRET_KEY

# grant: exactly who may resolve what, scoped and expiring
paladin grant 'stripe*' --to skill:stripe-spend \
    --host api.stripe.com --method POST --path-prefix /v1/refunds --ttl 3600

# egress: run a command with the secret injected into ITS env
paladin exec --with stripe_sk -- python bill.py

# sandboxed egress: the child has no network but the broker socket —
# the key never enters its process at all
paladin exec --sandbox --as sandbox:job --with stripe_sk -- python tool.py

# audit — walk the hash chain
paladin audit verify
How it fits Custodian

A separate package, wired in by an adapter

Paladin is standalone — it imports nothing from the kernel, and the kernel imports nothing from it (enforced by a test, not just documented). They meet only through the adapter convention.

custodian/

The kernel

Decides whether an action is allowed — bands, caps, kill switch. Its built-in adapters recognize the paladin:// URI by regex, the same way they'd recognize https:// — never by importing Paladin.

paladin/

The broker

Decides whether a credential may be materialized for that action — and materializes it so the agent process never observes the value. Works with zero AI framework installed.

talaria/

The integration

The one layer that imports both — wiring a specific broker to a specific kernel for a specific agent (Hermes). A future Claude/Codex integration would sit here, never inside the other two.

Hand the agent a reference. Keep the key.

The agent can't steal a key it never holds, can't bypass a broker it must go through, and can't use a credential for a host the grant didn't authorize.