@percent20/mist-plugin
officialMist — MFA for agentic AI. Claude Code plugin.
Mist
MFA for agentic AI. Mist scores every prompt and tool call in Claude Code on a 7-axis risk vector (overall, financial, destruction, comms, access, production, scale). High-risk actions get gated. Approval happens silently when you’re at a bound location anchor — otherwise it falls through to Claude Code’s native terminal prompt with the risk score attached.
Install
Want to try Mist in a sandbox first? See
DOCKER.mdfor a Docker lab that runs Claude Code + Mist in an isolated container without touching your host config.
Recommended (interactive, inside Claude Code)
In an active Claude Code session:
/plugin marketplace add github:percent-20/mist-plugin
/plugin install mist@mist
Claude Code clones the repo into its plugin cache, registers the marketplace, and enables the plugin’s hooks declared in settings.json. Restart the session if hooks don’t fire immediately.
If the hooks fail at runtime with “Cannot find module” errors, install the plugin’s npm dependencies once in the cached copy:
cd ~/.claude/plugins/cache/mist/mist/<version>
npm install
(Required only if your Claude Code version doesn’t auto-install plugin dependencies.)
Non-interactive (CI / scripted setups)
Add the marketplace + plugin entries to ~/.claude/settings.json directly:
{
"extraKnownMarketplaces": {
"mist": {
"source": { "source": "github", "repo": "percent-20/mist-plugin" }
}
},
"enabledPlugins": { "mist@mist": true }
}
Restart Claude Code. The plugin gets fetched and enabled on next session start. This is the pattern the Docker lab uses (see DOCKER.md).
Local development
Clone the repo, install deps, then point Claude Code at it via a local marketplace source:
git clone https://github.com/percent-20/mist-plugin
cd mist-plugin
npm install
Then add to ~/.claude/settings.json:
{
"extraKnownMarketplaces": {
"mist-local": {
"source": { "source": "local", "path": "/absolute/path/to/mist-plugin" }
}
},
"enabledPlugins": { "mist@mist-local": true }
}
Edits to source files are picked up after /reload-plugins or a session restart.
Standalone classifier CLI (no Claude Code)
The npm package also ships a mist-classify command for shell use — handy for evaluating prompts without booting Claude Code:
npm install -g @percent20/mist-plugin
mist-classify "buy 50000 macbook pros"
Or smoke-test the hooks directly from a clone:
echo '{"prompt":"buy 50000 macbook pros"}' | node hooks/user-prompt-submit.js
# → {"hookSpecificOutput":{"permissionDecision":"ask","reason":"Mist gate (overall=9, financial=9, scale=9): buy 50000 macbook pros"}}
node bin/classify.js "transfer 50000 dollars to account 1234567890"
# → vector: [9,9,0,0,0,0,9] gate: FIRES (overall>=7, financial>=5, scale>=8)
Either path is zero-config — Mist becomes active the moment the plugin loads. No keys, no pairing required. The plugin uses your existing ANTHROPIC_API_KEY to run a tiny Haiku classifier locally, and surfaces high-risk requests through Claude Code’s built-in approval prompt with the risk score attached.
Optional bubble binding (recommended)
Bind a Mist bubble — a vault tied to a physical location — and Mist will auto-approve silently whenever you’re inside it. No prompt, no notification, sub-second.
/mist init ← first-time setup (API key + initial bubble bind)
/mist bind ← re-bind to a different bubble later
The SafeRoom app maintains the location signal that determines bubble membership. You install SafeRoom once, set the anchor, and forget about it. When Claude Code does something risky and you’re at the bound location, the gate disappears.
If you’re outside the bubble, the gate denies by default. Run /mist rules policy ask to fall back to the native terminal prompt instead — useful for developers who travel; not recommended for headless / cloud Claude Code instances where there’s no human at the terminal to ask.
What gets gated
The classifier outputs seven integers 0–9:
overall, financial, destruction, comms, access, production, scale
A challenge fires when ANY axis crosses its threshold. Defaults:
overall >= 7
financial >= 5
destruction >= 3
comms >= 8
access >= 7
production >= 6
scale >= 8
Tune via /mist rules set <category> <threshold>.
Examples of what scores trigger:
- “explain typescript generics” →
0,0,0,0,0,0,0— silent - “rm -rf node_modules” →
4,0,5,0,0,0,1— gate fires (destruction>=3) - “buy 50000 macbook pros” →
9,9,0,0,0,0,9— gate fires - “git push —force origin main” →
7,0,5,0,0,8,2— gate fires - “delete the production database” →
9,0,9,0,0,9,4— gate fires - “clean up the warehouse inventory db” →
3,0,3,0,0,0,2— gate fires (destruction>=3, the borderline case)
How approval works
When the classifier crosses a threshold:
- No vault bound? Hook returns
permissionDecision: "ask"and Claude Code’s native terminal prompt appears with the Mist score as the reason. - Vault bound, inside the bubble? Hook posts a single synchronous challenge to
POST /api/v2/mfa_vaults/:vault_uid/challenge(the same endpoint the OpenClaw plugin already uses). Server runsVisitTracker.is_user_in_bubble?and returnsstatus: "approved". Action proceeds silently. - Vault bound, outside the bubble? Server returns
status: "denied". Default: hook denies the action. Opt in tooutside_bubble_policy: "ask"(via/mist rules policy ask) to fall back to the terminal prompt instead. - Anything fails (network down, API unreachable): hook returns
"ask"so Claude Code never silently locks you out.
No polling, no async challenge waiting. The classifier runs locally; the API call sends only { instance_id } — no prompt content, no score vector, no tool name ever leave your machine.
Commands
/mist init— first-time setup/mist bind— re-bind to a different bubble/mist status— show binding, recent decisions/mist rules— view/edit thresholds andoutside_bubble_policy
Privacy
Prompts are scored locally via the Anthropic API using your own key — they don’t leave your machine en route to Mist’s servers. The Mist API request body contains only your instance_id — no prompt content, no score vector, no tool name. If no vault is bound, the Mist API is never contacted at all.