A security scanner for Agent Skill packages. Skill Sentinel uses multi-agent AI analysis to detect prompt injection, data exfiltration, command injection, malware, and other threats hiding in skill packages for Cursor, Claude Code, Codex, and OpenClaw.
Agent Skills extend AI coding assistants with custom instructions and scripts β but they also create a new attack surface. A single malicious skill can steal credentials, inject hidden prompts, exfiltrate code, or execute arbitrary commands. Skill Sentinel catches these threats before they reach your agent.
Cursor |
Claude Code |
Codex |
OpenClaw |
and other agents |
Specialized agents work together to analyze Skills from multiple angles β manifest inspection, file verification, cross-referencing, and threat correlation.
Automatic VirusTotal integration scans binary files (executables, archives, PDFs) for known malware before any LLM analysis begins.
Reads complete file contents without arbitrary cutoffs β catching malicious instructions hidden deep in documentation where other scanners stop looking.
Detects sophisticated attacks that span multiple files by tracking data flows and verifying that script behavior matches documented claims.
Purpose-built for prompt injection, command injection, credential theft, and other threats specific to AI coding assistants.
Scan entire directories of Skills concurrently with organized reports β audit all your Cursor, Claude Code, Codex, and OpenClaw Skills in one command.
Requires Python >= 3.10, < 3.14.
pip install skill-sentinelOr using uv (recommended):
uv pip install skill-sentinelFor development or local modifications:
# Clone the repository
git clone https://github.com/enkryptai/skill-sentinel.git
cd skill-sentinel
# Install with uv (recommended)
uv venv --python 3.13 .venv
source .venv/bin/activate
uv pip install -e .
# Or install with pip
pip install -e .# 1. Export your OpenAI API key
export OPENAI_API_KEY="sk-..."
# 2. (Optional) Export your VirusTotal API key for binary malware scanning
export VIRUSTOTAL_API_KEY="your-vt-api-key"
# 3. Scan a skill directory
skill-sentinel scan /path/to/skill/directoryAlternatively, create a .env file in the project root instead of exporting variables:
cp .env.example .env
# Edit .env with your keysskill-sentinel scan [provider] [OPTIONS]
Positional:
provider cursor / claude / codex / openclaw to auto-discover that
provider's skills, or omit to discover all.
Can also be a direct path to a skill directory.
Path flags (mutually exclusive):
--skill PATH Scan a single skill directory.
--dir PATH Scan all skill subdirectories inside a parent directory.
Options:
-o, --output PATH Single scan: output file (default: report.json).
Multi-scan: output directory (default: ./skill_sentinel_reports).
--parallel Scan multiple skills in parallel (5 concurrent).
-m, --model MODEL OpenAI model to use (default: gpt-5.4-mini).
--api-key KEY OpenAI API key (prefer OPENAI_API_KEY env var).
-V, --version Show version and exit.
# Scan a single skill directory
skill-sentinel scan --skill ./my-skill
skill-sentinel scan --skill ./my-skill -o report.json
# Scan all skills inside a parent directory
skill-sentinel scan --dir ./all-my-skills/
skill-sentinel scan --dir ./all-my-skills/ -o ./reports/
# Scan in parallel (5 concurrent)
skill-sentinel scan --dir ./all-my-skills/ --parallel
# Auto-discover and scan ALL skills from cursor, claude, codex, and openclaw paths
skill-sentinel scan
# Auto-discover only Cursor skills, in parallel
skill-sentinel scan cursor --parallel
# Scan only Claude skills
skill-sentinel scan claude
# Custom output directory for auto-discovery
skill-sentinel scan codex -o ./my-reports/
# Use a different model
skill-sentinel scan --skill ./my-skill -m gpt-4oWhen no path is given (or a provider keyword is used), the scanner searches these well-known locations for skill directories containing a SKILL.md:
| Location | Scope |
|---|---|
.cursor/skills/ |
Project-level (Cursor) |
.claude/skills/ |
Project-level (Claude) |
.codex/skills/ |
Project-level (Codex) |
skills/ |
Agent workspace-level (OpenClaw) |
~/.cursor/skills/ |
User-level global (Cursor) |
~/.claude/skills/ |
User-level global (Claude) |
~/.codex/skills/ |
User-level global (Codex) |
~/.openclaw/skills/ |
User-level global (OpenClaw) |
Reports are saved as <provider>__<skill_name>.json in ./skill_sentinel_reports/ (or the directory specified with -o).
from skill_sentinel.main import scan
report = scan("/path/to/skill", output_path="report.json", model="gpt-5.4-mini")
print(report["overall_risk_assessment"]["skill_verdict"])The scanner performs a multi-step security analysis:
- File Discovery β lists all files in the skill directory (static, no LLM).
- VirusTotal Binary Scan (optional) β if binary files (executables, archives, images, PDFs, etc.) are found and a
VIRUSTOTAL_API_KEYis set, each binary is checked against VirusTotal's malware database via SHA-256 hash lookup. Results are passed to the report synthesizer. - SKILL.md Analysis β an agent reads the SKILL.md manifest and instructions, looking for prompt injection, trust abuse, discovery abuse, and other threats.
- File Verification (conditional) β if the skill contains scripts or referenced files beyond SKILL.md, a second agent reads each file and checks alignment with SKILL.md claims, searching for command injection, data exfiltration, hardcoded secrets, obfuscation, etc.
- Report Synthesis β a final agent combines all findings (including VirusTotal results), filters false positives, prioritizes findings, and produces a structured JSON report.
If a VIRUSTOTAL_API_KEY environment variable is set, Skill Sentinel automatically scans binary files found in skill packages against VirusTotal's malware database. This runs before the agent pipeline β no LLM calls are needed.
Supported binary types: executables (.exe, .dll, .so, .dylib, .bin), archives (.zip, .tar, .gz, .7z, .rar), documents (.pdf, .doc, .xls), images (.png, .jpg, .gif), JVM/WASM (.jar, .war, .wasm, .class), and more.
Getting a free API key: Sign up at virustotal.com β the free tier allows 500 lookups/day, which is more than enough for skill scanning.
The scanner writes a JSON report containing:
skill_pathβ absolute path to the scanned skill directoryvalidated_findingsβ confirmed threats with severity, evidence, remediationfalse_positivesβ dismissed findings with reasoningpriority_orderβ ranked list of finding IDscorrelationsβ related findings grouped togetherrecommendationsβ actionable next stepsreferencesβ VirusTotal scan links and other reference URLsoverall_risk_assessmentβ risk level, verdict (SAFE / SUSPICIOUS / MALICIOUS), reasoningtoken_usageβ LLM token usage metrics for the scanskill_nameβ skill name from the SKILL.md frontmatter (falls back to the folder name)content_hashβ sha256 over all files in the skill directory (a change/dedup key)scan_durationβ wall-clock scan time ({seconds, display})
skill_scanner_package/
βββ pyproject.toml # Package build config
βββ README.md
βββ src/skill_sentinel/
βββ __init__.py # Package version
βββ cli.py # CLI entry point
βββ main.py # Programmatic API
βββ crew.py # Multi-agent crew definition
βββ config/
β βββ agents.yaml # Agent definitions
β βββ tasks.yaml # Task definitions
βββ data/
β βββ threat_categories.md # Threat taxonomy
β βββ report_schema.json # Output JSON schema
βββ tools/
βββ custom_tool.py # ReadFile & Grep tools
βββ file_discovery.py # Static file listing
βββ virustotal_tool.py # VirusTotal binary malware scanning
| Variable | Description | Default |
|---|---|---|
OPENAI_API_KEY |
OpenAI API key (required when OpenAI is the primary or a fallback) | β |
OPENAI_MODEL_NAME |
Primary model for analysis (takes precedence over PRIMARY_MODEL) |
gpt-5.4-mini |
PRIMARY_MODEL |
Primary model, used when OPENAI_MODEL_NAME is unset (lets you pick a non-OpenAI primary, e.g. anthropic/claude-<model-id>) |
β |
FALLBACK_MODELS |
Comma-separated provider/model list tried in order when the primary fails with a transient/provider error (see below) |
β |
ANTHROPIC_API_KEY |
Anthropic key (required if an anthropic/... model is used) |
β |
VIRUSTOTAL_API_KEY |
VirusTotal API key for binary malware scanning (optional) | β |
By default the scanner uses a single primary model. Set FALLBACK_MODELS to a
comma-separated list of provider/model strings to make each agent try the
primary first and fall back to the next model when a call fails with a
transient or provider-level error (rate limit, quota exhausted, 5xx,
connection, or auth). Non-retryable errors (e.g. a malformed request) are not
retried. Fallbacks may be a different provider than the primary:
export OPENAI_API_KEY="sk-..." # primary (OpenAI, native)
export ANTHROPIC_API_KEY="sk-ant-..." # first fallback
export GROQ_API_KEY="gsk_..." # second fallback (via LiteLLM)
export OPENAI_MODEL_NAME="gpt-5.4-mini"
export FALLBACK_MODELS="anthropic/claude-<model-id>,groq/llama-3.3-70b-versatile"Each model authenticates with its provider's standard key env var. OpenAI,
Anthropic, and Gemini ship as native providers with this package. Most other
providers work with no extra setup β either as native OpenAI-compatible
providers (DeepSeek, OpenRouter, Bedrock, β¦) or through the bundled LiteLLM
backend using their usual provider/model id (Groq, Together, Mistral, Cohere,
xAI, β¦). A native-only provider not bundled here (e.g. Azure) additionally needs
its CrewAI extra installed (e.g. pip install "crewai[...]"); a fallback whose
provider isn't installed is skipped with a warning and never breaks the primary.
Skill Sentinel detects the following threat categories, mapped to OWASP Top 10 for LLM Applications 2025 and OWASP Top 10 for Agentic Applications:
| Category | Severity | Description |
|---|---|---|
| Prompt Injection | HIGHβCRITICAL | Override attempts, mode changes, policy bypass hidden in SKILL.md |
| Transitive Trust Abuse | HIGH | Instructions that delegate trust to external/untrusted data sources |
| Data Exfiltration | CRITICAL | Network calls that steal credentials, files, or environment variables |
| Command Injection | CRITICAL | Dangerous eval(), exec(), os.system(), shell injection |
| Hardcoded Secrets | CRITICAL | API keys, passwords, private keys embedded in code |
| Obfuscation | HIGH | Base64 blobs + exec, hex-encoded payloads, deliberately unreadable code |
| Unauthorized Tool Use | HIGH | Code that violates the skill's own allowed-tools declaration |
| Skill Discovery Abuse | HIGH | Brand impersonation, keyword baiting, misleading descriptions |
| Tool Chaining Abuse | HIGH | Multi-step workflows that read sensitive data then transmit it |
| Resource Abuse | MEDIUM | Infinite loops, unbounded memory allocation, recursive bombs |
| Autonomy Abuse | MEDIUM | Unsolicited activation, unbounded retries, no user confirmation |
| Over-Collection | MEDIUM | Disproportionate data access relative to stated purpose |
| Cross-Context Bridging | MEDIUM | Accessing data from other sessions, conversations, or workspaces |
| Dependency Risk | MEDIUM | Unpinned pip install, typosquatting, unknown GitHub repos |
| Malware | MEDIUMβCRITICAL | Binary files flagged by VirusTotal or unverifiable binaries |
Contributions are welcome! By submitting a pull request or other contribution, you agree to the terms of our Contributor License Agreement (CLA). See CONTRIBUTING.md for setup, project structure, and how to submit a PR.
This project is licensed under the Apache License 2.0.
Copyright 2025 Enkrypt AI, Inc.
