PromptABCD
FeaturesLearnHow it worksUse casesFAQGuideBlogContext Blocks
Sign inGet started free
Sign inSign up
PromptABCD

A calm home for your best AI prompts. Save them once, find them in seconds, reuse them forever.

Product

  • Features
  • Chrome Extension
  • Free Courses
  • How it works
  • Use cases
  • Blog
  • Context Blocks
  • Export Anywhere
  • FAQ

Resources

  • User guide
  • Learn prompting
  • Sign in
  • Get started free

© 2026 PromptABCD. All rights reserved.

Privacy PolicyTerms and Conditions
Home/Blog/AI Harness/How to Handle Secrets and API Keys in a Harness
AI Harness

How to Handle Secrets and API Keys in a Harness

A Stripe key sitting in every log line is what bad agent harness secrets management looks like. This teardown rebuilds it so the model never sees a credential.

September 8, 2026·8 min read
ShareShare
⚡Featured Prompt— copy and use right now
def charge_customer(amount: int, customer_id: str) -> str:
    key = os.environ["STRIPE_KEY"]
    resp = requests.post("https://api.stripe.com/v1/charges",
                         auth=(key, ""),
                         data={"amount": amount, "customer": customer_id})
    return resp.text          # <-- returns raw response into model context

Picture this: you're debugging a flaky agent run at 11 p.m., you scroll back through the conversation log to see what the model did, and there — in plain text, in a tool result the model received and then echoed back in its reasoning — is your production Stripe key. It's been in every log line, every trace, every error report for weeks. Anyone with read access to your logging system has had your payment credentials the whole time. This is the failure mode that agent harness secrets management exists to prevent, and it's more common than anyone likes to admit, because the naive way to give an agent a secret is to just... hand it the secret.

The core problem is that a model's context is the least private place in your entire system. Everything in it gets logged, traced, and sometimes echoed back. A secret that enters the context has effectively been published. This teardown starts with the way most harnesses handle secrets, shows exactly why it leaks, and rebuilds it so the model never sees a credential at all.

Before: The Weak Prompt

Here's the common pattern — the harness passes secrets to tools by putting them where the tool can read them, which usually means the environment or, worse, the arguments.

hljs python
[object Object], ,[object Object],(,[object Object],) -> ,[object Object],:
    key = os.environ[,[object Object],]
    resp = requests.post(,[object Object],,
                         auth=(key, ,[object Object],),
                         data={,[object Object],: amount, ,[object Object],: customer_id})
    ,[object Object], resp.text          ,[object Object],

What this does: Reads the Stripe key from the environment and charges a customer, then returns the raw API response straight back to the model. The key never appears in the arguments, which feels safe — but the response often contains sensitive data, and if the tool ever errors, the exception message can include the key or the full request. That return value goes directly into the model's context and from there into every log.

Why It Fails

There are three separate leaks hiding in that twelve-line function, and most harnesses have all three.

The first is the return value. Raw API responses contain more than you think — account identifiers, partial card numbers, internal reference tokens — and dumping the whole thing into the model's context means dumping it into your logs. The model rarely needs the full response; it needs a summary of what happened.

The second is error handling. When

requests.post
fails, the exception can carry the full request, headers and auth included. An unhandled error path is the most common way a secret that was "only in the environment" ends up in a stack trace that gets logged and alerted on.

The third is the environment itself. If this tool runs inside a sandbox — and dangerous tools should — then

os.environ["STRIPE_KEY"]
means the secret was injected into the sandbox's environment, where any code running there, including agent-generated code, can read it with a single line. You've handed the credential to the least trusted part of your system.

⚠️ Common mistake: Assuming that keeping a secret out of the tool's arguments keeps it out of the model's context. The arguments are only one path. Return values, error messages, and inherited environments are three more, and secrets management has to close all four or it closes none.

After: The Improved Prompt

The rebuilt version follows one rule: secrets live only in a trusted broker, never in the model's context, never in the sandbox, never in a log. The model refers to credentials by name, and the broker substitutes the real value at the last possible moment, in trusted code, discarding it immediately after.

hljs python
[object Object], ,[object Object],:
    ,[object Object], ,[object Object],(,[object Object],):
        ,[object Object],._store = store            ,[object Object],

    ,[object Object], ,[object Object],(,[object Object],) -> ,[object Object],:
        ,[object Object],
        key = ,[object Object],._store.get(spec[,[object Object],])      ,[object Object],
        resp = requests.request(spec[,[object Object],], spec[,[object Object],],
                                auth=(key, ,[object Object],), json=spec.get(,[object Object],),
                                timeout=,[object Object],)
        ,[object Object], key                                        ,[object Object],
        ,[object Object], summarize_response(resp)                ,[object Object],

What this does: The broker runs in the trusted harness layer, fetches the secret by reference right before the call, uses it, deletes it, and returns only a scrubbed summary — never the raw response. The model asked to call an API "using the credential named

stripe_prod
"; it never learned what that credential is. The secret existed as a variable for microseconds, in trusted code, and touched neither the model nor the sandbox nor the log.

Breaking Down Each Element

Each piece closes one of the three leaks, so it's worth seeing how they map.

Reference, not value. The model and any sandboxed tool traffic in names —

secret_ref: "stripe_prod"
— while the mapping from name to real credential lives only in the broker. This is the heart of agent harness secrets management: the thing that can be persuaded (the model) never holds the thing that must stay secret (the credential).

