Skip to content

Analysis Modes

MCP-Scan offers two analysis modes with different levels of depth and performance.


Table of Contents

  1. Overview
  2. Fast Mode
  3. Deep Mode
  4. Detailed Comparison
  5. Taint Analysis
  6. Call Graph
  7. When to Use Each Mode
  8. 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 (default)
mcp-scan scan . --mode fast

# Deep mode
mcp-scan scan . --mode deep

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:

  1. Direct patterns - Regular expressions on code
  2. Intra-procedural flows - Source to sink within a function
  3. Insecure configurations - Cookies without Secure, JWT without verification
  4. Hardcoded secrets - Variables with sensitive values
  5. 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

  1. Inter-procedural flows - Source in one function, sink in another
  2. Sanitizers in separate functions - Reduces false positives
  3. Callbacks and closures - Captured variables
  4. 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:

main
  +-- handler
        |-- get_input
        +-- process
              +-- execute

Call Graph Usage

Deep mode uses the call graph to:

  1. Follow taint between functions
  2. Detect sanitizers in the path
  3. Calculate reachability
  4. 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
# .github/workflows/pr.yml
- run: mcp-scan scan . --mode fast --fail-on high

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
# .github/workflows/release.yml
- run: mcp-scan scan . --mode deep --output evidence

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