Zero-knowledge credentials for AI agents: how keyBunker and keyCocoon keep secrets out of the agent's context
Handing an autonomous agent a real login is a different problem than handing a human one. A human who types a password into a form has the same exposure whether or not anything is watching over their shoulder. An agent is different: everything it sees becomes part of a prompt, a context window, a tool call, or a transcript - artifacts that get logged, cached, replayed into future model calls, and often persisted to disk for debugging or auditing. If a credential ever passes through that plane, even once, it's no longer a secret in any meaningful sense. It's a string sitting in a session file.
This is the specific problem webSlinger's credential architecture is built to avoid, and it's worth being precise about the mechanism rather than just asserting "we don't store your passwords," because the mechanism is what makes the claim true. This post walks through how keyBunker and keyCocoon keep credential material out of every path an agent - or webSlinger itself - can read, log, or replay, grounded in the actual message-passing code rather than just the marketing description.
Three components, one boundary
The architecture splits credential handling across three pieces with a hard boundary between automation logic and secret material:
- webSlinger (the extension and the agent-facing MCP server) drives navigation - clicks, page transitions, form interactions - but is architecturally incapable of holding a credential. It knows a domain and a username, and which form field a credential belongs in. It never receives the value itself.
- keyCocoon is a separate browser extension whose only job is to act as a bridge. It receives credential requests from webSlinger, forwards them to keyBunker, and injects whatever comes back directly into the page - never through webSlinger.
- keyBunker is a local, native application holding an encrypted credential vault on the user's own machine. It's the only component that ever has a plaintext password or TOTP secret in hand, and it only produces one on request, authenticated, for immediate one-time use.
We checked this boundary against the actual extension code rather than taking the architecture diagrams on faith. In automation-core.js, the functions that request a login or TOTP code (loginWithKeyCocoon(domain, username, passwordSelectors, submitSelectors, ...) and submitTOTPWithKeyCocoon(domain, username, totpSelectors, submitSelectors, ...)) send exactly that signature to the background script - domain, username, and selector references, nothing else. The background script relays it to keyCocoon as a login-request or totp-request message over Chrome's cross-extension messaging API, again carrying only domain, username, and selectors. There is no field in that message shape for a password or secret, because webSlinger's code never has one to put there. A session map recorded during a login - the artifact a generated automation script is built from - stores the same thing: the password field's selector, not its value. The credential simply never enters webSlinger's data model at any layer, recorded or live.
What happens on the other side of that boundary
Once keyCocoon receives a login-request, its handling (in kc_background.js) proceeds in order: confirm there's an active, unexpired authentication session (more below) - if not, the request fails here, before anything else happens. Generate a fresh RSA-2048 keypair, in-browser, for this request only. Send the domain, username, and the new public key to keyBunker over Chrome's native messaging channel (chrome.runtime.sendNativeMessage('com.keybunker.helper', ...)) with the command getCredentials. keyBunker looks up the matching vault entry, encrypts the password with the public key it was just given, and returns the ciphertext. keyCocoon decrypts it locally, in memory, using the private key from the keypair it generated a moment earlier, then sends the decrypted password to the page's content script already running the automation, which fills the field and submits the form. Immediately after, the code explicitly nulls and deletes the credential from the response object it just used - credentials.password = null; delete credentials.password; delete credentials; - rather than leaving it for garbage collection to clean up eventually.
Two things about this are worth calling out. First, the encryption isn't decorative: native messaging between an extension and a local process is a local IPC channel, but generating a one-time keypair and having keyBunker encrypt to it means the plaintext password exists in exactly two places for its entire lifetime - inside keyBunker's process, and briefly inside keyCocoon's memory during injection - never on disk, never in a log line, never in a message any other code (webSlinger included) could intercept. Second, the explicit delete calls are a deliberate choice, not an accident of the language: the code actively destroys the reference the moment it's no longer needed rather than relying on eventual garbage collection to make the point moot.
TOTP codes follow the same shape with one additional guarantee: the shared secret - the seed value that generates the 6-digit codes - never leaves keyBunker at all. keyBunker generates the current code locally, following the public RFC 6238 algorithm (the same standard an authenticator app uses), and only the code, already expiring in 30 seconds, is handed to keyCocoon for injection. Compromising that value in transit gets an attacker a code that's useless by the time they'd have a use for it, not something that generates future codes.
Master-password session auth, not standing access
keyCocoon cannot retrieve anything from keyBunker without an active, time-boxed authentication session. The user unlocks keyBunker with a master password and chooses a session duration - anywhere from 5 minutes up to 24 hours - and only within that window will keyBunker answer credential requests at all. Outside that window, every request fails at the authentication check, the very first step above, before any key generation or native messaging happens. There is deliberately no recovery path for a forgotten master password: the vault itself is encrypted with AES-256 using a key derived from that password, so the vault is only ever as accessible as the password protecting it, on the user's own disk, with no cloud copy to fall back on and nothing for a webSlinger server breach to expose.
Why this specifically matters for an agent, not a human
A human filling in their own password already trusts themselves with it. The question this architecture answers is different: what happens when the thing deciding to log in is an autonomous process whose entire working state - every instruction it's given, everything it's told to do, the value of every tool call it makes - is exactly the material that gets captured into a transcript, possibly reviewed by someone other than the account owner, possibly replayed into a future prompt, possibly logged by whatever framework is orchestrating it.
Look at where credential release actually happens in the flow above: entirely inside cross-extension messaging and native messaging, two channels an MCP client has no visibility into and no way to address. The webSlinger MCP server an agent actually talks to has no credential-shaped tool at all - no login, no get_password, no submit_totp, by explicit design. An agent cannot request a credential because there is no tool through which to ask for one; the only thing it can do is call run_script on an automation whose login step was already recorded, once, by a human who authenticated to keyCocoon themselves. There is no code path - not a bug to be patched, but an absent path - by which a credential could end up in anything the agent reads, logs, or passes to another model call. The record of what the agent did contains a domain, a username, and a success/failure result; it cannot contain a password or TOTP code, because those values were never routed anywhere the agent's transcript could observe them.
The same absence of access closes off a second worry: a compromised or carelessly-prompted agent tampering with a script to smuggle a credential out. Because the MCP server has no write path to scripts, an agent cannot modify one in the first place - and even setting that aside, every credentialed script is registered with keyBunker as a SHA-256 hash at first run, and keyBunker refuses to release a credential to a script whose current bytes don't match that registered hash. A tampered script simply stops being able to authenticate, regardless of who or what tampered with it.
How to try it
Because the free Scraper tier has no automation executions, seeing this in practice requires the Trial tier - the same on-ramp described for the selector-validation side of the platform. Set up a keyBunker vault, add a credential for a site you're comfortable testing against, record a script that logs in, and run that script through the MCP server's run_script tool from an agent session. Read back the output_data and execution summary the tool returns, and the run's transcript on the agent's side - the password and TOTP code you used are, provably, nowhere in either.