Security Scanner
Every cloned repository is scanned before the generated MCP server is built. Two scan modes are available: the default AST scanner and the optional LLM scanner. They can be used independently or together.
AST scanner (default)
Static analysis using Python's ast module. Fast, deterministic, and runs on every build.
Severity levels
| Severity | What triggers it |
|---|---|
HIGH | eval/exec/compile, os.system, subprocess with shell=True, subprocess.getoutput, pickle.loads, yaml.load without Loader= |
MEDIUM | Capability imports (subprocess, pickle, raw sockets, unsafe XML parsers), dynamic reflection (getattr/setattr with non-literal names), potential secret logging |
LOW | HTTP clients (requests, urllib, httpx), crypto primitives (hashlib, ssl), deprecated modules |
Behavior
- generates a terminal report
- writes
workspace/security_report.txt - for repos with
HIGHfindings: prompts the user to include or skip before the build proceeds - excluded repos do not appear in the generated server
LLM scanner (--llm-scan)
An LLM reads the full source of each repo and makes an autonomous INCLUDE / SKIP decision. The human approval prompt is bypassed entirely — the build proceeds automatically based on the LLM verdict.
How it works
- Python source files are collected per repo (up to ~80k chars total, 12k per file)
- The source is sent to the configured model with a security-focused system prompt
- The model returns a structured verdict:
INCLUDEorSKIP, with confidence level and per-finding details - Repos the model marks
SKIPare excluded from the build automatically - All findings are merged into the same
security_report.txt
LLM findings are tagged [LLM] in the report so they are visually distinct from AST findings.
Model-agnostic via LangChain
The LLM scanner uses LangChain's init_chat_model and with_structured_output, so any supported provider works. The provider is inferred automatically from the model string.
# Claude (default)
export ANTHROPIC_API_KEY=sk-...
toolstorepy build --queries q.json --index core-tools --llm-scan
# GPT-4o
export OPENAI_API_KEY=sk-...
toolstorepy build --queries q.json --index core-tools --llm-scan --llm-model gpt-4o
# Gemini
export GOOGLE_API_KEY=...
toolstorepy build --queries q.json --index core-tools --llm-scan --llm-model gemini-2.0-flash
Install the integration package for your provider:
pip install langchain-anthropic # Claude → ANTHROPIC_API_KEY
pip install langchain-openai # GPT → OPENAI_API_KEY
pip install langchain-google-genai # Gemini → GOOGLE_API_KEY
Verdict schema
The model returns a structured response validated by a Pydantic schema:
{
"verdict": "INCLUDE" | "SKIP",
"confidence": "HIGH" | "MEDIUM" | "LOW",
"summary": "one sentence",
"findings": [
{
"severity": "HIGH" | "MEDIUM" | "LOW",
"file": "relative/path.py",
"line": 42,
"category": "Shell injection",
"detail": "what the risk is and why it matters"
}
]
}
Fail-open behavior
If the LLM API call fails (missing key, network error, rate limit), the scanner logs the error as a MEDIUM finding and defaults to INCLUDE for that repo. A broken API key does not silently kill the entire build.
Using both scanners together
When --llm-scan is active, the AST scan still runs. Both sets of findings are merged into the same RepoReport and written to security_report.txt. The LLM decision is final — no human prompt fires.
AST scan (always)
+
LLM scan (--llm-scan)
|
v
merged security_report.txt
|
v
LLM verdict → INCLUDE or SKIP (no human prompt)
Security report
workspace/security_report.txt is always written regardless of scan mode. It contains:
- per-repo status (
CLEAN,HIGH risk,MEDIUM risk,LOW risk) - all individual findings with file path, line number, category, and detail
[LLM]-tagged findings when LLM scan was used- a summary table at the end
Important limitation
Both scanners are static. They improve reviewability and catch common risky patterns, but they are not sandboxes and they cannot prove that a repository is safe. Review security_report.txt and inspect the generated server before production use.