NVIDIA/SkillSpector
Security scanner for AI agent skills. Detect vulnerabilities, malicious patterns, security risks, prompt injection, data exfiltration, and supply-chain risks in Claude Code, Codex
About NVIDIA/SkillSpector
NVIDIA/SkillSpector is an open-source project on GitHub, mainly written in Python. Security scanner for AI agent skills. Detect vulnerabilities, malicious patterns, security risks, prompt injection, data exfiltration It currently holds 18,464 stars and 1,607 forks with 144 open issues, and was last pushed on 2026-09-26 (repository created 2026-03-21).
Project Overview
Git Homed tracks it on the Today's Trending board.
GitHub Repository Details
README
SkillSpector
Security scanner for AI agent skills. Detect vulnerabilities, malicious patterns, and security risks before installing agent skills.
Overview
AI agent skills (used by Claude Code, Codex CLI, Gemini CLI, etc.) execute with implicit trust and minimal vetting. In the 31,132-skill analyzed subset of the research dataset, 26.1% of skills contain vulnerabilities and 5.2% show likely malicious intent.
SkillSpector helps you answer: "Is this skill safe to install?"
SkillSpector is part of the NVIDIA Verified Skills pipeline, which scans, evaluates, and signs agent skills before publication. Skills that pass are published to the NVIDIA skills catalog.
Documentation
- Scan agent skills before installation — Hosted guide: when to scan, how to read a report, and how to gate installs.
- Development guide — Architecture, package layout, and how to extend the analyzer pipeline.
- Analysis resource bounds — Fail-closed bundle, parser, nested-artifact, ledger, and finding ceilings.
- Pi extension — Install SkillSpector as a Pi tool for scanning skills from inside agent sessions.
- OpenCode extension — Install SkillSpector as an OpenCode tool and
/skillspectorcommand for scanning skills from inside agent sessions.
Features
- Multi-format input: Scan Git repos, URLs, zip files, directories, or single files
- 71 vulnerability patterns across 17 categories: prompt injection, data exfiltration, privilege escalation, supply chain, excessive agency, output handling, system prompt leakage, memory poisoning, tool misuse, rogue agent, anti-refusal, trigger abuse, dangerous code (AST), taint tracking, YARA signatures, MCP least privilege, and MCP tool poisoning
- Two-stage analysis: Fast static analysis + optional LLM semantic evaluation
- Live vulnerability lookups: SC4 queries OSV.dev for real-time CVE data with automatic offline fallback
- Multiple output formats: Terminal, JSON, Markdown, and SARIF reports
- Risk scoring: 0-100 score with severity labels and clear recommendations
- Baseline / false-positive suppression: Accept known findings via a glob-rule or fingerprint baseline so re-scans surface only new issues (docs)
Quick Start
Installation
Open-source software notice: This project will download and install additional third-party open source software projects. Review the license terms of these open source projects before use.
Create and activate a virtual environment first (all make targets assume the venv is active). Use uv or pip; the Makefile uses uv if available, otherwise pip.
Quick install with uv (CLI-only):
uv tool install git+https://github.com/NVIDIA/skillspector.git
Update later: uv tool update skillspector
If you plan to run skillspector mcp, install the MCP extra at install time:
uv tool install 'skillspector[mcp] @ git+https://github.com/NVIDIA/skillspector.git'
From source:
# Clone the repository
git clone https://github.com/NVIDIA/skillspector.git
cd skillspector
Create and activate virtual environment
uv venv .venv && source .venv/bin/activate
or: python3 -m venv .venv && source .venv/bin/activate
Install for production use
make install
Or install with development dependencies
make install-dev
Docker (no Python required)
Run SkillSpector without installing Python by building it locally from the included Dockerfile. The image is based on the Docker Official Python 3.12-slim-bookworm image.
Build the image:
make docker-build
or: docker build -t skillspector .
Scan a local directory by mounting your current directory into /scan, the container's working directory:
docker run --rm -v "$PWD:/scan" skillspector scan ./my-skill/ --no-llm
Scan with LLM analysis by passing credentials with a local .env file:
cat > .env <<'EOF'
SKILLSPECTOR_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...
EOF
docker run --rm \
-v "$PWD:/scan" \
--env-file .env \
skillspector scan ./my-skill/
Or pass credentials directly from your shell environment:
docker run --rm \
-v "$PWD:/scan" \
-e SKILLSPECTOR_PROVIDER=anthropic \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
skillspector scan ./my-skill/
Write a report to the host filesystem by writing to the mounted directory:
docker run --rm \
-v "$PWD:/scan" \
skillspector scan ./my-skill/ --no-llm --format json --output report.json
Optional alias for repeated static scans:
alias skillspector-docker='docker run --rm -v "$PWD:/scan" skillspector'
skillspector-docker scan ./my-skill/ --no-llm
Basic Usage
# Scan a local skill directory
skillspector scan ./my-skill/
Scan a single SKILL.md file
skillspector scan ./SKILL.md
Scan a Git repository
skillspector scan https://github.com/user/my-skill
Scan a zip file
skillspector scan ./my-skill.zip
Size limits
SkillSpector enforces two independent caps on remote and archive inputs to bound the impact of oversized downloads and zip bombs:
- Per-ingest cap:
INGEST_MAX_BYTES(100 MiB) — applied to streamed URL downloads, total uncompressed size of zip archives, and post-clone disk usage of Git repos. - Zip member cap:
INGEST_MAX_ZIP_MEMBERS(10,000) — caps the number of entries in a single zip.
MAX_FILE_BYTES) is a separate, downstream limit: it bounds what individual analyzers will read out of an already-ingested directory. The ingest caps above bound how much content can land on disk in the first place. A breach of either ingest cap fails closed with an IngestLimitExceededError.
Output Formats
# Terminal output (default) - pretty formatted
skillspector scan ./my-skill/
JSON output - machine readable
skillspector scan ./my-skill/ --format json --output report.json
Markdown output - for documentation
skillspector scan ./my-skill/ --format markdown --output report.md
SARIF output - for CI/CD integration and IDE tooling
skillspector scan ./my-skill/ --format sarif --output report.sarif
Batch Scanning
Scan entire directories of skills in parallel from contrib/batch_scan/:
python -m contrib.batch_scan.batch_scan ./my-skills/ --no-llm
python -m contrib.batch_scan.batch_scan ./my-skills/ --workers 20 -f json -o report.json
python -m contrib.batch_scan.batch_scan ./tests/fixtures/ -f terminal --workers 20
Supports multilingual detection (zh/ja/ko) and terminal/JSON/Markdown output.
For LLM scans with higher concurrency, configure multiple API keys following
.env.example — the pool improves throughput
and resilience, provided the keys don't share an account-level rate limit.
See the contrib guide for details.
Note on LLM support: The default configuration targets DeepSeek as the
cheapest public option. DeepSeek-Chat is
expected to sunset, and the contributor
does not have hardware to test against local models. The batch scanner was
originally tested with OpenAI-compatible endpoints — DeepSeek's lack of
structured-output support required manual JSON-parsing patches. If you can
contribute a more universal backend (Ollama, vLLM, or a different provider),
PRs are very welcome.
Comparing MCP Registry snapshots
Start with raw registry payload captures (registry-before.json and
registry-after.json). Save a scan report as previous.json, then compare a
later scan against that generated local report:
skillspector scan registry-before.json --mcp-registry --format json --output previous.json
skillspector scan registry-after.json --mcp-registry --format json \
--mcp-registry-compare previous.json --output compared.json
The optional comparison object lists added and removed server identities,
changed normalized fields with their previous and current values, and an
unchanged_count. Identity is the server name and version, so a new version
appears as an addition and the old version as a removal if it is absent from the
new scan. Acquisition source and scan timestamp are excluded from comparison.
unmodeled_changes lists same-identity records whose raw-record hash changed
while normalized fields match; these are not counted as unchanged. This can
indicate a change to fields outside the snapshot model, or array reordering in
the raw record. Package and remote ordering alone is not a normalized field change.
Entries in changed may also contain changes outside the snapshot model; inspect
the raw record to see those changes.
Compare reports with the same selection scope: a server absent from the current
input is reported as removed, which does not prove it was removed from a registry.
The comparison file must be a local report produced by this registry mode with
valid normalized snapshots. Malformed, oversized or duplicate server identities
are rejected. This option requires --mcp-registry and is separate from the
finding-suppression --baseline: it never suppresses findings or changes risk
scores, and it does not fetch or execute listed server endpoints.
Suppressing False Positives (baseline)
Suppress known/accepted findings so the risk score reflects only un-triaged issues and re-scans surface only new findings. See the suppression guide for the full reference.
# Accept all current findings into a baseline (run once), then commit it.
skillspector baseline ./my-skill/ -o .skillspector-baseline.yaml
Scan against the baseline — only NEW findings are reported and scored.
skillspector scan ./my-skill/ --baseline .skillspector-baseline.yaml
Review what was suppressed (still excluded from the score).
skillspector scan ./my-skill/ --baseline .skillspector-baseline.yaml --show-suppressed
A baseline can also use drift-tolerant glob rules (by rule id, file path, or
message) — see .skillspector-baseline.example.yaml.
Exact fingerprint baselines are evidence-bound: changing the scanned source or
SkillSpector version keeps the finding active until it is reviewed again.
When a selected baseline or baseline output is stored inside the skill
directory, SkillSpector excludes that exact file from content analysis so its
suppression text cannot create findings or enter regenerated fingerprints;
sibling files remain in normal scan scope.
Explicit scan scope
For a local directory containing one SKILL.md or skill.md, repeat --exclude to omit
selected files before content analysis:
skillspector scan ./my-skill --exclude 'tests/' --exclude 'fixtures/.json'
Patterns are case-sensitive globs matched against the entire relative POSIX path;
* also matches /. Quote patterns so your shell does not expand them. This is
an explicit caller option, not an author-controlled ignore file. Patterns matching
either manifest name (SKILL.md or skill.md) are rejected, as are recursive, multi-skill, transitive, registry, and
non-directory inputs. No-match patterns are still recorded in the report.
Files already outside the scan inventory, such as policy-excluded dependencies,
retain their existing policy handling and are not counted as caller exclusions.
Explicitly excluded inventory files remain in the coverage denominator as entirely uninspected. The
report lists the applied patterns, excluded count and individual skipped paths;
any matched exclusion makes coverage partial and prevents a SAFE recommendation.
Use --fail-on-incomplete when partial scope should fail CI. These exclusions do
not suppress findings in included files or override existing safety limits.
LLM Analysis
For the best results, configure an OpenAI-compatible LLM endpoint for
semantic analysis. Pick a provider with SKILLSPECTOR_PROVIDER; hosted providers ship bundled default models, while CLI providers fall back to the local runtime's default model unless SKILLSPECTOR_MODEL is set. SkillSpector also works against
local OpenAI-compatible servers (Ollama, vLLM, llama.cpp) and managed
inference gateways.
| Provider (SKILLSPECTOR_PROVIDER) | Credential env var | Endpoint | Default model |
| ---------- | ---- | ---- | ---- |
| openai | OPENAI_API_KEY (+ optional OPENAI_BASE_URL) | api.openai.com (or any OpenAI-compatible URL) | gpt-5.4 |
| anthropic | ANTHROPIC_API_KEY | api.anthropic.com | claude-opus-4-6 |
| anthropic_proxy | ANTHROPIC_PROXY_API_KEY + ANTHROPIC_PROXY_ENDPOINT_URL | Any Vertex-style raw-predict proxy | claude-sonnet-4-6 |
| bedrock | AWS_PROFILE (optional) + AWS_REGION — SigV4 via boto3 | AWS Bedrock Runtime | us.anthropic.claude-sonnet-4-6-20250915-v1:0 |
| nv_build | NVIDIA_INFERENCE_KEY | build.nvidia.com | z-ai/glm-5.2 |
| gemini | GOOGLE_CLOUD_PROJECT (+ optional GOOGLE_CLOUD_LOCATION) via ADC | Google Cloud OpenAI-compatible Gemini endpoint | gemini-3.8-flash |
| ollama | _(none)_ | OLLAMA_BASE_URL (default http://localhost:11434/v1) | llama3.1:8b |
| azure_openai | AZURE_OPENAI_API_KEY + AZURE_OPENAI_ENDPOINT | Azure OpenAI Service | gpt-4o (deployment defaults to the model label) |
| openai_compatible | SKILLSPECTOR_COMPAT_API_KEY + SKILLSPECTOR_COMPAT_BASE_URL | Any OpenAI-compatible endpoint | llama-3.1-70b-versatile |
| claude_cli | _(none — uses local CLI auth)_ | local claude binary | local Claude runtime fallback, or SKILLSPECTOR_MODEL |
| codex_cli | _(none — uses local CLI auth)_ | local codex binary | local Codex runtime fallback, or SKILLSPECTOR_MODEL |
| gemini_cli | _(none — uses local CLI auth)_ | local gemini binary | local Gemini runtime fallback, or SKILLSPECTOR_MODEL |
| opencode_cli | _(none — uses local CLI auth)_ | local opencode 1.18.33 binary | local OpenCode runtime fallback, or SKILLSPECTOR_MODEL |
Structured output is requested through LangChain's with_structured_output,
whose default forces a tool call. Some models reject a forced tool call with
HTTP 400 (`tool_choice: type "tool" and "any" are not supported for this
model). The anthropic and anthropic_proxy` providers route those models
(claude-fable-5-1, claude-mythos-5-1, or any registry entry with
structured_output: json_schema) to the native JSON-schema response format.
Bedrock has no JSON-schema output for them, so the bedrock provider leaves
toolChoice at auto, asks for the tool call in the prompt, and retries a
prose answer; it recognises the model from the model ID, a geo/global
inference-profile ID, or a foundation-model / inference-profile ARN. An
application-inference-profile ARN hides the model, so add that ARN to the
registry (SKILLSPECTOR_MODEL_REGISTRY) with tool_choice: auto.
The openai_compatible provider honours the same tool_choice: auto entry
for endpoints that ignore both response_format and a forced tool_choice
and answer in prose (for example iFlytek's spark-x2.5, which is bundled).
SKILLSPECTOR_STRUCTURED_OUTPUT_METHOD=json_schema|function_calling
overrides the method for any provider.
# Stock OpenAI
export SKILLSPECTOR_PROVIDER=openai
export OPENAI_API_KEY=sk-...
skillspector scan ./my-skill/
Anthropic
export SKILLSPECTOR_PROVIDER=anthropic
export ANTHROPIC_API_KEY=sk-ant-...
skillspector scan ./my-skill/
Anthropic via Vertex-style proxy (corporate gateways, GCP Vertex AI)
export SKILLSPECTOR_PROVIDER=anthropic_proxy
export ANTHROPIC_PROXY_ENDPOINT_URL=https://my-gateway.example.com/models/claude-sonnet-4-6:streamRawPredict
export ANTHROPIC_PROXY_API_KEY=your-bearer-token
export SKILLSPECTOR_MODEL=claude-sonnet-4-6
skillspector scan ./my-skill/
AWS Bedrock (Claude via SigV4)
export SKILLSPECTOR_PROVIDER=bedrock
Optional: select an AWS named profile. When unset, the standard
boto3 credential chain (env vars, instance metadata, SSO, etc.) resolves.
export AWS_PROFILE=my-profile
export AWS_REGION=us-west-2 # default if unset
Default model: us.anthropic.claude-sonnet-4-6-20250915-v1:0
Override with any Bedrock model ID, cross-region inference-profile
ID, or your own application-inference-profile ARN:
export SKILLSPECTOR_MODEL=us.anthropic.claude-opus-4-6-20250915-v1:0
skillspector scan ./my-skill/
NVIDIA build.nvidia.com
export SKILLSPECTOR_PROVIDER=nv_build
export NVIDIA_INFERENCE_KEY=nvapi-...
skillspector scan ./my-skill/
Gemini on Google Cloud (Application Default Credentials / Workload Identity)
Prerequisites:
1. Google Cloud project with billing enabled.
2. Enable Gemini Enterprise Agent Platform / Vertex AI API: aiplatform.googleapis.com.
3. IAM permission: grant roles/aiplatform.user (or at minimum aiplatform.endpoints.predict)
to your user account or Kubernetes service account.
4. Local authentication: run gcloud auth application-default login.
Configure a quota project if needed: gcloud auth application-default set-quota-project PROJECT_ID.
5. Kubernetes / GKE: configure Workload Identity and leave GOOGLE_APPLICATION_CREDENTIALS unset
rather than exporting service account keys.
Note on Data Residency:
The default global endpoint does not support data-residency requirements. While you can target
us, eu, or regional endpoints (e.g. us-central1), endpoint selection alone does not guarantee
data residency or in-region processing without appropriate organizational policies. Always verify
that your selected model is supported in your target location.
Optional credentials:
GOOGLE_APPLICATION_CREDENTIALS is optional and can reference Workload or Workforce Identity Federation
configuration files; exporting long-lived service account keys is discouraged.
export SKILLSPECTOR_PROVIDER=gemini
export GOOGLE_CLOUD_PROJECT=my-project-id
export GOOGLE_CLOUD_LOCATION=global # default is global; or us, eu, or specific region (e.g. us-central1)
Default model: gemini-3.8-flash
export SKILLSPECTOR_MODEL=gemini-3.7-flash
skillspector scan ./my-skill/
Local Claude CLI — no API key; uses your existing claude auth login session
Requires: claude CLI installed and authenticated (claude auth login)
export SKILLSPECTOR_PROVIDER=claude_cli
Uses the local Claude CLI runtime fallback unless SKILLSPECTOR_MODEL is set.
export SKILLSPECTOR_MODEL=claude-sonnet-4-6
skillspector scan ./my-skill/
Local Codex CLI — no API key; uses your existing codex login session
Requires: codex CLI installed and authenticated
export SKILLSPECTOR_PROVIDER=codex_cli
skillspector scan ./my-skill/
Gemini (via OpenAI compatibility layer)
export SKILLSPECTOR_PROVIDER=openai
export OPENAI_API_KEY="YOUR_GEMINI_API_KEY"
export OPENAI_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/"
export SKILLSPECTOR_MODEL=gemini-3.5-flash
skillspector scan ./my-skill/
Local Ollama — no API key
export SKILLSPECTOR_PROVIDER=ollama
export OLLAMA_BASE_URL=http://localhost:11434/v1 # shown default
export SKILLSPECTOR_MODEL=llama3.1:8b
skillspector scan ./my-skill/
Azure OpenAI
export SKILLSPECTOR_PROVIDER=azure_openai
export AZURE_OPENAI_API_KEY=...
export AZURE_OPENAI_ENDPOINT=https://example.openai.azure.com/
export AZURE_OPENAI_DEPLOYMENT=my-deployment
skillspector scan ./my-skill/
Any other OpenAI-compatible endpoint
export SKILLSPECTOR_PROVIDER=openai_compatible
export SKILLSPECTOR_COMPAT_API_KEY=...
export SKILLSPECTOR_COMPAT_BASE_URL=https://api.groq.com/openai/v1
export SKILLSPECTOR_MODEL=llama-3.1-70b-versatile
skillspector scan ./my-skill/
Override the provider's default model
export SKILLSPECTOR_MODEL=gpt-5.2
skillspector scan ./my-skill/
Skip LLM analysis (faster, static analysis only)
skillspector scan ./my-skill/ --no-llm
MCP Server
Run SkillSpector as a Model Context Protocol server so local MCP-capable agents (Claude Code, Codex CLI, Gemini CLI) can call scanning as a tool and gate skill/MCP installs on the result — turning SkillSpector into a runtime guardrail instead of an out-of-band audit step.
skillspector mcp requires skillspector[mcp].
# Install, or reinstall if you already used the CLI-only path
uv tool install --force 'skillspector[mcp] @ git+https://github.com/NVIDIA/skillspector.git'
FastMCP stdio transport for local CLI agents
skillspector mcp
Streamable HTTP transport on a local loopback interface
skillspector mcp --transport http --host 127.0.0.1 --port 8000
The stdio transport is the current FastMCP path for local CLI agents, and the initialize hang reported in issue #199 still applies there.
The server exposes a single tool:
scan_skill(target, use_llm=true, output_format="json")— scans a Git
.zip, .md file, or directory and returns a structured
verdict: risk_score (0-100), severity, recommendation,
safe_to_install, and findings. It also reports llm_used / scan_mode
so a low score from a static-only scan is never mistaken for a clean full
scan.
Register it with Claude Code via:
claude mcp add skillspector -- skillspector mcp
Security — HTTP transport trust model
> The HTTP transport ships without authentication. Any caller that can
reach the port can invoke scan_skill. HTTP bindings are restricted to
loopback IPs (127.0.0.1or::1);localhostbinds to127.0.0.1without
DNS resolution. Wildcard, routable and other hostname bindings are rejected.
> - For remote access, put an authenticating reverse proxy (e.g. nginx + mTLS)
in front of the loopback listener.
- Local paths and file:// URLs are automatically rejected over HTTP to
prevent unauthenticated callers from reading arbitrary host files. Only
remote Git and .zip URLs are accepted.
Vulnerability Patterns
SkillSpector detects 71 vulnerability patterns across 17 categories:
Prompt Injection (6 patterns)
| ID | Pattern | Severity | Description | |----|---------|----------|-------------| | P1 | Instruction Override | HIGH | Commands to ignore safety constraints | | P2 | Hidden Instructions | HIGH | Malicious directives in comments/invisible text | | P3 | Exfiltration Commands | HIGH | Instructions to transmit context externally | | P4 | Behavior Manipulation | MEDIUM | Subtle instructions altering agent decisions | | P5 | Harmful Content | CRITICAL | Instructions that could cause physical harm | | P9 | Whitespace Padding | MEDIUM | Large whitespace padding hiding instructions below/beside the visible area |
Anti-Refusal (3 patterns)
| ID | Pattern | Severity | Description | |----|---------|----------|-------------| | AR1 | Refusal Suppression | HIGH | Instructions to never refuse or always comply (e.g. "never refuse", "always comply") | | AR2 | Disclaimer Suppression | HIGH | Instructions to omit warnings, disclaimers, or ethical commentary (e.g. "no disclaimers", "do not moralize") | | AR3 | Safety Policy Nullification | HIGH | Jailbreak framing that nullifies guardrails (e.g. "you have no restrictions", "ignore your guidelines", "do anything now") |
Data Exfiltration (4 patterns)
| ID | Pattern | Severity | Description | |----|---------|----------|-------------| | E1 | External Transmission | MEDIUM | Sending data to external