The story that kicked this off is short and painful. Someone moved a project from one coding harness to another, let an LLM do the grunt work, and the agent happily read credentials out of 1Password and wrote them into a project file. The repo later got open-sourced. Nobody noticed the keys were still in there, so they shipped straight to GitHub. The fix was the usual fire drill: rotate every affected service key, one by one, while filing support tickets. What stuck with me wasn't the mistake itself. It was the honest follow-up from the person teling it: I don't fully isolate this stuff either. I drop some keys in a `.env`, hand it to the agent, and after that I have no idea whether it writes them to a file, prints them into a log, or leaks them through some tool call I never watched. You can't babysit every step. The person who hit this even started an open-source project (they called it Plankton) with a neat core idea: when an agent asks for a secret, read the call chain, and only release the value if the caller's behavior matches the exposure surface the user declared.
That idea is worth stealing regardless of whether you use their tool. So let me walk through how I'd rebuild the same shape from scratch with a thin Python broker and a hand-inspectable log, and where the honest limits sit.
The real problem is trust, not storage
Most secret-management advice tells you where to keep keys. Vault, sops, a secrets manager, encrypted at rest. All fine. But none of that addresses the moment that actually burned this person: the agent already had legitimate access, and then did something dumb with the plaintext after the read.
Once a value leaves the store and lands in the agent's context, it can go anywhere the agent goes. Into a file it edits. Into a debug log. Into a commit. The threat isn't a stranger stealing the vault. It's your own automation being careless with a value it was allowed to see.
So the design goal shifts. Instead of "kep the key safe at rest," it becomes "put a checkpoint in front of every read, decide whether this specific request is allowed, and record what happened so I can inspect it later." That's a broker plus an audit log.
The broker: a checkpoint in front of every read
The broker is a small Python function that sits between the agent and the actual secret. The agent never touches the store directly. It asks the broker, and the broker decides.
The check I care about most is a declared exposure surface. Before anything runs, you write down what each secret is allowed to be used for. `STRIPE_KEY` is for outbound HTTPS calls to Stripe, nothing else. `DB_PASSWORD` is for a local socket connection. If a caller asks for `STRIPE_KEY` while claiming it needs to write it into a file, the request doesn't match the declaration, and the broker refuses.
import sqlite3, json, time, hashlib
# What each secret is allowed to be used for.
# Anything not listed here is refused by default.
POLICY = {
"STRIPE_KEY": {"purposes": {"http_call"}, "allow_domains": {"api.stripe.com"}},
"DB_PASSWORD": {"purposes": {"db_connect"}, "allow_domains": set()},
}
def get_secret(name, declared_purpose, declared_target, caller):
policy = POLICY.get(name)
decision = "deny"
reason = "unknown_secret"
if policy:
purpose_ok = declared_purpose in policy["purposes"]
target_ok = (not policy["allow_domains"]) or (declared_target in policy["allow_domains"])
if purpose_ok and target_ok:
decision, reason = "allow", "matched_declaration"
elif not purpose_ok:
reason = "purpose_mismatch"
else:
reason = "target_mismatch"
_audit(name, declared_purpose, declared_target, caller, decision, reason)
if decision == "allow":
return _load_actual_value(name)
raise PermissionError(f"{name} refused: {reason}")The key move here is deny-by-default. A secret with no policy entry never gets released. When the agent invents a new integration you didn't plan for, it fails loudly and you go add the policy on purpose, rather than the value slipping out silently.
Reading the actual call chain (who called the broker, through what stack) is doable in Python with the `inspect` and `traceback` modules, and it's the part that most closely matches Plankton's described approach. It's also the part I'd treat with the most suspicion, because a determined or confused agent operating in the same process can shape whatever it declares. Treat the declaration as a tripwire and a record, not a hard wall. More on that below.
The audit log: SQLite you can actually read
Every request, allowed or denied, goes into a row. Not a value, a request. You never log the secret itself, only its name, the declared purpose, the declared target, the decision, and a hash you can use to correlate without exposing anything.
def _audit(name, purpose, target, caller, decision, reason):
con = sqlite3.connect("secret_audit.db")
con.execute("""
CREATE TABLE IF NOT EXISTS reads (
ts REAL, secret TEXT, purpose TEXT, target TEXT,
caller TEXT, decision TEXT, reason TEXT, chain_hash TEXT
)"")
chain_hash = hashlib.sha256(f"{name}|{caller}|{purpose}".encode()).hexdigest()[:16]
con.execute(
"INSERT INTO reads VALUES (?,?,?,?,?)",
(time.time(), name, purpose, target, caller, decision, reason, chain_hash),
)
con.commit()
con.close()I chose SQLite here on purpose. It's a single file, it needs no server, and you can open it and eyeball it after any suspicious session. When something fels off, `SELECT secret, decision, reason, count(*) FROM reads GROUP BY 1,2,3` tells you in one glance whether the agent has been hammering a secret it shouldn't touch, or whether a denied purpose keeps showing up. That's the hand-inspection the original poster wished they'd had before the keys hit GitHub.
Rebuilding this on VicroCode
The whole pattern is small Python and one database file, which lines up cleanly with what the platform actually offers. You can run Python online for the broker logic, kep `secret_audit.db` under file management, and open it directly with the built-in SQLite editor to run the review queries without dropping into a shell.
If your agent lives on the platform too, expose the broker through API Endpoint Hosting or an in-platform tool call, so the agent's only route to a secret is `get_secret(...)` and never a raw file read. That's the structural win: there's one door, it's instrumented, and it logs. Building the agent side of this is squarely in AI coding territory, where you're wiring the assistant to call your tool instead of reaching for the environment directly.
A few boundaries worth stating plainly. If your secrets live in an external manager like 1Password, the broker still has to fetch the real value from wherever it's stored, and the platform's confirmed capabilities cover in-platform Python, hosted endpoints, tool calls, file management, and SQLite, not a managed connector to a third-party vault. You'd write that fetch yourself inside `_load_actual_value`. And this pattern controls reads through the broker; it does not sandbox what the agent does with a value after a legitimate release, nor does it scan your commits. The community thread pointed at pre-push hooks and secret scanners for exactly that gap, and those live outside this design.
What this buys you, and what it doesn't
The honest framing: this shrinks the window where a secret leaks silently, and it gives you a log to inspect when you're suspicious. Whether it would have stopped this specific leak, or how much it reduces leaks in general, is unverified. I haven't measured it, and neither has the original project as far as the thread shows. A broker in the same process as agent is a checkpoint, not a vault boundary. It's strong against careless behavior (agent grabs a key and dumps it somewhere unexpected) and weak against an agent that will say whatever it takes to pass the check.
What I'd actually claim is narower and defensible. You move from "I hand the agent a `.env` and hope" to "every secret read is an explicit, policy-checked, logged event." That alone changes the debugging story. When keys leak today, most people find out from a rotated-credentials fire drill. With an audit table, you at least get to open the file and see which secret was requested, for what declared reason, and whether the broker let it through. Given how often these threads describe the same after-the-fact scramble, having the record beats not having it.
Start with the two or three secrets that would hurt most if they leaked, write their policies by hand, route the agent through the broker, and read the log after your next real session. Expand the policy only when a denied request turns out to be legitimate. Small, boring, and inspectable tends to survive contact with a coding agent better than clever.