hljs python
[object Object], ,[object Object],(,[object Object],) -> ,[object Object],:
    ,[object Object], {,[object Object],: resp.status_code,
            ,[object Object],: resp.ok,
            ,[object Object],: resp.json().get(,[object Object],) ,[object Object], resp.ok ,[object Object], ,[object Object],,
            ,[object Object],: scrub(resp.text[:,[object Object],]) ,[object Object], ,[object Object], resp.ok ,[object Object], ,[object Object],}

What this does: Returns only the fields the model actually needs — did it succeed, and the resulting charge ID — with any error text truncated and scrubbed. The full response, with all its sensitive extras, stays in the broker and dies there. The model gets enough to continue reasoning and nothing it could leak.

Just-in-time fetch. The broker pulls the secret from a vault at call time rather than holding it in a long-lived variable or config. Combined with short-lived credentials — tokens that expire in minutes — this means even a leaked value is worthless quickly.

Trusted-layer execution. Because the broker runs in the harness and not in the sandbox, the secret is never present in the environment the untrusted code can read. The sandbox gets the result of a privileged call, never the ability to make one itself.

⚡ Pro tip: Run a scrubber over everything on its way into a log or the model context, keyed to your secret patterns, as a backstop. Even with the broker pattern, a stray credential in a code path you forgot will show up somewhere eventually. A regex that redacts anything shaped like your keys is the seatbelt that catches the mistake the architecture missed.

Variations for Different Contexts

The broker pattern adapts to how much your tools need to touch secrets directly.

For tools that call a handful of known APIs, bake the calls into the broker as above — the model picks the operation, the broker owns the credentials. This is the tightest and the default choice.

For agents that need to run arbitrary user-provided integrations, issue the sandbox a short-lived, narrowly-scoped token instead of a root credential — a token that can only do the one thing this run needs, expiring in minutes. If it leaks, the blast radius is one operation and a few minutes wide.

For local development, use a secrets manager even then, and especially keep secrets out of the

.env
files people commit by accident. The habit of never letting a real credential near the model is one you want ingrained before production, not learned after an incident.

⚠️ Common mistake: Logging the request for debugging. It's tempting, when a tool call fails, to log the full outgoing request so you can see what went wrong — but that request often carries the auth header. Log the sanitized spec (method, URL, secret reference) instead of the realized request, and you get your debugging visibility without publishing your keys.

Rotating, Scoping, and Catching Leaks

Good agent harness secrets management doesn't stop at hiding the value — it assumes a leak will eventually happen anyway and limits what a leaked credential is worth. Two habits do most of that work.

Scope every credential to the narrowest thing it needs to do. An agent that reads from one table doesn't need a database admin key; it needs a read-only credential for that one table. When you can, issue credentials that are both scoped and short-lived, so a value that escapes is useless outside a tight window and a tiny slice of your system. The broker makes this natural, because it's the only thing minting credentials and can hand out a fresh, narrow one per call.

Then plan for rotation as a routine, not an emergency. Because the model only ever holds a reference like

stripe_prod
, rotating the underlying key is a change in the broker's store that no prompt, no tool, and no agent needs to know about. This is a quiet advantage of the reference pattern: the thing everyone else has to coordinate a rotation around — the code that holds the key — doesn't hold the key.

⚡ Pro tip: Rotate a credential on a schedule and then confirm the old value actually stops working. A rotation you never verify is a rotation that may have silently failed, and you find that out during the incident you were trying to prevent. Because references insulate the agent from the value, you can rotate aggressively without touching agent code — so there's no excuse not to.

⚡ Pro tip: Seed your logs and model context with a canary credential — a real-looking key that's actually a tripwire wired to alert if anyone ever uses it. If that canary is ever exercised, you know instantly that something is reading secrets out of a place it shouldn't, and you know it before a real credential gets abused the same way.

Save and Reuse This

The broker, the response summarizer, and the scrubber are exactly the kind of infrastructure you write once and must never get subtly wrong twice. A single new tool that returns a raw response undoes the whole pattern. Keep the broker and its conventions — reference-not-value, summarize-don't-return, scrub-everything — documented and versioned alongside your prompts and tool definitions in a library like PromptABCD, so every tool anyone adds inherits the same discipline, and the 11 p.m. discovery of a key in your logs becomes a mistake your architecture simply doesn't permit.

ai-harnesssecretsapi-keyssecuritybrokerredaction

Continue Reading

Managing Prompt Templates Across a Harness Codebase
AI Harness

Managing Prompt Templates Across a Harness Codebase

Four divergent copies of one prompt caused a two-day bug. Harness prompt templates management makes prompts versioned, tested, single-source artifacts instead of scattered strings.

September 10, 2026·8 min read
How to Open-Source Your Agent Harness
AI Harness

How to Open-Source Your Agent Harness

An agent harness isn't an ordinary library — it's security-sensitive infra tangled with your secrets. Release an open source agent harness without leaking a key or shipping unusable code.

September 10, 2026·8 min read
Error Taxonomy: Classifying Harness Failures
AI Harness

Error Taxonomy: Classifying Harness Failures

When every failure looks the same, you can't retry, route, or alert correctly. Agent harness error classification gives failures types that drive real behavior.

September 10, 2026·8 min read

Save the prompts from this post

PromptABCD is a free prompt manager. Paste, organize, and reuse your best AI prompts — no more hunting through chat history.

Start free →
← PreviousApproval Gates: Requiring Human Sign-Off in the HarnessNext →Building an Audit Log Into Your Harness
Share this post:
ShareShare