safe-ai-skill
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Security firewall and supply-chain verifier for Claude Code / Codex / Agent skills and MCPs.
safe-ai-skill
safe-ai-skill is a Claude Code security plugin — a Rust hook-based firewall and registry-free supply-chain verifier that works with any skills, MCPs, or agents to add a policy-driven security layer. It was built with solanabr/solana-ai-kit as the reference integration (gating Solana transactions and deploys, verifying its ext/ submodules and skill-registry.json catalog), but the gates, guards, and verifier are general-purpose and protect any Claude Code setup.
At PreToolUse it intercepts commands, MCP calls, and secret-file reads before they execute. At SessionStart it walks every installed skill and MCP submodule, pins content to git SHAs, and reports anything that drifted. Hard guards — authority changes, account closes, irreversible on-chain actions, secret reads — are never relaxed by any tier, grant or flag. The one close that is not a hard guard is a program buffer close: a confirmed destructive grant can allow it below High.
Read this first: what this plugin cannot protect on its own
[!WARNING]
safe-ai-skill's firewall is hook-level string analysis, and it has known holes. Every gate reads the text of a tool call and decides from that text. None of them watches what the call does once it runs, so a determined or prompt-injected model can get past them. Each adversarial review round of this engine found new bypasses, and we don't think that tail is exhausted. A Claude Code plugin can't shipsandboxorpermissionssettings (Claude Code drops both keys from a plugin), so the plugin can't close these holes itself.The solana-ai-kit implementation (OS sandbox plus permission rules) closes most of them. Run both. Without the kit, turn on Claude Code's own sandbox with the recommended
denyReadanddeniedDomainsentries in SECURITY.md.
Known holes, in short (the full table, with what closes each, is in SECURITY.md):
- Cross-call write-then-run: a script written in one call and run in a later one.
- Encoded and indirect forms:
base64 -d,$'…', variables, runtime-built paths,python3 -cone-liners. - Bash writes to
.env(sed -i,perl -pi): not covered at any tier. - Any process running as you outside the sandbox: test suites,
build.rs, postinstall scripts, MCP server processes. - Values already in context: a secret that was read has reached the model provider and the transcripts; nothing recalls it.
- Failed tool calls are not redacted: redaction applies to successful tool results only. When a Bash command exits non-zero (or an MCP or Read call errors), Claude Code gives the model the raw output and offers hooks no way to replace it; the plugin only warns you and the model when that output held a secret.
- Egress: no exfil tripwire ships in 1.1.0 (planned, #19). Sending a secret file by path (
curl -d @.env,-T,-F,scp) is denied and an inlinecurl/wgetPOST to a non-localhost host asks (below High, not when it goes to a known Solana RPC host), but requests to request-bin, tunnel and paste hosts, IP literals, redirects, DNS andWebFetchURLs are not gated. The kit's sandboxdeniedDomains, or Claude Code's own sandbox with the entries in SECURITY.md, closes the host list. - Code run through an MCP executor (for example context-mode's
ctx_execute): shell code gets the same checks as a Bash call, but code in other languages gets only its string literals checked as paths and shell commands, and a Solana CLI named there asks. Anything built at runtime gets through, as in Bash. - Directory search through Bash
grep/rg: only a pattern nudge applies there. - Engine crash or tampering by code running as you: the confidentiality gates fail open (the on-chain, MCP and Skill gates block), and the state's HMAC makes tampering evident, not impossible.
The kit's sandbox and permission rules close most of these because they enforce file and network policy on every sandboxed process, not on command text. Neither layer closes values already in context, WebFetch URLs [unverified], DNS, MCP server processes, or commands the kit leads with excludedCommands (anchor test* and surfpool * run unsandboxed).
Recommended: run safe-ai-skill together with solana-ai-kit's firewall at Relaxed or above, or enable Claude Code's own sandbox with the denyRead and deniedDomains entries in SECURITY.md. safe-ai-skill doctor checks your settings and prints a denyRead snippet. The scope split between the two projects is proposed in ai-kit#264 and ai-kit#265; until the kit moves its plugin pin, it still ships safe-ai-skill 1.0.0.
Install
safe-ai-skill has two layers. The plugin is the always-on runtime firewall (SessionStart verifier + PreToolUse gates). The CLI is the safe-ai-skill binary that powers the Tier 1/2 commands (registry, add skill, add mcp, install, verify, status). Most users want the plugin; install the CLI too if you want to gate skill/MCP installs before they run.
Plugin (runtime firewall)
Requires Claude Code 2.1.295 or newer. Older versions still run the hooks but ignore their onFailure setting, so a hook that can't start never blocks anything. The prebuilt engine covers macOS (arm64, x64), Linux (x64, arm64) and Windows (x64, also used on ARM). The Linux builds are static (musl), so they run on any distribution, Alpine included. On Windows, Claude Code must use Git Bash: the shell gates cover the Bash tool only, PowerShell tool calls are not gated, and PowerShell-only setups fail open with a notice. SessionStart warns when the plugin is installed from more than one marketplace (only one copy's hooks run) and when it can tell Claude Code is older than 2.1.295.
claude plugin marketplace add solanabr/safe-ai-skill
claude plugin install safe-ai-skill@safe-ai-skill
Every Claude Code session is protected automatically from that point. No per-project configuration required.
Dev install: claude plugin marketplace add . from the repo root, then claude plugin install safe-ai-skill@safe-ai-skill.
solana-ai-kit users: do nothing. The kit's stbr marketplace already installs safe-ai-skill for you. Don't add this marketplace as well: with two copies installed, only one copy's hooks register, and you can't tell which one.
Moving from safe-ai-skill@stbr
Between 1.0.0 and 1.0.1 this README said to install safe-ai-skill@stbr. The solana-ai-kit marketplace is also named stbr, and removing it uninstalls the kit and deletes its data. Check what your stbr is before you remove anything:
claude plugin marketplace list
If
stbrcomes from anything other thansolanabr/safe-ai-skill(for examplesolanabr/solana-ai-kit), stop. You have the kit's copy and there is nothing to move.If
stbrcomes fromsolanabr/safe-ai-skill, switch to the new name:# only if 'marketplace list' shows stbr → solanabr/safe-ai-skill claude plugin uninstall safe-ai-skill@stbr --keep-data claude plugin marketplace remove stbr claude plugin marketplace add solanabr/safe-ai-skill claude plugin install safe-ai-skill@safe-ai-skill
The plugin keeps its pins, grants and audit log in a directory named after plugin@marketplace, so the new install starts empty, and its first session pins whatever is on disk at that moment. To keep your existing pins, copy the old state across before you start a session:
mkdir -p ~/.claude/plugins/data/safe-ai-skill-safe-ai-skill
cp -R ~/.claude/plugins/data/safe-ai-skill-stbr/. ~/.claude/plugins/data/safe-ai-skill-safe-ai-skill/
CLI (safe-ai-skill command)
The plugin runs the firewall; the Tier 1/2 commands need the safe-ai-skill binary on your PATH. Pick one:
npm
npm install -g @stbr/safe-ai-skill # installs the CLI globally (command: safe-ai-skill)
npx @stbr/safe-ai-skill add skill <name|url> # or run without installing
safe-ai-skill add skill <name|url> is the headline command — it runs the full verification pipeline on the fetched content before any install touches your machine (see Usage tiers).
Standalone script (no npm or cargo)
curl -fsSL https://raw.githubusercontent.com/solanabr/safe-ai-skill/main/install.sh | sh
Downloads the prebuilt binary for your platform and verifies its SHA-256 against the release's published SHA256SUMS before installing — checksum-pinned, not an opaque pipe. Set SAFE_AI_SKILL_BIN_DIR to override the install location (default ~/.local/bin).
cargo (any platform with a Rust toolchain)
cargo install safe-ai-skill
The npm and standalone paths download the binary matching their release tag and abort on any checksum mismatch.
What it protects
Runtime (PreToolUse firewall)
- CLI commands: any
solana,spl-token,anchor, or arbitrary shell command matching policy - MCP calls: value-moving and authority-changing verbs (
send*,stake*,delegate*,mint*,bridge*,lend*,borrow*) across any connected MCP - Secret reads:
.envand other env files, keypair files (by name and by content), private keys, credential paths — on Read, Grep and MCP path arguments, matched case-insensitively and through symlinks (see Secrets and.env). A varlock.env.schemastays readable but is denied when it holds key material (a keypair, a private-key block, an API token, or a literal value on a secret-named key;varlock(...)calls,$VARreferences and placeholders don't count), is over 64 KiB, or is not a regular.env.schemafile. safe-ai-skill never runsvarlock. - Secret uploads: a
curl/wget/scpthat sends a key file or project env file by path is denied at every tier, and an inlinecurl/wgetPOST body to a non-localhost host asks, except a JSON-RPC body to a known Solana RPC host (the publicapi.*.solana.comendpoints, Helius, QuickNode, Alchemy's Solana hosts, Triton, Ankr, Syndica, extrnode; docs/tiers.md has the list), which has no opinion below High. The host-list exfil tripwire (request-bin, tunnel and paste hosts) is planned (#19) and does not ship in 1.1.0; use a sandboxdeniedDomainslist for that - Interactive gate breakage: replaces broken
read -rprompts (no TTY in hook context) with a functioningaskdecision
Supply-chain (SessionStart verifier)
- Skill packs: sweeps the project's
.claude/skills;~/.claude/skillsonly when a project's.safe-ai-skill/policy.yamladds it tosupply_chain.verify_skills_dirs(the only dir outside the project a project may add), plus packs installed there withinstall --from - Pack state: each skill pack and
ext/submodule is graded (clean, warned, unreviewed, blocked) and pinned by content (ext submodules also by git SHA). A combination rule, drift from the pin, or a mismatch with a committed.safe-ai-skill/packs.lockblocks the pack: it is denied in place (Skill tool,/name, reads and Bash access inside it) at Relaxed and above, and only reported at Off. A HIGH heuristic (unreviewed) is denied only at High. Nothing is moved unlesssupply_chain.on_block: move.safe-ai-skill verify approve <name>(orverify trust <dir>, which follows the content to other worktrees) clears drift, unreviewed and reviewed lock mismatches after you confirm it; nothing clears a combination rule. - Catalog gating: classifies
skill-registry.jsonentries by risk class (wallet_signing,key_custody,installer_script,standard); blocks installation of denied classes curl | bashdetection: flags installer-script patterns before they execute- MCP version tracking: flags
@latestentries as informational — does not silently rewrite them - CVE lookup:
addqueries osv.dev for an exact, non-local npm source such asadd mcp's resolvedpkg@version(no API key required). The session sweep makes no network calls - Pack scanner: rule-based detectors with code/doc/meta context (docs cap at WARN, a single code signal is HIGH, only combinations BLOCK); see docs/scanner-coverage.md
Hard guards (never relaxable)
set_authority · account_close · irreversible on-chain actions · secret_read
No tier, grant, flag or repo policy relaxes them. Authority changes and account closes ask you (they deny at High on mainnet or an unidentified cluster), and secret reads are denied. Irreversible actions (--final, program close (program or buffer), spl-token close-mint/close/burn, authorize --disable, IDL erase/close, …) deny on mainnet or any cluster not positively identified as localnet or devnet, ask on an identified localnet or devnet, and no grant, tier, repo setting or env var changes either answer. A non-final deploy, including one to mainnet, is a counterparty action: it asks on mainnet or an unidentified cluster (deny at High), and a confirmed deploy grant can allow it below High.
Firewall tiers
The plugin takes a tier as an input: off < relaxed < medium < high, default relaxed. (The strict, paranoid and autopilot profiles of 1.0 map onto these; see Upgrading to 1.1.0.) off is not SAFE_AI_SKILL_DISABLE=1: every deny row below holds at Off, and nothing but a user-scope tier can lower the tier. The table is the behaviour of each gate at each tier; n/o means no opinion (empty stdout, the call goes on to Claude Code's own permission flow).
| Gate | Off | Relaxed | Medium | High |
|---|---|---|---|---|
| Credential stores and key files, read by path or by command | deny | deny | deny | deny |
| Key material sent to a network command | deny | deny | deny | deny |
Project env file, raw read (default env_read: deny) |
deny | deny | deny | deny |
… user-scope env_read: tier, sandbox On (opt-in; not in 1.1.0 release builds) |
n/o | n/o | deny | deny |
… user-scope env_read: ask, sandbox On (opt-in; not in 1.1.0 release builds) |
ask | ask | ask | deny |
| varlock commands that print values | = raw read | = raw read | = raw read | = raw read |
.env.schema and templates (content guard applies) |
n/o | n/o | n/o | n/o |
Write/Edit/MultiEdit to a project env file; agent env set|unset |
n/o | n/o | deny | deny |
env keys |
n/o | n/o | n/o | n/o |
| Pattern redaction (keys, seeds, PEM, vendor tokens) in successful tool results | on | on | on | on |
| Value scrub of project env values in successful tool results, except an opted-in raw read | on | on | on | on |
Irreversible on-chain action (--final, program close (program or buffer), spl-token close-mint/close/burn, authorize --disable, …) on mainnet, an unknown cluster or testnet |
deny | deny | deny | deny |
| … on a positively identified localnet or devnet (never relaxed by a grant) | ask | ask | ask | ask |
Default-wallet destruction (keygen --force, file overwrite/delete) |
deny | deny | deny | deny |
| Value-moving or authority-changing action on mainnet or an unknown cluster | ask | ask | ask | deny |
| … with a live confirmed grant covering it (grantable scopes only) | allow | allow | allow | deny |
| … on an identified test cluster: deploy/upgrade | n/o | n/o | n/o | n/o |
| … transfer within spend caps | allow | allow | allow | n/o |
| … authority change | ask | ask | ask | ask |
| Solana command the parser cannot read (opaque or dynamic) | ask | ask | ask | deny |
MCP value-moving or signing tool (gate-mcp) |
ask | ask | ask | deny |
| Project env file as a network command's input (a raw read) | deny | deny | deny | deny |
Inline curl/wget POST body (no file upload) to a known Solana RPC host |
n/o | n/o | n/o | ask |
Inline curl/wget POST body to any other non-localhost host, or a file-body upload (-d @file, -T, -F k=@file) |
ask | ask | ask | ask |
| Supply chain: combination rule, drift, lock mismatch, unscanned pack | report | deny in place | deny in place | deny in place |
| Supply chain: HIGH heuristic (unreviewed pack) | report | report | report | deny in place |
| Tamper tripwire, authenticated state, prompt guard | on | on | on | on |
What each row covers is explained in docs/tiers.md. At High the engine never emits allow (a grant or a within-cap transfer falls back to Claude Code's own flow), no tier emits defer, and headless runs turn every ask into a deny. A command line chaining two or more gated on-chain actions, or holding any hard-guard ask, is never covered by a grant, and a Bash command over 256 KiB (or with more than 2048 nested expansions) asks without being parsed (deny at High and headless). The raw env-read opt-in rows describe a build with the env-read-opt-in feature; 1.1.0 release builds ship without it, so raw reads stay denied at every tier whatever env_read says. The host-list exfil tripwire is planned (#19) and has no row.
Choose a tier with safe-ai-skill tier (Usage tiers lists the command). Only a user-scope tier, written by that command and confirmed by you in the prompt, can go below the default. A tier named by solana-ai-kit's .claude/security.json, by a repo's .safe-ai-skill/policy.yaml or by SAFE_AI_SKILL_TIER can only raise it. Without Claude Code's sandbox declared on, the label reads relaxed (hooks only). A clean session at the default tier prints nothing at SessionStart; the tier line appears when the tier isn't the plain default or there is anything else to report, and safe-ai-skill tier and status always print it. Selection, the confirmation flow, migration from profiles and the headless rule are in docs/tiers.md.
Secrets and .env
Raw reads of env files (.env, .env.*, *.env, .envrc, .dev.vars, ~/.config/solana/cli/config.yml) are denied at every tier, and the deny message gives the commands to use instead. Key material and home credential stores are denied at every tier, Off included, through Read, Grep, MCP path arguments and any Bash command that names them (metadata-only commands such as ls excepted): Solana keypairs and the Solana config tree (except cli/config.yml), ~/.ssh/** (except config, known_hosts and *.pub), ~/.gnupg, ~/.aws, ~/.kube/config, the ~/.claude/.credentials.json token file, registry and git-host tokens (~/.netrc, ~/.git-credentials, ~/.npmrc, gh/hosts.yml, …), cloud and infrastructure CLI tokens (~/.config/gcloud, ~/.azure, ~/.config/doctl, ~/.terraform.d/credentials.tfrc.json, ~/.config/helm, ~/.vault-token), keychains and password-manager stores, browser profiles and wallet-extension data. Naming an SSH key as the identity of ssh, scp or sftp (-i <key>, -o IdentityFile=<key>) or to ssh-keygen -y -f is allowed, since the key is used and not printed; any other use of a private key path is denied. Commands that print a credential (gh auth token, git credential fill, security find-generic-password -w, aws configure get *secret*, npm config get *_authToken, docker-credential-* get, pass show, op read, bw get, …) are denied the same way. .env.example|.sample|.template|.dist and .env.schema stay readable. A Grep over a directory gets env, key and credential globs excluded from its results, including one rooted at or above $HOME (this also hides .env.example and .env.schema from directory searches; read them directly), and a Grep rooted inside a credential store is denied; Glob only lists names and is not gated. Tool output that still contains a value from a project env file, and any key, seed, PEM or vendor-token pattern, is shown redacted («redacted:KEY») at every tier. Reads of Claude Code transcripts (~/.claude/projects/**) are denied, which also stops the agent reading its own auto-memory files with its tools; open those yourself.
Three options, pick the one that fits; each keeps working without the next:
Plain
.env+safe-ai-skill env. The agent works with keys, never values:safe-ai-skill env keys .env # keys with masked values printf %s '<value>' | safe-ai-skill env set .env KEY # value on stdin, never in argv safe-ai-skill env unset .env KEYAdd a
.env.schema(env-spec, the format varlock uses). It lists keys, types and@sensitive/@requireddecorators and is committed, so the agent can read it directly.safe-ai-skill env init-schemawrites one from your.env(keys only, every key sensitive by default), andenv keysthen annotates each key and lists declared keys that are unset. safe-ai-skill parses the schema itself: it never runs varlock, because loading a schema with varlock executes$(…)/exec()values and plugins.varlock with a secret provider or
varlock("local:…")encryption. Run tools withvarlock run -- <cmd>and inspect withvarlock load --agent. Commands that print resolved values (varlock printenv,reveal,load --format json|env|shellwithout--agent, …) are treated likecat .env.
Writes follow the tier: Write, Edit and MultiEdit to a project env file, and an agent-run env set|unset, are allowed at Off and Relaxed and denied at Medium and High. A write through Bash (sed -i, perl -pi, python3 -c) is not covered at any tier.
The raw read opt-in ships disabled in 1.1.0. safe-ai-skill tier --env-read ask|tier is accepted and stored, but release builds print that it is inactive and keep denying raw reads; SessionStart says the stored setting is inactive. It stays off until the planned exfil tripwire (#19) can deny a project env file sent to the network. In a build with the env-read-opt-in cargo feature, ask prompts on every read below High and tier leaves reads alone at Off and Relaxed and denies them at Medium and High; the setting is user-scope only (loosening needs your confirmation in the prompt, and a repo policy, a kit file or an env var can only tighten it), it works only while Claude Code's sandbox is declared on (sandbox.enabled in your settings, on macOS or Linux with bwrap), and the response to an opted-in raw read isn't value-scrubbed, so an Edit of one line leaves the other values intact.
The hooks stop a careless or prompt-injected model; for a hard boundary enable Claude Code's sandbox with sandbox.filesystem.denyRead — safe-ai-skill doctor checks your settings and prints the globs to add.
Threat model
safe-ai-skill protects against a careless model and prompt-injected instructions. It is not a sandbox. Code that runs unsandboxed as your user can rewrite any user-space state, including the plugin's grants, pins and settings, so the Claude Code sandbox is the hard boundary. A tamper tripwire denies agent tool calls that would loosen the firewall from the inside, but encoded commands, variable indirection and scripts written now and run later can get past it. What this plugin cannot protect on its own lists the known holes. Run allow, tier <lower> and verify approve yourself in a terminal.
If the engine can't run on a platform (no prebuilt binary, wrong executable format, exit 126/127), the hooks fail open with a one-line notice and SessionStart reports the firewall INACTIVE with the reason (that SessionStart message is the only place you see it: Claude Code keeps an exit-0 hook's stderr in its debug log), but solana, spl-token and anchor commands still get ask. SAFE_AI_SKILL_STRICT=1 blocks instead. SAFE_AI_SKILL_DISABLE=1 switches the hooks off, and is ignored (the hooks keep running) in strict mode, or when the project's committed .claude/settings.json mentions it or can't be read. See SECURITY.md for details.
Upgrading to 1.1.0 (read before upgrading)
- Relaxations need a code only you see. Unexpired 1.0 grants become pending requests at your first 1.1.0 session; confirm each as described under Usage tiers, or re-create it. The 1.0 spend ledger counts as spent for the rest of that UTC day. Lowering the tier needs the same confirmation, and nothing can be confirmed in headless
claude -p. - Profiles became tiers.
strict(the 1.0 default) becomesrelaxed,paranoidbecomeshigh, andautopilotbecomesrelaxed(the notice namessafe-ai-skill allow --scope transferfor what autopilot's higher cap did). A tier belowrelaxedis never migrated: a 1.0offis set aside with a hint to runsafe-ai-skill tier off(confirmed) or setSAFE_AI_SKILL_DISABLE=1. Your first session prints one line about it. Raw.envreads stay denied for everyone after the upgrade. - What
relaxednow denies. Irreversible actions (--final,program close(program or buffer),spl-token close-mint|close|burn,authorize --disable, IDL erase/close,close-vote-account) now deny on mainnet, testnet and any cluster that isn't positively identified as localnet or devnet (an RPC host without anhttp(s)://scheme identifies nothing), and still ask on an identified localnet or devnet. Credential stores beyond~/.ssh/id_*(~/.aws,~/.gnupg,~/.netrc,~/.npmrc, cloud CLI tokens, keychains, browser and wallet-extension data, and more) are read-denied at every tier, by path and by any Bash command naming them. An inlinecurl/wgetPOST body to a non-localhost host still asks at every tier (a deny underclaude -p), except an inline body to a known Solana RPC host, which has no opinion below High. gate-bashruns for every Bash call and fails closed. It used to run only for commands starting withsolana,spl-tokenoranchor, so wrapped forms (env FOO=1 …,bash -c '…',timeout 5 …) andsolana-keygennever reached it. A fast path keeps ordinary commands cheap. If the engine crashes, Bash calls are blocked until it is fixed; if it cannot run on your platform at all, only commands naming the Solana CLIs ask.- Headless asks become denies. Under
claude -por CI (CI,GITHUB_ACTIONS, no/dev/tty, …), everyaskis adeny; only a grant you confirmed interactively beforehand passes.CONFIRM_MAINNET-style env variables are ignored, and no setting turns headless off. - Skill-pack drift is a BLOCK. A pinned pack whose content changes is denied (the Skill tool,
/name, reads and Bash access inside it) until you runsafe-ai-skill verify approve <name>in a terminal and confirm it. A pack the SessionStart sweep could not fully scan in its time budget is denied the same way until a later sweep scans it;safe-ai-skill verify check <dir>scans it without a budget. - The agent can't read Claude Code transcripts (
~/.claude/projects) or the plugin's state key, through Read, Grep or Bash.safe-ai-skill doctorrecommends matching sandboxdenyReadentries. - The Skill hook fails closed: a broken engine blocks the Skill tool until it is fixed.
- The CLI needs the plugin installed: from a terminal it uses
$CLAUDE_PLUGIN_DATA, else the plugin's data dir, and exits 1 asking forCLAUDE_PLUGIN_DATAwhen there is none.
Runtime threats — agentic workflows drive CLI commands and value-moving MCP calls with no gating. Interactive read -r prompts silently fail in hook context (no TTY). safe-ai-skill intercepts every such action before execution.
Supply-chain threats — skill hubs ship external submodules from third-party sources. Each submodule is its own supply-chain unit and can contain telemetry preambles, curl | bash installer scripts, or unpinned MCP packages. Opt-in skill catalogs include high-risk entry classes that can execute privileged code. safe-ai-skill catches all of this at session start, before any skill or MCP is active.
Usage tiers
These are levels of use (install and go, gate installs, verified hub install), not the firewall tiers.
Tier 0 — install and go
Install once. Every session is protected automatically. No configuration required.
Tier 1 — gate installs before they run
safe-ai-skill registry list # list catalog entries with risk classification
safe-ai-skill registry verify # audit installed entries against registry
safe-ai-skill add skill <name|url> # catalog entry or any GitHub URL, verified before install
safe-ai-skill add mcp <id|pkg|url> # any MCP; verified before writing .mcp.json
safe-ai-skill verify # on-demand audit of all installed skills and MCPs
safe-ai-skill verify check <dir> # grade one skill directory as the sweep would at your tier, listing every finding (INFO included)
safe-ai-skill verify approve <name> # re-pin a drifted pack after you reviewed it
safe-ai-skill status # pins, tier, live grants, spend, recent decisions
safe-ai-skill tier # show the tier, where it came from, and the sandbox state
safe-ai-skill tier <name> [--project] [--env-read deny|ask|tier]
# off | relaxed (default) | medium | high; lowering needs confirmation
safe-ai-skill tier --clear [--project]
safe-ai-skill allow --scope transfer --for 30m --max-tx 1 --budget 5 [--to <addr>]
# request a time-boxed grant (needs confirmation)
safe-ai-skill revoke # drop every grant at once
safe-ai-skill session # show active session state
verify approve, install and add change what the firewall pins, so the agent can't run them: the tamper tripwire denies them, and bare verify / verify session too, which only the SessionStart hook runs. Run them in your own terminal; verify check <dir> stays open to the agent because it pins nothing. From a terminal the CLI uses the same state as the hooks: $CLAUDE_PLUGIN_DATA if set, otherwise the plugin's dir under ~/.claude/plugins/data/ (or $CLAUDE_CONFIG_DIR/plugins/data/, which replaces ~/.claude), safe-ai-skill-safe-ai-skill first, then safe-ai-skill-stbr, then the only safe-ai-skill-* dir. If none of these resolves it exits 1 and asks you to set CLAUDE_PLUGIN_DATA; when the plugin is installed but no session has run yet (Claude Code creates the data dir on the first hook run), it says to start a session once. status prints the dir it used and lists pending requests (id, kind, when created and when they lapse, how to confirm; never the code).
Relaxations need your confirmation in the prompt. allow and a tier lowering (or an --env-read loosening) change
nothing on their own (the agent may run them, because they only create a request it cannot confirm): they record a request (request r3fa91c) and print no code. Claude Code
then shows you a confirmation code in a hook message (right after the command, or at your
next prompt or session start) that is never put in the agent's context. The request takes
effect only when your next prompt is exactly safe-ai-skill confirm <code>, on its own
(within 10 minutes, once); Claude Code then shows what you approved. A confirm line inside
pasted text is refused, and three wrong codes cancel the session's pending requests. A request belongs to
one Claude Code session (the one whose agent ran the command, or, for a request made in your terminal, the
first interactive session that shows its code): its code is shown and accepted only there (resuming keeps
the session; /clear starts a new one). Nothing is announced or confirmed in a headless session (CI or a
headless switch), in a non-interactive claude -p, or in a Claude Code session started from inside
another one (an agent's nested claude): a code typed there changes nothing, you are told why, and the
request stays pending. The same holds wherever the engine can't read the process table to tell: on
native Windows, confirmations (lowering the tier, loosening env_read, grants, verify trust/approve)
are unavailable; raising the tier and everything else work, and WSL works for those actions. Whether
desktop or IDE hosts run Claude Code in print mode (which can't confirm) is unverified; a terminal
session on macOS, Linux or WSL works. Raising the tier (or tightening --env-read) and revoke apply at
once. Grant scopes are transfer, swap, deploy, authority and destructive; anything
else is an error.
Tiers. relaxed is the default; see Firewall tiers for what each tier
does. safe-ai-skill tier writes the user-scope tier (--project scopes it to the current
repository). Raising it applies at once; lowering it, or loosening --env-read, waits for your
confirmation. SAFE_AI_SKILL_TIER (alias SAFE_AI_SKILL_PROFILE), a repo's.safe-ai-skill/policy.yaml tier and solana-ai-kit's .claude/security.json can only
raise the tier, so a repo or an env block can't loosen your firewall. mode is a deprecated
alias for tier. The tier off keeps every deny row; to turn the hooks off entirely, setSAFE_AI_SKILL_DISABLE=1.
State is authenticated. Grants, the tier, the daily spend ledger and session metadata are
signed with a per-install key in the plugin data directory, and each file carries a
generation, so a hand-edited file or an older copy put back is ignored, a deleted or edited
spend ledger counts as today's budget spent, and a deleted, hand-edited or replayedtier.json (or one whose key is gone) makes the tier High, with a SessionStart and status
note naming the cause, until you run safe-ai-skill tier … again. This is tamper-evident, not tamper-proof: code
running as you outside the Claude Code sandbox can read the key, run the hook commands itself
or restore a snapshot of the whole directory. The sandbox is the hard boundary. At the first
1.1.0 session, unexpired 1.0 grants become pending requests you can
confirm with the codes shown then; the 1.0 spend ledger becomes today's exhausted budget. A session
start with anything to report prints tier: <t> (<source>); sandbox: <state> and lists what is
currently loosened: a tier below the default, live grants, ignored repo policy and ignored tier
hints, state that failed authentication, and a Claude Code sandbox that is off. A clean session at
the default tier prints nothing.
Each add command runs the intrinsic verification pipeline on the actual fetched content before executing the underlying install. registry list shows the full catalog with risk classes flagged. High-risk entries (wallet_signing, key_custody, installer_script) require explicit approval.
Tier 2 — verified hub install
safe-ai-skill install # verify the project's .claude/skills in place (scan, pin, no copy)
safe-ai-skill install --from <dir|tarball> # verified install from a local skill directory or tarball
safe-ai-skill install --home ~ # install into <dir>/.claude/skills (default: ~)
install never downloads anything: --from takes a local directory or tarball, so fetch a hub yourself (for example git clone) and point --from at it. It grades every skill the way the SessionStart sweep would at your tier, neutralizes telemetry preambles and pins each pack it takes. A clean skill is taken. A skill with warnings, or with an unreviewed HIGH finding below tier high, is held unless you pass --yes (it is pinned as unreviewed, so the sweep keeps reporting it). A skill the tier denies (a combination BLOCK, a packs.lock mismatch, an unreviewed HIGH at tier high, a pack too large to scan) and a combination BLOCK at any tier, Off included, are held unless you pass --force; the gates still deny such a pack in place once it is swept. add follows the same rules for a local directory. With no --from it verifies the current project's .claude/skills in place, and rewrites a SKILL.md there to remove a telemetry preamble it finds, unless the project's .safe-ai-skill/packs.lock pins that pack (the output lists it under not_neutralized). It does not auto-widen ~/.claude/settings.json permissions.
Registry-free verification model
Trust is based on the artifact itself, not a hash allowlist to maintain:
- Rule-based scan — every file of a pack is classified as code, documentation or pack metadata, and detectors run in that context (strings and comments are data, interpolations are code). Findings in docs cap at WARN, a single code signal is HIGH (the pack is unreviewed: reported, not blocked), and only combinations of signals (an exfiltrating
!`cmd`, padding plus a sink,evalplus a decoder, a symlink leaving the pack, …) BLOCK. - Per-pack pinning — each skill pack and
ext/submodule is pinned on first sight by content (ext submodules also by commit SHA). Later drift blocks the pack, in place, until you approve it withsafe-ai-skill verify approve <name>and confirm the code safe-ai-skill shows you. - Catalog gating — entries are classified by risk class. High-risk classes require explicit policy approval; safe-ai-skill blocks installation of denied classes without prompting.
- Provenance pinning — GitHub URLs resolve to immutable commit SHAs (moving refs are rejected). npm packages pin to
[email protected]plusdist.shasum.@latestentries are flagged informational. - CVE lookup —
addqueries osv.dev for an exact, non-local npm source (for exampleadd mcp's resolvedpkg@version). No API key required. This is the only network step; the session sweep and gates make no network calls. - Local state — per-pack state (
state.json, authenticated) records the pin, grade and findings; a project may commit.safe-ai-skill/packs.lockto pin pack contents for everyone (a mismatch blocks, a match can't clear a block).
At SessionStart: block on a combination rule or drift, report a HIGH as unreviewed, warn on documentation findings, pin clean. add and install also block on a known CVE.
Integration model
Install any skill hub (e.g. solanabr/solana-ai-kit) alongside safe-ai-skill as a plugin. No settings.json merge is needed — Claude Code merges plugin hooks with project hooks automatically. safe-ai-skill's deny decisions survive enableAllProjectMcpServers: true and bypassPermissions.
Using with solana-ai-kit
The kit's stbr marketplace installs this plugin; don't add this marketplace too (only one copy's hooks register). The kit pins a specific plugin commit (1.0.0, 2997280e, as of kit 2.4.0), so a release here reaches kit users when the kit moves the pin.
- The kit owns: the OS sandbox, permission rules, tier selection and regeneration (
/firewall,.claude/security.json), and the git, publish, fetch-and-execute,ghand MCP tool-name gates. - This plugin owns: the Solana-semantic gates (cluster, amounts, irreversible and authority actions, mainnet, grants), secrets for plugin-only users (key material and home stores,
.envthrough theenvCLI, varlock and redaction) and supply chain (scan, pins, Skill and slash deny, no moves). A best-effort exfil tripwire is planned (#19); until then the kit's sandboxdeniedDomainscovers known exfil hosts. It takes the kit's tier as an input:.claude/security.jsoncan raise the plugin's tier, never lower it. - Shared: versioned list files in
policy/shared/(credential globs, exfil hosts, on-chain verbs), which the kit vendors and pins, and a conformance corpus both parsers run. - Duplicated hard denies are kept as defence in depth (default-wallet
solana-keygen --force, irreversible actions on mainnet); two denies never conflict.
The proposal is in ai-kit#264 (firewall scope) and ai-kit#265 (docs); the proposed kit-side changes are drafted in docs/drafts/aikit-firewall-split.md. Until the kit change lands, /firewall does not run safe-ai-skill tier; run safe-ai-skill tier <t> --project yourself. Today both hook sets run on every Bash call.
Codex CLI (experimental): safe-ai-skill adapt codex <gate> runs the Bash, file-edit and MCP gates as Codex hooks, and turns every ask into a deny because Codex hooks cannot ask. Setup and limits are in docs/adapters.md. opencode is not covered yet (#23).
safe-ai-skill is standalone — it can be installed independently of any particular skill hub. It adds a security and control layer over whatever skills, MCPs, and agents you already have.
Security
See SECURITY.md for the full threat model, guarantees, out-of-scope items, and vulnerability reporting.
License
MIT
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found