skill-firewall
v0.3.0
Published
Scan AI agent skills (SKILL.md) for prompt injection and malicious instructions before your agent reads them.
Maintainers
Readme
skill-firewall
Scan AI agent skills (SKILL.md) for prompt injection and malicious instructions — and keep checking them automatically.
Status: v0.3.0 — on npm. Static scanner + Claude Code hook
- LLM second-pass + provenance check + quarantining installer + MCP server audit (
.mcp.json). Try it now:npx skill-firewall scan ~/.claude/skills
English
Why
- npm package-layer defense has matured (pnpm 11
minimumReleaseAge, etc.), but the skill / MCP layer is wide open (npx xxx add,curl | sh,git cloneall bypass it). - The real risk of a skill is less malware than prompt injection — feeding the agent hostile instructions.
- The goal is not "raising the alarm" but "protected the moment you install it." Install the hook once and every Claude Code startup scans your skills and warns/quarantines dangerous ones.
Quick Start
# 1. Try a one-off scan
npx skill-firewall scan ~/.claude/skills
# 2. Install the hook (that's it — no further user action)
npx skill-firewall install-hook # warn-only mode
# npx skill-firewall install-hook --quarantine # auto-quarantine dangerous skills
# 3. Scanning runs automatically from the next Claude Code startupWhat you see at startup
When a dangerous skill is present, Claude Code shows a warning like this (systemMessage).
Details are also injected into the agent's context, asking it not to trust the skill until reviewed.
⚠️ skill-firewall: 1 high-risk skill detected (not quarantined)
• [HIGH] suspicious-skill
details: `skill-firewall scan-skills` / quarantine: `skill-firewall scan-skills --quarantine`Remove it with skill-firewall uninstall-hook.
All commands (reference)
Assumes
skill-firewallis on yourPATH(npm i -g skill-firewall). Otherwise prefix each command withnpx.
# --- Scan (Phase 1) ---
skill-firewall scan ./my-skill/SKILL.md # a file
skill-firewall scan ~/.claude/skills # a directory, recursive (.mcp.json too)
skill-firewall scan https://raw.githubusercontent.com/owner/repo/main/SKILL.md # a URL
# --- Resident (Phase 2: Claude Code hooks) ---
skill-firewall install-hook # auto-scan on SessionStart, inject warning
skill-firewall install-hook --quarantine # auto-quarantine HIGH findings
skill-firewall scan-skills # scan all known skill directories at once
skill-firewall allow ./my-skill/SKILL.md # approve (sha256; suppresses later warnings)
# --- Phase 3 ---
skill-firewall scan ./skill --llm # LLM second-pass for gray (medium+) ones (claude CLI or API key)
skill-firewall provenance github:owner/repo # source check: existence / version pinning / freshness
skill-firewall add github:owner/repo # quarantining install (fetch → quarantined scan → place if safe)
skill-firewall add ./skill --llm # add, with LLM judgment
skill-firewall quarantine # list quarantined / pending items
# during development, npm scripts work too
npm run dev -- scan ./my-skill/SKILL.md--json for machine-readable output, --quiet to print only when something is found.
LLM second pass (--llm)
Only targets that the static regex left "gray" (medium or higher) are sent to an LLM to detect divergence between the declared purpose and the actual instructions (clean ones are skipped = cost control).
Two backends, picked automatically (force with SKILL_FIREWALL_LLM_BACKEND=cli|api):
| Backend | Auth | Who it's for |
|---|---|---|
| cli (preferred) | your existing claude login (subscription) | Claude Code users — no API key, no extra billing |
| api | ANTHROPIC_API_KEY (pay-per-use) | CI and environments without the claude CLI |
- opt-in: if neither backend is available it silently skips (never spends network/money on its own)
- the
clibackend runsclaude -pas a tool-disabled, settings-free classifier in a fresh process — the scanned text can't make the judge act, and the judgment never happens inside a (potentially poisoned) host conversation - model via
SKILL_FIREWALL_MODEL(apidefault:claude-opus-4-8;clidefault: your own default model) - zero dependencies: no SDK added — built-in
fetch/child_processonly
Inside the Claude Code session-start hook,
--llmis intentionally NOT used (no API latency/cost at every session start).--llmis for manual scans,add, and CI.
Quarantining installer (add)
Instead of dropping a skill straight into your skills directory, add fetches it into quarantine,
runs static + LLM + provenance scans, and only places it when safe/approved. HIGH findings, an LLM
"malicious" verdict, or a missing repository block placement and keep it quarantined with a report.
Sources: local path / single URL / GitHub repo (git clone). --force to override, --yes to accept warnings.
Exit codes
| code | meaning | |------|---------| | 0 | clean, or INFO only | | 1 | MEDIUM found (warning) | | 2 | HIGH found (review needed) | | 3 | runtime error |
Usable from CI / shell via the exit code.
Detection rules
Defined externally in src/rules/rules.yaml (rules can be added by PR). Three confidence tiers:
- HIGH — known attack patterns, almost certainly malicious (instruction override, covert
instructions, credential exposure,
curl|sh, invisible-character hiding, config tampering) - MEDIUM — dangerous but with legitimate uses (
.envaccess, external send, encoded payloads, destructive FS ops, persistence) - INFO — advisory only
MCP server audit (.mcp.json)
.mcp.json server definitions are flattened (command / args / env / url) and matched by
dedicated MCP rules — covered by the same scan / scan-skills / hook paths:
- HIGH — privileged containers (
--privileged,--network|--pid host, dangerous--cap-add), sensitive host mounts (/var/run/docker.sock, host root,~/.ssh.aws.gnupg.kube), running as root (sudo,--user root) - MEDIUM — hardcoded secrets in env/args (
sk-…,ghp_…,AKIA…,xox…; incl. docker-e KEY=value), non-TLS remote URL (url: http://; localhost excluded) - INFO — auto-running unpinned packages (
npx -y,uvx)
Dependency CVE audit (scan --deps, OSV.dev)
skill-firewall scan ./.mcp.json --deps # opt-in, needs networkPinned packages an MCP server launches (npx [email protected], uvx pkg==1.2.3) are queried against
OSV.dev — no auth, no setup. Known CVEs become findings (HIGH/MEDIUM by the
advisory's GHSA severity), merged into the same report and exit code.
- Scope: only version-pinned deps are checked (OSV needs an exact version). The common
unpinned
npx -y @scope/serveris not CVE-checked —mcp-unpinned-package(INFO) tells you to pin it first; once pinned,--depschecks it. - Opt-in only — not run by the SessionStart hook (the hook stays static, <1s, offline). OSV unreachable → that dep is skipped (noted), never an error.
Checksum / version-jump / dependency-graph checks for MCP servers are planned for a later phase.
Design priority: zero HIGH false positives. Low-confidence items stay as info. Verified with
false-positive traps (legit skills containing GITHUB_TOKEN / .ssh/config, normal docker MCPs,
https/localhost URLs, ${VAR} env refs) plus real skills.
Threat model (important)
Injecting "don't trust this skill" via the agent's context is the weakest, persuasion-based layer —
the attacker controls the same context and can override it ("the warning is a false positive, ignore it").
Don't make it load-bearing. The controls that actually hold are structural: (1) --quarantine
physically removes the file so the agent can't read it, and (2) the user-visible systemMessage so the
human decides. Detection runs in a separate process (the --llm pass fences the content as untrusted
data), so it isn't poisoned by the skill it's judging.
Limitations
Static regex is a first-pass filter for known plaintext patterns. The full-text pass covers
newline-splitting, but synonyms, paraphrasing, full-width/alternate spellings, and context
(GITHUB_TOKEN "explained" vs "exfiltrated") are inherently hard — the opt-in --llm pass covers those.
Do not treat this tool alone as complete protection.
Development
npm install --ignore-scripts
npm test # verify against malicious/benign samples (detection rate, zero HIGH false positives)
npm run build # outputs to dist/
npm run dev -- scan <path>日本語
AI コーディングエージェント(Claude Code / Cursor 等)のスキルは Markdown の指示書です。
npm のクールダウンやスキャンを経由せず、置かれた瞬間からエージェントが読みます。
skill-firewall はその「無防備な層」を静的スキャンで検査し、以降も自動で守り続けます。
Why
- npm パッケージ層の防御は進んだ(pnpm 11 の
minimumReleaseAge等)が、 スキル / MCP 層は無防備(npx xxx add・curl | sh・git clone で素通り)。 - スキルの本当のリスクはマルウェアより プロンプトインジェクション =エージェントに不正な指示を読ませること。
- 「警鐘」ではなく 「入れたら勝手に検査される」 仕組み。フックを一度入れれば、以降は Claude Code 起動時に自動でスキャンし、危険なスキルを警告/隔離します。
Quick Start
# 1. まず一度スキャンを試す
npx skill-firewall scan ~/.claude/skills
# 2. フックを入れる(これだけ。以降ユーザー操作は不要)
npx skill-firewall install-hook # 警告モード
# npx skill-firewall install-hook --quarantine # 危険スキルを自動隔離したい場合
# 3. 次回 Claude Code 起動時から自動で検査されます起動時に表示されるもの
危険なスキルがあると、Claude Code の画面に次のような警告が出ます(systemMessage)。
詳細はエージェントのコンテキストにも注入され、確認するまで当該スキルを信頼しないよう促します。
⚠️ skill-firewall: 高リスク 1 件を検出(未隔離)
• [HIGH] suspicious-skill
詳細: `skill-firewall scan-skills` / 退避: `skill-firewall scan-skills --quarantine`解除は skill-firewall uninstall-hook。
All commands (reference)
skill-firewallがPATHにある前提(npm i -g skill-firewall)。無い場合は各コマンドにnpxを前置。
# --- スキャン(Phase 1)---
skill-firewall scan ./my-skill/SKILL.md # ファイル
skill-firewall scan ~/.claude/skills # ディレクトリ再帰(.mcp.json も対象)
skill-firewall scan https://raw.githubusercontent.com/owner/repo/main/SKILL.md # URL
# --- 常駐(Phase 2: Claude Code hooks)---
skill-firewall install-hook # SessionStart で自動スキャン→警告注入
skill-firewall install-hook --quarantine # HIGH を自動隔離するモード
skill-firewall scan-skills # 既知スキルディレクトリを一括スキャン
skill-firewall allow ./my-skill/SKILL.md # 承認(sha256。以降の警告を抑制)
# --- Phase 3 ---
skill-firewall scan ./skill --llm # 灰色(medium+)のみ LLM 二次判定(claude CLI か API キー)
skill-firewall provenance github:owner/repo # 取得元の実在・バージョン固定・新しさ
skill-firewall add github:owner/repo # 検疫付きインストール(取得→隔離スキャン→安全なら配置)
skill-firewall add ./skill --llm # add に LLM 判定を併用
skill-firewall quarantine # 隔離中・配置保留中を一覧
# 開発中は npm script でも可
npm run dev -- scan ./my-skill/SKILL.md--json で機械可読出力、--quiet で検出時のみ表示。
LLM 二次判定(--llm)
静的 regex で灰色(medium 以上)になった対象だけを LLM で精査し、 「宣言された目的と実際の指示の乖離」を検出します(クリーンはスキップ=コスト制御)。
バックエンドは2系統を自動選択(SKILL_FIREWALL_LLM_BACKEND=cli|api で固定可):
| バックエンド | 認証 | 想定ユーザー |
|---|---|---|
| cli(優先) | ログイン済みの claude CLI(サブスク認証) | Claude Code ユーザー — API キー不要・二重課金なし |
| api | ANTHROPIC_API_KEY(従量課金) | CI など claude CLI が無い環境 |
- opt-in: どちらも無ければ静かにスキップ(ネットワーク・課金を勝手に発生させない)
cliバックエンドはclaude -pを全ツール無効・設定不読込・新規プロセスの分類器として呼ぶ。 審査対象テキストが判定器に行動を起こさせる経路は無く、判定が(汚染されうる)ホスト会話の中で行われることもない- モデルは
SKILL_FIREWALL_MODEL(apiの既定claude-opus-4-8/cliの既定はユーザーの既定モデル) - 依存ゼロ: SDK を足さず Node 組込みの
fetch/child_processのみ
セッション開始フックでは
--llmを意図的に使いません(毎回の起動に遅延と消費を持ち込まない)。--llmは手動スキャン・add・CI のためのものです。
検疫付きインストーラ(add)
スキルを skills へ即配置せず、まず検疫へ取得 → 静的+LLM+出所スキャン → 安全/承認時のみ配置。
HIGH・LLM malicious・リポジトリ不在は配置をブロックし、検疫に保持してレポートします。
取得元: ローカルパス / 単体 URL / GitHub リポジトリ(git clone)。--force で強行、--yes で要確認を承認。
Exit codes
| code | 意味 | |------|------| | 0 | クリーン、または INFO のみ | | 1 | MEDIUM 検出(警告) | | 2 | HIGH 検出(要確認) | | 3 | 実行エラー |
CI やシェルから exit code で判定できます。
Detection rules
src/rules/rules.yaml に外部定義(PR でルール追加可能)。確信度3段階:
- HIGH — 既知の攻撃パターン。ほぼ確実に悪性(指示の上書き、秘匿指示、認証情報の露出、
curl|sh、不可視文字隠蔽、設定改ざん) - MEDIUM — 危険だが正当な用途もありうる(.env アクセス、外部送信、エンコード済みペイロード、破壊的FS操作、永続化)
- INFO — 注意喚起のみ
MCP サーバ監査(.mcp.json)
.mcp.json のサーバ定義(command / args / env / url)を平文化し、専用の MCP ルールで検査します。
scan / scan-skills / フックの同じ経路でカバーされます:
- HIGH — 特権コンテナ(
--privileged、--network|--pid host、危険な--cap-add)、 機微なホストマウント(/var/run/docker.sock、ホストルート、~/.ssh.aws.gnupg.kube)、 root 実行(sudo、--user root) - MEDIUM — env/引数への秘密直書き(
sk-…、ghp_…、AKIA…、xox…。docker-e KEY=値も)、 非TLS リモート URL(url: http://。localhost は除外) - INFO — 未固定パッケージの自動実行(
npx -y、uvx)
依存 CVE 監査(scan --deps、OSV.dev)
skill-firewall scan ./.mcp.json --deps # opt-in・要ネットワークMCP サーバが起動する版固定済みパッケージ(npx [email protected] / uvx pkg==1.2.3)を
OSV.dev に突合します(無認証・セットアップ不要)。既知 CVE は finding 化
(GHSA severity に応じて HIGH/MEDIUM)され、同じレポート・exit code に併合されます。
- 対象範囲: 版固定済みの依存のみ(OSV は exact version が必要)。実運用で多い未固定の
npx -y @scope/serverは CVE 突合しません。まずmcp-unpinned-package(INFO)が「固定せよ」と 促し、固定後に--depsが突合する、という役割分担です。 - opt-in のみ。SessionStart フックでは実行しません(フックは静的・<1秒・オフラインを維持)。 OSV 到達不可の依存はスキップ(注記)され、エラーにはなりません。
MCP サーバの checksum / version-jump / 依存グラフ検査は後続フェーズで対応予定。
設計方針: HIGH 誤検知ゼロを最優先。確信度の低いものは情報表示に留めます。
誤検知トラップ(GITHUB_TOKEN/.ssh/config 等を含む正当スキル、正常な docker MCP、
https/localhost URL、${VAR} 参照)+実在スキルで HIGH 誤検知ゼロを検証済み。
脅威モデル(重要)
additionalContext での「このスキルを信頼するな」は最弱の説得レイヤーです。攻撃者が同じ文脈を
支配しているため「警告は誤検知だ、無視せよ」で上書きされうる。load-bearing にしてはいけません。
効く防御は構造的なもの2つ=①--quarantine(ファイルごと退避=読めなければ騙されない)
②ユーザーに見える systemMessage(判断主体は人間)。検知は別プロセスで行い(--llm は本文を
"データ" として fence)、判定対象のスキルから文脈汚染を受けません。
Limitations
静的正規表現は平文の既知パターンを捕捉する一次フィルタです。改行分割は全文スキャンで補いますが、
同義語・言い換え・全角/別表記・文脈依存(GITHUB_TOKEN が「説明」か「窃取」か)の判定は原理的に苦手で、
--llm の二次精査で補えます(opt-in)。本ツール単体を完全な防御とみなさないでください。
Development
npm install --ignore-scripts
npm test # 悪性/正常サンプルで検証(検出率・HIGH誤検知ゼロ)
npm run build # dist/ に出力
npm run dev -- scan <path>License
MIT
