Analysis Modes¶
MCP-Scan offers two analysis modes with different levels of depth and performance.
Table of Contents¶
- Overview
- Fast Mode
- Deep Mode
- Detailed Comparison
- Taint Analysis
- Call Graph
- When to Use Each Mode
- Performance Optimization
Overview¶
| Feature | Fast | Deep |
|---|---|---|
| Analysis | Intra-procedural | Inter-procedural |
| Speed | Fast (seconds) | Slow (minutes) |
| Precision | Medium | High |
| Coverage | Function by function | Flows between functions |
| Memory | Low | High |
| Typical use | CI/CD, PRs | Audits, certification |
Fast Mode¶
Description¶
Fast mode performs intra-procedural analysis, meaning it analyzes each function in isolation without following data flow between functions.
How It Works¶
# Example: Function with vulnerability
@tool
def execute_command(cmd: str) -> str:
# Fast mode detects this because:
# 1. cmd is a tool parameter (source)
# 2. subprocess.run with shell=True is a sink
# 3. The source -> sink flow is within the same function
return subprocess.run(cmd, shell=True, capture_output=True).stdout
Detections in Fast Mode¶
Fast mode detects:
- Direct patterns - Regular expressions on code
- Intra-procedural flows - Source to sink within a function
- Insecure configurations - Cookies without Secure, JWT without verification
- Hardcoded secrets - Variables with sensitive values
- Basic tool poisoning - Malicious descriptions
# DETECTED in fast mode
@tool
def run(command: str) -> str:
return os.system(command) # Direct flow
# NOT DETECTED in fast mode (requires deep)
@tool
def run(command: str) -> str:
safe_cmd = sanitize(command) # Another function
return execute(safe_cmd) # Another function
Fast Mode Advantages¶
- Speed: Seconds instead of minutes
- Memory: Low RAM consumption
- Parallelism: Files analyzed independently
- Determinism: Consistent and fast results
Fast Mode Limitations¶
- Does not follow flows between functions
- Does not detect sanitizers in other functions
- May have false negatives in modular code
- Does not build call graph
Deep Mode¶
Description¶
Deep mode performs inter-procedural analysis, building a call graph and following data flow across multiple functions.
How It Works¶
# Example: Flow between functions
def get_user_input() -> str:
return input() # Source
def process_input(data: str) -> str:
return data.strip() # Propagation
def execute(cmd: str):
os.system(cmd) # Sink
@tool
def handler():
data = get_user_input() # Taint: data
processed = process_input(data) # Taint: processed
execute(processed) # DETECTED in deep mode
Call Graph Construction¶
Deep mode builds a call graph:
handler()
|-- get_user_input() -> [source: user_input]
|-- process_input(data) -> [taint propagation]
+-- execute(cmd) -> [sink: os.system]
Additional Detections in Deep Mode¶
- Inter-procedural flows - Source in one function, sink in another
- Sanitizers in separate functions - Reduces false positives
- Callbacks and closures - Captured variables
- Imports between files - Cross-file flows
# DETECTED in deep mode
# file: utils.py
def execute_query(sql: str):
cursor.execute(sql) # Sink
# file: handlers.py
@tool
def search(query: str) -> str: # Source
result = execute_query(f"SELECT * WHERE name='{query}'")
return result
Deep Mode Advantages¶
- Detection of complex flows
- Fewer false negatives
- Sanitizer analysis
- Better coverage in modular code
Deep Mode Limitations¶
- Slower (minutes vs seconds)
- Higher memory consumption
- May timeout on large projects
- Does not support all dynamic constructs
Detailed Comparison¶
Capabilities Table¶
| Capability | Fast | Deep |
|---|---|---|
| Pattern detection | Yes | Yes |
| Intra-procedural flow | Yes | Yes |
| Inter-procedural flow | No | Yes |
| Call graph | No | Yes |
| Sanitizer analysis | Partial | Complete |
| Cross-file flows | No | Yes |
| Closures and callbacks | No | Yes |
| Imports/exports | No | Yes |
| Type resolution | Basic | Advanced |
| Return value analysis | No | Yes |
Comparative Example¶
# Code to analyze
def sanitize(data: str) -> str:
return shlex.quote(data)
def execute(cmd: str):
subprocess.run(cmd, shell=True)
@tool
def run_safe(command: str) -> str:
safe = sanitize(command)
execute(safe)
return "done"
@tool
def run_unsafe(command: str) -> str:
execute(command)
return "done"
Results:
| Mode | run_safe | run_unsafe |
|---|---|---|
| Fast | ALERT (false positive) | ALERT (correct) |
| Deep | OK (sanitized) | ALERT (correct) |
Deep mode recognizes that sanitize() is a sanitizer and does not report run_safe.
Taint Analysis¶
What is Taint Analysis¶
Taint analysis tracks the flow of "tainted" data from input sources to dangerous operations (sinks).
Sources (Data Sources)¶
| Source | Description | Example |
|---|---|---|
tool_input |
MCP tool parameters | @tool def f(cmd: str) |
env_var |
Environment variables | os.environ["KEY"] |
http_request |
HTTP request data | request.args |
file_content |
File contents | open(f).read() |
db_result |
Database results | cursor.fetchone() |
config |
Configuration values | config["setting"] |
Sinks (Dangerous Operations)¶
| Sink | Description | Example |
|---|---|---|
exec |
Command execution | os.system(), subprocess.run() |
eval |
Dynamic evaluation | eval(), exec() |
filesystem |
File operations | open(), shutil.copy() |
network |
Network operations | requests.get(), urllib.urlopen() |
database |
Database queries | cursor.execute() |
logging |
Logging | logger.info() |
response |
Responses | return data |
Sanitizers (Safe Functions)¶
| Sanitizer | Purpose | Example |
|---|---|---|
shlex.quote |
Shell escaping | shlex.quote(cmd) |
html.escape |
HTML escaping | html.escape(text) |
parameterized |
Parameterized SQL | cursor.execute(sql, (param,)) |
os.path.basename |
Filename only | os.path.basename(path) |
Taint Flow¶
SOURCE --> PROPAGATION --> SANITIZER? --> SINK
(assignment, (breaks (dangerous
concat, chain) operation)
calls)
Flow example:
@tool
def search(query: str) -> str: # SOURCE: query tainted
sql = f"SELECT * FROM users WHERE name='{query}'" # PROPAGATION: sql tainted
cursor.execute(sql) # SINK: tainted data reaches sink
# ALERT: SQL Injection
Call Graph¶
What is the Call Graph¶
A call graph is a directed graph where: - Nodes: Functions/methods - Edges: Calls between functions
Call Graph Example¶
def main():
handler()
def handler():
data = get_input()
process(data)
def get_input():
return input()
def process(x):
execute(x)
def execute(cmd):
os.system(cmd)
Resulting call graph:
Call Graph Usage¶
Deep mode uses the call graph to:
- Follow taint between functions
- Detect sanitizers in the path
- Calculate reachability
- Identify entry points
Call Graph Caching¶
The call graph can be cached for future scans:
# First scan (builds cache)
mcp-scan scan . --mode deep
# Subsequent scans (uses cache)
mcp-scan scan . --mode deep # Faster
The cache is saved in .mcp-scan/callgraph.gob and is invalidated when:
- Files change (different hash)
- Dependencies are modified
When to Use Each Mode¶
Use Fast Mode¶
| Scenario | Reason |
|---|---|
| CI/CD on every PR | Speed is critical |
| Pre-commit hooks | Immediate feedback |
| Local development | Fast iteration |
| Very large projects | Avoid timeouts |
| First pass | Identify obvious issues |
Use Deep Mode¶
| Scenario | Reason |
|---|---|
| Security audit | Maximum coverage |
| Certification | Required by compliance |
| Final release | Before production |
| Critical code | High security required |
| Finding investigation | Understand flows |
Combined Strategy¶
# On every PR: fast
mcp-scan scan . --mode fast --fail-on critical
# Nightly: deep
mcp-scan scan . --mode deep --fail-on high
# Pre-release: deep with evidence
mcp-scan scan . --mode deep --output evidence
Performance Optimization¶
Optimize Fast Mode¶
# Limit files
include:
- "src/**/*.py" # Source only
exclude:
- "**/tests/**"
- "**/vendor/**"
# Increase workers
scan:
workers: 8
timeout: 2m
Optimize Deep Mode¶
# Limit scope
include:
- "src/core/**/*.py" # Critical code only
# Use cache
scan:
mode: deep
timeout: 30m
workers: 4
# Exclude tests
exclude:
- "**/tests/**"
- "**/mocks/**"
Performance Tips¶
| Tip | Impact |
|---|---|
| Exclude node_modules/vendor | High |
| Limit to src/ | High |
| Increase workers | Medium |
| Use cache (deep) | Medium |
| Disable LLM | Medium |
| Disable CodeQL | High |
Typical Benchmarks¶
| Project | Files | Fast | Deep |
|---|---|---|---|
| Small (10 files) | 10 | 2s | 15s |
| Medium (100 files) | 100 | 10s | 2m |
| Large (1000 files) | 1000 | 1m | 15m |
| Very large (10000 files) | 10000 | 5m | 1h+ |
Summary¶
| Aspect | Fast | Deep |
|---|---|---|
| Speed | Seconds | Minutes |
| Precision | Medium | High |
| Memory | Low | High |
| Call Graph | No | Yes |
| Cross-file | No | Yes |
| Sanitizers | Partial | Complete |
| Use | CI/CD | Audits |
General recommendation:
- Daily development:
--mode fast - Before release:
--mode deep - Formal audit:
--mode deep --output evidence
Previous: Configuration | Next: Output Formats