Your AI agents don't need your API keys
If you give an AI agent a shell and file tools, you should assume that sooner or later it will read something it shouldn't. Not out of malice — out of obedience. A prompt-injected web page, a poisoned README, or a plain hallucination is all it takes for an agent to happily run cat .env and paste the result somewhere you'll regret.
And in almost every agent framework out there, that .env is exactly where the API keys live: OpenAI, Anthropic, Gemini — the most valuable secrets on the machine, sitting one file read away from a language model that executes tools.
HydraOps takes a different position: the keys are simply not there. Not in the repository, not in the database, not in the environment the workers see. Wherever a key would appear, there is the literal string proxy. This post explains how that works, in the roughly 130 lines of Node it takes.
The placeholder trick
Every worker in HydraOps talks to cloud LLM providers through a local process called the key-proxy, listening on http://127.0.0.1:9099. Instead of calling api.openai.com with a real key, a worker calls 127.0.0.1:9099/openai/... with the placeholder key proxy. The proxy knows each provider's host and auth style, swaps the placeholder for the real key at the network boundary, and forwards the request unchanged — streaming included:
const PROVIDERS: Record<string, { host: string; keyName: string; auth: AuthStyle }> = {
openai: { host: "https://api.openai.com", keyName: "OPENAI_API_KEY", auth: "bearer" },
anthropic: { host: "https://api.anthropic.com", keyName: "ANTHROPIC_API_KEY", auth: "x-api-key" },
google: { host: "https://generativelanguage.googleapis.com", keyName: "GEMINI_API_KEY", auth: "google" },
// ...groq, xai, openrouter, mistral, leonardo
};
The nice property of this design is that the placeholder is load-bearing. It's not that the keys are hidden somewhere clever inside the project — they were never put there. You can share your project folder, your logs, your SQLite database, even a full memory dump of a worker process, and the only "credential" anyone will find is the seven-letter word proxy.
What 130 lines buy you
The proxy is a single small HTTP server, and each of its details closes a specific hole:
- Incoming auth headers are stripped.
authorization,x-api-key,x-goog-api-keyand friends are dropped from every request before forwarding. Whatever a worker (or a manipulated prompt) puts in those headers never reaches the provider — the proxy always writes its own. - Auth is injected per provider style. Bearer token for most,
x-api-keyfor Anthropic, and for Google both the header and a rewrite of any?key=query parameter. - Bodies stream through untouched, in both directions, so SSE token streaming works exactly as if the worker were talking to the provider directly.
- The key store is hot-reloaded by mtime. Change a key in the Settings view and the next request uses it — no restart, no cached stale secret.
/healthreports which providers are configured, never the values. The UI needs to know "is there an OpenAI key?"; it never needs to know the key.- It binds to
127.0.0.1only. Nothing on your network can reach it, ever.
let keysCache = { mtimeMs: -1, keys: {} };
async function loadKeys() {
const st = await stat(KEYS_PATH);
if (st.mtimeMs !== keysCache.mtimeMs) {
keysCache = { mtimeMs: st.mtimeMs, keys: JSON.parse(await readFile(KEYS_PATH, "utf-8")) };
}
return keysCache.keys;
}
No frameworks, no dependencies beyond Node's standard library. Small enough to audit in one sitting — which, for the one process that holds your keys, is a feature in itself.
Where the real keys actually live
The key store is a JSON file outside the project tree, on purpose: %APPDATA%\hydraops\keys.json on Windows, ~/.config/hydraops/keys.json on Linux. Agent file tools are scoped to the repository, so they can't reach it by construction — and HydraOps' tool guard additionally blocks credential paths outright, as defense in depth.
The practical consequences are the ones that matter day to day:
- Backups and git history of your project contain no secrets, because there were never secrets in it.
- A prompt-injected agent that dumps its entire environment gets
proxy. - Rotating a key is editing one file (or one Settings field), not grepping a codebase.
What this doesn't do
Honesty section. The credential firewall keeps keys out of reach; it does not make agents safe in general:
- An agent can still spend your money — it has legitimate access to the providers through the proxy. The firewall protects the credential, not the quota.
- The tool guard blocks credential paths, catastrophic commands and requests to internal network addresses (anti-SSRF), and redacts secrets from tool results — but it's a filter, not a sandbox. Full isolation needs containers, and that's on the roadmap.
- Your own add-ons in
my_addons/are your code and run unrestricted, by design.
The rule of thumb we give users: don't ask an agent to do anything you wouldn't let a script running as your user do.
Steal this pattern
None of this is specific to HydraOps. If you're building any system where an LLM executes tools next to provider credentials, the pattern transfers directly: put the secrets outside the tree the agent can touch, give the agent a placeholder, and let a tiny local proxy do the substitution at the last possible moment.
The whole implementation is one file on GitHub, Apache 2.0 licensed. The broader security model — tool guard, network defaults, auth tokens — is described in the security documentation.
HydraOps is a self-hosted multi-agent AI system: one chat, several agents with their own personality, model and tools, working on tasks in parallel. Try it here.