Most secret managers solve a simple problem: store credentials, retrieve them at runtime. That works when you trust the machine running your code. But AI agents break that assumption.
An agent running on a user’s laptop, inside a container, or on a remote VM can be prompt-injected, have its memory inspected, or just be running in an environment you don’t fully control. The moment you inject a secret into that environment — even for one API call — it’s exposed.
Janee’s Runner/Authority architecture solves this by splitting the secret manager into two components. The part talking to the agent never has secrets. The part holding secrets never talks to the agent.
The core idea: two halves of a secret manager
In a standard Janee deployment, the MCP server runs alongside the agent. It holds the encrypted config, decrypts credentials at runtime, injects them into API calls, and scrubs them from output. This works well for single-machine setups — your Claude Desktop, a trusted CI server.
But when the agent’s environment is untrusted, you need a different model:
Untrusted Environment Trusted Environment
┌──────────────────────┐ ┌──────────────────────┐
│ │ │ │
│ Agent │ │ Authority │
│ │ │ network │ │ │
│ ▼ │ │ ▼ │
│ Runner (MCP server) │────────────▶│ Credentials │
│ - no secrets │ authorize │ - encrypted store │
│ - no config keys │ + execute │ - policy engine │
│ - just a proxy │ │ - audit log │
│ │ │ │
└──────────────────────┘ └──────────────────────┘
The Runner is a lightweight MCP server that sits next to the agent. It exposes the same janee_exec and janee_list tools that a normal Janee instance would. But it holds no credentials and no encryption keys. When the agent calls a tool, the Runner forwards the request to the Authority over an authenticated API.
The Authority is the real secret manager. It holds the encrypted config, evaluates access policies, decrypts credentials, executes the API call, scrubs secrets from the response, and returns the clean result. It runs on infrastructure you control — a private server, a locked-down VM, your orchestrator process.
Why not just use a remote Janee instance?
You might wonder: if the Authority runs remotely, why not just point the agent at a remote Janee HTTP endpoint?
Two reasons.
First, the Runner preserves the local MCP experience. The agent connects to a local MCP server over stdio or a local HTTP port. No special configuration, no remote URLs in the MCP client config. To the agent (and to the user configuring it), it looks exactly like a normal Janee installation.
Second, agent identity flows through correctly. Each agent connecting to the Runner gets its own MCP session. When the Runner forwards a request, it opens a separate session on the Authority for each agent identity. This means the Authority’s access control sees creature:patch or creature:audit — not a single generic “runner” identity. Per-agent policies work exactly as they do in a single-machine deployment.
The authorization flow
When an agent calls janee_exec through a Runner, here’s what happens step by step:
- Agent → Runner: The agent sends a standard MCP
tools/callwithjanee_exec, specifying the capability, command, and arguments. - Runner → Authority (authorize): The Runner sends an authorization request containing the runner’s identity, the agent’s identity, the capability name, the full command, and an optional reason string.
- Authority evaluates: The Authority checks the capability exists, the agent is allowed to use it, the command matches the policy’s allowlist, and grants are within rate limits. If everything passes, it returns a grant containing the environment variables (decrypted secrets), a policy hash, timeout, and values that must be scrubbed from output.
- Runner executes: The Runner spawns the command with the granted environment. It monitors stdout/stderr, scrubs any secret values the Authority flagged, and enforces the timeout.
- Runner → Authority (complete): After execution, the Runner reports back: exit code, duration, byte counts, and how many scrub hits occurred. The Authority logs everything for audit.
- Runner → Agent: The scrubbed output goes back to the agent. At no point does the agent see the actual credential values.
The key insight: even though the Runner briefly holds decrypted values in the child process environment, it never stores them. They exist only for the duration of the spawned command and are scrubbed from any output before the agent sees it. An attacker who compromises the agent’s reasoning gets nothing — the values aren’t in the context window, the tool output, or the MCP session state.
The grant model
The Authority doesn’t just return credentials — it returns a scoped grant that constrains exactly what the Runner can do:
{
"grantId": "g_7f3a...",
"grantExpiresAt": "2026-02-23T10:05:00Z",
"effectiveTimeoutMs": 30000,
"envInjections": {
"GITHUB_TOKEN": "ghs_xxxxx..."
},
"scrubValues": ["ghs_xxxxx..."],
"constraints": {
"policyHash": "sha256:ab12cd...",
"executable": "gh",
"command": ["gh", "issue", "create", "--repo", "..."]
}
}
The grant expires (typically in seconds, not hours). It locks the command to the exact executable and arguments the policy allows. The scrubValues array tells the Runner which strings must never appear in tool output. And the policyHash lets the Authority verify that the Runner executed against the same policy version it authorized.
When to use the split
Not every deployment needs Runner/Authority separation. If you’re running Janee alongside Claude Desktop on your own laptop, the default single-process mode is simpler and works great.
The split makes sense when:
- The agent runs in an untrusted environment — a shared container, a user’s machine, a sandboxed VM. You don’t want secrets on that machine at all.
- Multiple agents share credentials — an orchestrator (like OpenSeed) spawns many agents that all need GitHub access. One Authority, many Runners.
- You need centralized audit — the Authority is the single point where every API call is logged with agent identity, timing, and outcome. No need to aggregate logs from multiple Janee instances.
- Compliance requires secret isolation — some environments mandate that credentials never leave a specific security boundary. The Authority stays inside that boundary; Runners can be anywhere.
Running it
Start the Authority on your trusted machine:
# On the trusted host — holds credentials, evaluates policy
janee authority --port 9700 --api-key "your-runner-key"
Start a Runner alongside each agent:
# On the agent's machine — no secrets, just a proxy
janee serve --runner --authority-url http://authority:9700 \
--api-key "your-runner-key"
The Runner exposes the standard MCP interface (stdio or HTTP). Configure your agent’s MCP client to connect to it exactly as you would a normal Janee instance. The agent never needs to know it’s talking to a proxy.
Trust boundaries, drawn clearly
The Runner/Authority split gives you something rare in the AI agent space: a clearly defined trust boundary. The agent and its environment are on one side. Your secrets are on the other. The only thing that crosses the boundary is a scoped, time-limited, policy-evaluated grant — and even the results of that grant are scrubbed before they reach the agent.
This isn’t just defense in depth. It’s a fundamentally different security model from “encrypt your .env file” or “rotate keys frequently.” Those approaches still put secrets on the agent’s machine. Janee’s split mode doesn’t.
If you’re building multi-agent systems, or deploying agents into environments you don’t fully control, the Runner/Authority pattern gives you the security properties you actually need — without changing how your agents work.
Try Janee
Give your AI agents secure, scoped API access — without exposing your secrets.
View on GitHub →