A Python-based code scanning tool for AI/LLM-specific vulnerabilities
Project description
LLM Security Scanner
A Python-based code scanning tool that uses the Semgrep Python SDK to detect AI/LLM-specific vulnerabilities. This tool is designed to run in both GitHub Actions (headless CI) and as the scanning engine behind a VS Code extension.
Components
- Scanner (
llm_scan/): Python package for scanning code - VS Code Extension (
vscode-extension/): IDE integration - Visual Studio Extension (
visual-studio-extension/): Full IDE integration - Backend API (
backend/): Node.js server with MySQL for storing scan results (similar to Semgrep dashboard)
Features
- Semgrep Python SDK Integration: Uses Semgrep's Python APIs directly (no CLI subprocess calls)
- Multi-language Support: Architecture supports Python, JavaScript, and TypeScript (initial rules are Python-focused)
- Offline-first: All scanning runs without network access
- Multiple Output Formats: SARIF (for GitHub Code Scanning), JSON (for VS Code), and human-readable console output
- Extensible Rule System: Easy to add new rule packs and vulnerability patterns
- Performance Optimized: Incremental scanning, respects .gitignore, configurable include/exclude patterns
Installation
From PyPI (Recommended)
pip install trusys-llm-scan
This will install the trusys-llm-scan command-line tool and all dependencies.
From Source
For development or if you need the latest version:
# Clone the repository
git clone https://github.com/your-org/code-scan2.git
cd code-scan2
# Install dependencies
pip install semgrep requests
# Install in development mode
pip install -e .
Quick Start
Command Line Usage
After installation, you can use the trusys-llm-scan command:
# Show installed version
trusys-llm-scan --version
# or
trusys-llm-scan -V
# Check PyPI for a newer version
trusys-llm-scan --check-updates
# Scan current directory
trusys-llm-scan . --format console
# Or use as a Python module
python -m llm_scan.runner . --format console
# Scan specific paths with SARIF output
python -m llm_scan.runner \
src/ tests/ \
--rules llm_scan/rules/python \
--format sarif \
--out results.sarif \
--exclude 'tests/**' \
--exclude '**/__pycache__/**'
# Filter by severity
python -m llm_scan.runner \
. \
--severity critical high \
--format json \
--out results.json
# Enable AI-based false positive filtering
python -m llm_scan.runner \
. \
--enable-ai-filter \
--ai-provider openai \
--ai-model gpt-4 \
--format console
# AI filtering with specific rules only
python -m llm_scan.runner \
. \
--enable-ai-filter \
--ai-analyze-rules openai-prompt-injection-direct \
--ai-analyze-rules openai-excessive-agency-file-deletion \
--format console
Python Library Usage
from llm_scan.config import ScanConfig
from llm_scan.runner import run_scan
from llm_scan.models import Severity
config = ScanConfig(
paths=["src/"],
rules_dir="llm_scan/rules/python",
include_patterns=["*.py"],
exclude_patterns=["tests/**"],
severity_filter=[Severity.CRITICAL, Severity.HIGH],
output_format="json",
)
result = run_scan(config)
for finding in result.findings:
print(f"{finding.severity}: {finding.message}")
VS Code Extension Integration
from llm_scan.runner import run_scan_for_vscode
from llm_scan.models import ScanRequest, Severity
request = ScanRequest(
paths=["/workspace/src"],
rules_dir="/workspace/llm_scan/rules/python",
include_patterns=["*.py"],
severity_filter=[Severity.CRITICAL, Severity.HIGH],
output_format="json"
)
response = run_scan_for_vscode(request)
if response.success:
# Process response.result.findings
pass
See vscode-integration.md for the complete integration contract.
AI-Powered False Positive Filtering
The scanner includes an optional AI-based false positive filter that uses LLM APIs to analyze Semgrep findings and filter out false positives. This feature helps reduce noise and improve the accuracy of security findings.
How It Works
- Semgrep Scan: First, Semgrep runs and finds potential vulnerabilities
- AI Analysis: Selected findings are analyzed by an AI model (OpenAI GPT-4 or Anthropic Claude)
- Context-Aware Filtering: AI considers code context, sanitization, framework protections, and exploitability
- Confidence-Based Filtering: Only high-confidence false positives are filtered (configurable threshold)
Usage
# Enable AI filtering with OpenAI
python -m llm_scan.runner . \
--enable-ai-filter \
--ai-provider openai \
--ai-model gpt-4 \
--ai-confidence-threshold 0.7
# Use Anthropic Claude
python -m llm_scan.runner . \
--enable-ai-filter \
--ai-provider anthropic \
--ai-model claude-3-opus-20240229
# Analyze only specific rules (cost optimization)
python -m llm_scan.runner . \
--enable-ai-filter \
--ai-analyze-rules openai-prompt-injection-direct \
--ai-analyze-rules openai-excessive-agency-file-deletion
Configuration Options
--enable-ai-filter: Enable AI filtering--ai-provider: Choose provider (openaioranthropic)--ai-model: Model name (e.g.,gpt-4,gpt-3.5-turbo,claude-3-opus-20240229)--ai-api-key: API key (or use environment variables)--ai-confidence-threshold: Confidence threshold (0.0-1.0, default: 0.7)--ai-analyze-rules: Specific rule IDs to analyze (can be used multiple times)
Cost Considerations
- AI filtering is optional and disabled by default
- Only analyzes findings with
confidence: "medium"or"low"by default - Uses caching to avoid re-analyzing identical code patterns
- Processes findings in batches for efficiency
- Estimated cost: ~$0.01-0.10 per analyzed finding
When to Use AI Filtering
- Recommended for: Medium/low confidence rules, complex patterns, reducing false positives
- Not needed for: High confidence rules, simple patterns, cost-sensitive environments
- Best practice: Start with specific rules (
--ai-analyze-rules) to test effectiveness
Detected Vulnerabilities
The scanner detects vulnerabilities based on the OWASP Top 10 for LLM Applications:
OWASP LLM Top 10 Coverage
- LLM01: Prompt Injection - Unsanitized user input in prompts
- LLM02: Insecure Output Handling - LLM output used unsafely (code/command injection, XSS)
- LLM03: Training Data Poisoning - Training data from untrusted sources
- LLM04: Model Denial of Service - Resource exhaustion through excessive tokens/requests
- LLM05: Supply Chain Vulnerabilities - Untrusted models, libraries, or plugins
- LLM06: Sensitive Information Disclosure - Secrets/PII in prompts or responses
- LLM07: Insecure Plugin Design - Plugin execution without authorization/validation
- LLM08: Excessive Agency - LLM granted excessive permissions
- LLM09: Overreliance - Blind trust in LLM output without validation
- LLM10: Model Theft - Unauthorized model access or extraction
Core Vulnerability Patterns
- Code Injection (CWE-94): LLM output passed to
eval(),exec(), orcompile() - Command Injection (CWE-78): LLM output passed to
subprocess.run(),subprocess.call(),subprocess.Popen(), oros.system() - XSS (CWE-79): LLM output rendered in HTML without escaping
Supported LLM APIs
- OpenAI:
- Legacy API (
openai.ChatCompletion.create,openai.Completion.create) - v1 client (
OpenAI().chat.completions.create)
- Legacy API (
- Anthropic:
Anthropic().messages.create - Generic LLM wrappers:
call_llm(),.llm(),.generate(),.chat()
Rules are organized by provider in python/{provider}/generic/ directories.
Project Structure
llm_scan/
├── __init__.py
├── models.py # Data models (Finding, ScanResult, etc.)
├── config.py # Configuration management
├── runner.py # Main entry point and CLI
├── engine/
│ ├── semgrep_engine.py # Semgrep Python SDK integration
│ ├── ai_engine.py # AI-based false positive filtering
│ └── ai_providers.py # AI provider implementations (OpenAI, Anthropic)
├── utils/
│ └── code_context.py # Code context extraction for AI analysis
├── output/
│ ├── sarif.py # SARIF formatter
│ ├── json.py # JSON formatter
│ └── console.py # Console formatter
├── enrich/
│ └── uploader.py # Optional upload interface
└── rules/
└── python/ # Semgrep rule packs
├── openai/ # OpenAI-specific rules
│ ├── generic/ # Framework-agnostic OpenAI rules
│ │ ├── prompt-injection.yaml
│ │ ├── code-injection.yaml
│ │ ├── command-injection.yaml
│ │ ├── sql-injection.yaml
│ │ ├── sensitive-info-disclosure.yaml
│ │ ├── model-dos.yaml
│ │ ├── overreliance.yaml
│ │ ├── supply-chain.yaml
│ │ ├── jailbreak.yaml
│ │ ├── data-exfiltration.yaml
│ │ ├── inventory.yaml
│ │ └── taint-sources.yaml
│ ├── flask/ # Flask-specific patterns (future)
│ └── django/ # Django-specific patterns (future)
├── anthropic/ # Anthropic-specific rules
│ └── generic/
│ ├── prompt-injection.yaml
│ ├── code-injection.yaml
│ ├── inventory.yaml
│ └── taint-sources.yaml
└── [other providers]/ # Additional LLM providers
Adding New Rules
How to Add a New Rule Pack
- Create a new YAML file in the appropriate location following the structure:
llm_scan/rules/python/{llm_framework}/generic/for framework-agnostic rulesllm_scan/rules/python/{llm_framework}/{web_framework}/for web framework-specific rules- Example:
llm_scan/rules/python/openai/generic/my-new-rule.yaml
rules:
- id: my-new-rule
pattern: |
$LLM_OUTPUT = ...
dangerous_function($LLM_OUTPUT)
message: "LLM output passed to dangerous function"
severity: ERROR
languages: [python]
metadata:
category: security
cwe: "CWE-XXX"
remediation: "Fix guidance here"
paths:
include:
- "**/*.py"
- The rule will be automatically loaded when scanning with the rules directory.
How to Add a Sink Family
Sinks are dangerous functions that should not receive untrusted LLM output. To add a new sink family:
- Add patterns to existing rule files in the appropriate framework directory:
- For OpenAI:
llm_scan/rules/python/openai/generic/{vulnerability-type}.yaml - For Anthropic:
llm_scan/rules/python/anthropic/generic/{vulnerability-type}.yaml
- For OpenAI:
rules:
- id: llm-to-new-sink
patterns:
- pattern-either:
- pattern: dangerous_sink1($LLM_OUTPUT)
- pattern: dangerous_sink2($LLM_OUTPUT)
- pattern-inside: |
$LLM_RESPONSE = $LLM_CALL(...)
...
message: "LLM output flows to dangerous sink"
severity: ERROR
languages: [python]
- Update the category mapping in
llm_scan/engine/semgrep_engine.pyif needed:
CATEGORY_MAP = {
"code-injection": Category.CODE_INJECTION,
"command-injection": Category.COMMAND_INJECTION,
"llm-to-new-sink": Category.OTHER, # Add your category
}
How to Add a Provider/Source
LLM providers are sources of taint. To add support for a new LLM provider:
-
Create the provider directory structure:
mkdir -p llm_scan/rules/python/{new_provider}/generic
-
Add taint source patterns to
llm_scan/rules/python/{new_provider}/generic/taint-sources.yaml:
rules:
- id: llm-taint-new-provider
patterns:
- pattern: $CLIENT.new_provider_api(...)
- pattern-inside: |
$RESPONSE = ...
$CONTENT = $RESPONSE.output
...
$SINK($CONTENT)
message: "New provider API response content flows to dangerous sink"
severity: WARNING
languages: [python]
- Add complete taint flow rules in
llm_scan/rules/python/{new_provider}/generic/code-injection.yamlor similar:
rules:
- id: llm-to-sink-new-provider
patterns:
- pattern: |
$RESPONSE = $CLIENT.new_provider_api(...)
- pattern: |
$CONTENT = $RESPONSE.output
- pattern: |
dangerous_sink($CONTENT)
message: "New provider LLM output flows to dangerous sink"
severity: ERROR
Running Locally
Basic Scan
python -m llm_scan.runner --paths . --format console
With Custom Rules
python -m llm_scan.runner \
--paths src/ \
--rules /path/to/custom/rules \
--format json \
--out results.json
Exclude Patterns
python -m llm_scan.runner \
--paths . \
--exclude 'tests/**' \
--exclude '**/__pycache__/**' \
--exclude '.venv/**'
Running in CI
GitHub Actions
See .github/workflows/llm-scan.yml for a complete example.
The workflow:
- Checks out code
- Sets up Python
- Installs dependencies (semgrep, pytest)
- Runs the scanner
- Uploads SARIF results to GitHub Code Scanning
Other CI Systems
The scanner can be integrated into any CI system that supports Python:
pip install semgrep
pip install -e .
python -m llm_scan.runner \
--paths . \
--format sarif \
--out results.sarif
Testing
Run tests with pytest:
pip install pytest
pytest tests/
Test Fixtures
The tests/fixtures/ directory contains:
- Positive tests (
positive/): Files that should trigger findings - Negative tests (
negative/): Files that should not trigger findings
Running Specific Tests
# Test engine only
pytest tests/test_engine.py
# Test output formatters
pytest tests/test_output.py
# Test with verbose output
pytest -v tests/
Output Formats
SARIF (Static Analysis Results Interchange Format)
For GitHub Code Scanning integration:
python -m llm_scan.runner --paths . --format sarif --out results.sarif
JSON
For programmatic consumption:
python -m llm_scan.runner --paths . --format json --out results.json
Console
Human-readable output (default):
python -m llm_scan.runner --paths . --format console
Configuration
ScanConfig Options
paths: List of paths to scan (files or directories)rules_dir: Path to rules directoryinclude_patterns: Glob patterns for files to includeexclude_patterns: Glob patterns for files to excludeenabled_rules: List of rule IDs to enable (None = all)disabled_rules: List of rule IDs to disableseverity_filter: List of severity levels to includeoutput_format: "sarif", "json", or "console"output_file: Output file path (optional for console)respect_gitignore: Whether to respect .gitignore (default: True)max_target_bytes: Maximum file size to scan (default: 1MB)
Extensibility
Rule Pack Abstraction
Rule packs are defined by:
- Name
- Languages supported
- Path to rule files
- Version
- Default enabled status
Plugin Registry
Future versions will support:
- Entrypoint-based rule pack discovery
- Local folder configuration
- Remote rule pack fetching (with offline fallback)
Performance Considerations
- Incremental Scanning: Only scan changed files when possible
- File Size Limits: Large files (>1MB) are skipped by default
- Gitignore Support: Automatically excludes files in .gitignore
- Parallel Execution: Semgrep handles parallel rule execution internally
Limitations
- Initial rule set focuses on Python vulnerabilities
- JavaScript/TypeScript rules are planned but not yet implemented
- Dataflow analysis is limited to Semgrep's taint tracking capabilities
- Some complex taint flows may require multiple rule passes
Contributing
- Add test fixtures for new vulnerability patterns
- Create Semgrep YAML rules following existing patterns
- Update documentation
- Add tests for new functionality
License
[Specify your license here]
Acknowledgments
- Built on Semgrep
- Inspired by security scanning tools like CodeQL and Bandit
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file trusys_llm_scan-1.0.2.tar.gz.
File metadata
- Download URL: trusys_llm_scan-1.0.2.tar.gz
- Upload date:
- Size: 128.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e78de5b8d4d3d0a51a6a0d8b1bbc53698fe92297c8186305e87a6f91921111c0
|
|
| MD5 |
5e1f2a00adfd21e35e1e729065896ddb
|
|
| BLAKE2b-256 |
559cd115c0f538636eb2fcdaf4d8f28fc7f51ddf47954e0007a171e5c6396dec
|
File details
Details for the file trusys_llm_scan-1.0.2-py3-none-any.whl.
File metadata
- Download URL: trusys_llm_scan-1.0.2-py3-none-any.whl
- Upload date:
- Size: 216.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7337a0751555e3bd63c38ea1c2124789af71758ecebb35078fd6ce4669ee67f9
|
|
| MD5 |
c3fc9d8d9bc8251f356b2b7d6b9cc308
|
|
| BLAKE2b-256 |
af1c7a490bc3d4bf4001aa5e1c4135cb7a70c187071737cd0c52696cf7d82413
|