← Back to Learn
Concept 5 min read

Opaque Handles: How Agents Use Secrets Without Seeing Them

The most fundamental concept in the Never-Leak Protocol. Opaque handles decouple AI agents from the secret values they need, making it architecturally impossible for secrets to leak through the agent's context window.

The Problem

Today, AI agents that need to call APIs or access databases receive secret values directly in their context window. An orchestrator might inject an API key into the agent's system prompt, or the agent itself might read a secret from a file or environment variable.

This is fundamentally unsafe. Any data in an LLM's context can be memorized, leaked via prompt injection, or accidentally included in outputs. The model has no concept of "confidential" versus "public" data -- everything in the context window is equally accessible.

Unsafe: Secret in agent context
# The agent sees the raw secret value
# Any prompt injection can now exfiltrate it

agent.context = "Use API key: sk-1234567890abcdef to call the API"

# The secret is now in the LLM's context window
# It can be memorized, leaked, or extracted

The NL Protocol Solution

Instead of secrets, agents work with opaque handles: references like {{nl:api-key}} that carry no information about the secret's value. The handle is just a name -- it tells you what the secret is for, but nothing about what it contains.

The agent includes these handles in action templates -- structured requests that describe what needs to be done. The NL System receives the template, resolves the handles inside an isolation boundary, executes the action, and returns only the result to the agent.

Safe: Agent uses opaque handle
{
  "type": "exec",
  "template": "curl -H 'Authorization: Bearer {{nl:api-key}}' https://api.example.com",
  "purpose": "Fetch user profile"
}

The agent never sees sk-1234567890abcdef. It only sees the result of the curl command. The secret exists only inside the isolated execution boundary, for the duration of a single action.

How It Works

The opaque handle resolution process involves six steps. At no point does the agent have access to the secret value.

1
Agent constructs action template

The agent builds a request containing {{nl:secret-name}} handles wherever secret values are needed. The template also includes a purpose string explaining why the action is necessary.

2
NL System verifies identity and scope

The system checks the agent's identity document and confirms it has a valid scope grant for each referenced secret. If any check fails, the request is rejected before any secret is accessed.

3
Secret is resolved from secure storage

The handle {{nl:api-key}} is resolved to the actual secret value inside the system's secure memory. This value never leaves the isolation boundary.

4
Command is executed in isolation

The template is populated with real values and executed in an isolated subprocess. The subprocess has no connection back to the agent -- it runs independently with a clean environment.

5
Output is scanned and sanitized

Before returning results to the agent, the output is scanned for any occurrence of secret values. If a secret appears in stdout or stderr (for example, a verbose error message that echoes credentials), it is redacted.

6
Clean result returned to agent

Only the sanitized output (stdout, stderr, exit code) is returned. The agent can reason about the result, but has no path to the underlying secret values.

Resolution Flow
Agent                    NL System                  Secure Storage
  |                          |                          |
  |  action_request           |                          |
  |  {{nl:api-key}}          |                          |
  | ---------------------->  |                          |
  |                          |  resolve handle          |
  |                          | ---------------------->  |
  |                          |  sk-1234...               |
  |                          | <----------------------  |
  |                          |                          |
  |                     [execute in isolation]       |
  |                     [scan & sanitize output]    |
  |                          |                          |
  |  action_response          |                          |
  |  (clean result only)      |                          |
  | <----------------------  |                          |

Why This Matters

Prompt injection resistance

Even if the agent is compromised through prompt injection, it cannot leak what it does not have. The attacker controls the agent's behavior, but the secret values are simply not available in the context.

Zero information leakage

The handle {{nl:api-key}} tells an attacker nothing. It is just a reference name. The attacker cannot derive, guess, or reconstruct the secret value from the handle alone.

Transparent secret rotation

Secret rotation is invisible to the agent. The handle {{nl:api-key}} stays the same; only the underlying value changes. No agent code needs to be updated, no prompts need to be rewritten.