AI skills and instruction bundles are a new software supply-chain and identity attack surface. Developers and agents often treat these files as trusted setup or execution guidance, even though malicious or prompt-injected skills can steal secrets, exfiltrate data, pull remote payloads, and deliver malware through shell commands or other risky behavior.
It discovers AI skills and instruction files, analyzes them for any hidden and malicious behavior, and produces human-readable and machine-readable results for local review or CI gating.
skill-scanner combines:
- LLM analysis
- VirusTotal analysis
Use it to:
- review that trust surface before execution
- surface malicious instructions before a developer runs them
- validate local workflows or CI with machine-readable security results
- Malicious skills can steal secrets, exfiltrate data, pull remote payloads, and deliver malware.
- From magic to malware: How OpenClaw's agent skills become an attack surface
- Python 3.11+
uv- LiteLLM-compatible model configuration and/or VirusTotal API key (at least one analyzer)
uv pip install skill-scannerBase install includes LLM analysis and VirusTotal support.
uv sync --group devRun with:
uv run skill-scanner --helpAlias:
uv run skillscan --helpBy default, discover and scan detect common AI skill, instruction, prompt, agent, rule, and command artifacts such as SKILL.md, AGENTS.md, CLAUDE.md, GEMINI.md, copilot-instructions.md, *.instructions.md, *.prompt.md, *.agent.md, .claude/commands/*.md, .mdc, and .cursorrules. It also inspects installed VS Code extension package.json manifests to find contributed chat skills and then discovers the referenced SKILL.md files.
Default discovered locations follow the documented conventions for:
- Codex:
AGENTS.md,.agents/skills/, and.codex/skills/ - Claude Code:
CLAUDE.md,.claude/skills/,.claude/agents/,.claude/commands/, and Claude marketplace skill directories - GitHub Copilot:
.github/copilot-instructions.md,.github/instructions/,.github/skills/, and repo-local agent files - VS Code AI customization:
AGENTS.md,.github/prompts/,.github/agents/, and prompt/instruction files used by editor-integrated chat flows - VS Code extensions: installed extension manifests are inspected for contributed chat skills, and referenced
SKILL.mdfiles are discovered as extension-scope targets - Cursor:
.cursor/rules/(.mdc),.cursorrules, and.cursor/skills/ - Windsurf:
.windsurf/skills/and~/.codeium/windsurf/skills/ - Gemini CLI:
GEMINI.md,.gemini/skills/,.gemini/agents/, and.gemini/extensions/ - Cline:
.cline/skills/and.clinerules/ - OpenCode:
.opencode/skills/and.opencode/agents/
The scanner also supports repo, user, system, and extension variants of these locations where they are implemented in the tool or documented by the vendor.
Use --path to target a specific file or folder.
--path discovery is deterministic and only emits files that match known discovery roots/patterns
(plus a direct SKILL.md at the provided path root). It does not treat arbitrary *.md files as targets.
Default discover behavior:
discoverattempts all scopes (repo,user,system,extension).reposcope is only active when your current directory is inside a git repository.- Filesystem traversal errors are non-fatal; discovery returns partial results. Use
--verboseto inspect warnings.
# See targets
uv run skill-scanner discover --format json
# Discover only user scope
uv run skill-scanner discover --scope user
# Show detailed discovery warnings
uv run skill-scanner discover --verbose
# Verify key/model configuration
uv run skill-scanner doctor
# Run live API checks (fails non-zero if checks fail)
uv run skill-scanner doctor --check
# Run a scan (with whichever analyzers are configured)
uv run skill-scanner scan --format summaryscan requires at least one analyzer enabled.
- There is no built-in model default. Set
SKILLSCAN_MODELexplicitly or pass--model. - If
SKILLSCAN_MODELplus eitherSKILLSCAN_API_KEYorSKILLSCAN_BASE_URLis available, LLM analysis runs. - If only
VT_API_KEYis available, VT runs and LLM analysis is disabled. - If both LLM config and
VT_API_KEYare available, both analyzers run and VT context is passed into the LLM analysis. - You can disable either analyzer with
--no-aior--no-vt. - LLM analysis requires an explicit
SKILLSCAN_MODELor--modelvalue. - When
--fail-onis set, analyzer failures surfaced in notes also cause a non-zero exit to avoid false-clean scans.
Use doctor --check to verify model/API/base URL connectivity.
Use models.litellm.ai to choose a supported LiteLLM model string.
Supported configuration paths:
- Environment variables:
export SKILLSCAN_MODEL=openai/gpt-5.4
export SKILLSCAN_API_KEY=your-llm-key
export VT_API_KEY=your-vt-key
uv run skill-scanner scan --format summary- Local or user config file:
- project-local:
./skill-scanner.toml - user-level:
~/.config/skill-scanner/config.toml
model = "openai/gpt-5.4"
api_key = "your-llm-key"
vt_api_key = "your-vt-key"Local model config example:
model = "ollama/llama3.1"
base_url = "http://localhost:11434"- CLI overrides for a single run:
uv run skill-scanner scan \
--model openai/gpt-5.4 \
--api-key "$SKILLSCAN_API_KEY" \
--format summaryHosted model env example:
SKILLSCAN_MODEL=openai/gpt-5.4
SKILLSCAN_API_KEY=op://Developer/LLM/api_key
VT_API_KEY=op://Developer/VirusTotal/api_keyLocal model example:
SKILLSCAN_MODEL=ollama/llama3.1
SKILLSCAN_BASE_URL=http://localhost:11434Recommended approach: keep secrets out of skill-scanner.toml and store them as 1Password secret references in .env.
Example .env:
SKILLSCAN_MODEL=openai/gpt-5.4
SKILLSCAN_API_KEY=op://Developer/LLM/api_key
VT_API_KEY=op://Developer/VirusTotal/api_keyRun the scanner through 1Password CLI so those references are resolved at runtime:
op run --env-file=.env -- uv run skill-scanner scan --format summaryTo verify configuration before scanning:
op run --env-file=.env -- uv run skill-scanner doctor --checkIf you prefer shell exports instead of an env file:
export SKILLSCAN_MODEL=openai/gpt-5.4
export SKILLSCAN_API_KEY="$(op read 'op://Developer/LLM/api_key')"
export VT_API_KEY="$(op read 'op://Developer/VirusTotal/api_key')"
uv run skill-scanner scan --format summaryImportant:
skill-scannerdoes not auto-load.envfiles on its own.op://...references are resolved when you useop runorop read.- If you want to keep secrets in a config template, render it first with
op inject; do not commit the rendered file.
Security best practice:
- Prefer a 1Password Service Account scoped to only the vault/items required for scanning (least privilege).
References:
- https://developer.1password.com/docs/cli/secret-references/
- https://developer.1password.com/docs/cli/reference/commands/run/
- https://developer.1password.com/docs/service-accounts/
scan --format supports:
table(default)summaryjsonsarif
You can write output to a file with --output <path>.
For json, sarif, and summary, the written file matches the selected format.
For table, the terminal still shows the Rich table and --output writes JSON.
Interactive table and summary scans show a live Rich progress bar while targets are being analyzed.
Risk output is severity-classified, not numerically scored.
The final risk_level is the strongest surviving signal from llm and vt.
Structured output exposes llm_findings, vt_findings, llm_risk_level, vt_risk_level, risk_level, vt_report, and notes.
# List providers
uv run skill-scanner providers
# Scan one path only
uv run skill-scanner scan --path ./some/skill/folder --format summary
# Increase scan concurrency (default: 8)
uv run skill-scanner scan --jobs 16 --format summary
# Enable verbose logs for troubleshooting
uv run skill-scanner scan --verbose --format summary
# List discovered targets without running analyzers
uv run skill-scanner scan --list-targets
# Discover targets from user scope only
uv run skill-scanner discover --scope user --format table
# Discover with detailed traversal diagnostics
uv run skill-scanner discover --verbose --format table
# Scan only selected discovered targets (repeat --target)
uv run skill-scanner scan --target /absolute/path/to/SKILL.md --target /absolute/path/to/AGENTS.md --format summary
# Filter to medium+
uv run skill-scanner scan --min-severity medium --format summary
# Non-zero exit if high+ findings exist
uv run skill-scanner scan --fail-on high --format summary
# Verbose doctor checks
uv run skill-scanner doctor --check --verbose--list-targets can be used without API keys because it only runs discovery and exits.
# macOS/Windows: default discover should complete without crashing
uv run skill-scanner discover --format table
# Scoped check: verify user skill paths only
uv run skill-scanner discover --scope user --format tableWindows known-path sanity check:
- create
%USERPROFILE%\\.clinerules\\skills\\demo\\SKILL.md - run
uv run skill-scanner discover --platform cline --scope user --format table - confirm the demo skill appears in output
0: scan completed and fail threshold not hit1:--fail-onthreshold matched, or an analyzer failure was recorded while--fail-onwas set2: no analyzers enabled (for example missing keys combined with flags), or--targetdid not match any discovered target
doctor --check exit behavior:
0: all executed checks passed1: one or more checks failed
scan now surfaces per-target notes in table and summary output, including:
- analyzer failures (LLM or VirusTotal)
- VirusTotal timeout fallback when the latest available file verdict is used
- payload truncation when files are skipped due to the 400k-character LLM payload limit
- unreadable files excluded from payload construction
See CONTRIBUTING.md for setup, testing, and PR guidelines.
- Improve system prompt hardening to uncover more threat patterns
- Support multiple providers via LiteLLM model strings
- Baseline / suppression / false-positive management
- Ollama / local LLM provider support via LiteLLM
base_url - Configurable risk classification policy
- GitHub Actions template for automated scans
- Improve fixtures to include realistic malicious skills
Use uv version so version updates stay command-driven and lock state remains consistent.
# Patch bump (e.g., 0.1.2 -> 0.1.3)
uv version --bump patch
# Or set an explicit version
uv version 0.3.0Notes:
pyproject.tomlis the canonical version source.uv.lockis generated by uv and should not be edited manually.

