If you're adding static analysis to a Python project, you've probably narrowed it down to three tools: Bandit for security, Vulture for dead code, or something that does both. Here's how they actually compare.
What Each Tool Does
Before comparing, it's important to understand that these tools solve different problems:
| Bandit | Vulture | Skylos | |
|---|---|---|---|
| Primary focus | Security issue candidates | Dead code detection | Security and dead-code candidates + quality metrics |
| Detection method | AST pattern matching | AST name resolution | AST + local taint propagation + framework-aware liveness |
| Languages | Python only | Python only | Python (deepest), TypeScript/JavaScript and Java: dead code, security and quality. PHP, Rust, Dart: dead code and security. Go: dead code and security need the separate skylos-go engine (in the Docker image and GitHub Action, not the PyPI package). C# partial. Kotlin: dead code only. C++: unused file-local functions only. Shell: security only. |
| CI/CD integration | Official Action, pre-commit, SARIF | Documented Action and pre-commit | Generated workflow (skylos cicd init) |
| PR feedback | Action output or SARIF in Code Scanning | Action output | GitHub annotations and inline review comments |
| Framework handling | Framework-specific security plugins | Configurable decorator ignores and whitelists | Built-in liveness models for Django, FastAPI, Flask, Pydantic, pytest, and others |
The short version: Bandit reports Python security issue candidates. Vulture reports dead-code candidates. Skylos covers both categories and adds code-quality metrics, secrets detection, and CI/CD integration.
Detection Coverage
Security Scanning
Bandit covers common Python security issue types:
- Hardcoded passwords, SQL injection (pattern-based), shell injection
- Weak crypto (MD5, SHA1), unsafe YAML, pickle deserialization
- No taint analysis — can't trace data flow from user input to sink
Skylos covers common Python security issue types and includes dataflow-oriented checks:
- Taint analysis: Propagates taint through local assignments and expressions and conservatively treats ordinary function parameters as untrusted when checking most dangerous sinks (outbound HTTP URLs are the exception)
- SSRF detection (SKY-D216): Flags potentially tainted URLs passed to HTTP clients
- ORM injection (SKY-D217): Catches
sqlalchemy.text(),pandas.read_sql(), Django.raw()with interpolated strings - XSS patterns (SKY-D226-D228):
mark_safe(), template|safefilter, HTML string building - JWT vulnerabilities (SKY-D232):
algorithms=['none'],verify=False - MCP server security (SKY-D240-D244): Tool description poisoning, unauthenticated transport
- AI-defect checks: Evidence-backed phantom calls, dependency hallucinations, test gaps, and change risks under
--ai-defects - Other risky patterns: Hardcoded credentials, disabled security controls, and insecure randomness
Vulture does not scan for security issues.
Dead Code Detection
Vulture is the established tool here. It finds:
- Unused functions, classes, imports, variables
- Unreachable code after return/break/continue
- Confidence scoring (60-100%)
- Whitelist support
Skylos covers the same ground with additions:
- Framework-aware dead code (won't flag
@app.route,pytest.fixture,@admin.register, Pydanticmodel_validator) - Transitive dead code propagation (if A calls B and A is dead, B might be too)
- Cleanup: preview removals with
skylos clean . --dry-runand apply them with--apply(Python imports and functions), or review interactively withskylos . -i
Bandit does not detect dead code.
Code Quality
Neither provides the broader complexity and architecture metrics below.
Skylos includes:
- Cyclomatic complexity (SKY-Q301)
- Deep nesting (SKY-Q302)
- Argument count / function length (SKY-C303, SKY-C304)
- God class detection (SKY-Q501)
- Coupling and cohesion metrics (SKY-Q701, Q702)
- Async blocking calls (SKY-Q401)
- Advisory architecture metrics (distance from main sequence, SKY-Q802/Q803; not part of the gate by default)
How the findings differ
This is where the differences matter most in practice.
Bandit reports shell use and adjusts severity
Bandit's B602 plugin reports shell=True and adjusts severity based on whether the command appears static or computed:
# Bandit B602: LOW severity for a simple static command
subprocess.call("ls -la", shell=True) # B602: subprocess_popen_with_shell_equals_true
# Bandit B602: HIGH severity for a computed command
cmd = request.args.get("cmd")
subprocess.call(cmd, shell=True)
That is useful broad coverage. Bandit does not trace the request value to the subprocess sink as a separate source-to-sink command-injection result.
Vulture provides configuration for indirect uses
Framework callbacks are a common challenge for dead-code analysis:
@app.route("/api/users") # Flask route — called by HTTP, not Python
def get_users():
return jsonify(users)
@pytest.fixture # Called by pytest, not your code
def db_connection():
return create_connection()
Vulture documents --ignore-decorators "@app.route" for Flask and also supports ignore lists and generated whitelists. Projects still need to configure indirect-use patterns that their framework or plugin system introduces.
Skylos's Approach
Skylos uses framework-aware visitors that understand Django, Flask, FastAPI, Pydantic, and pytest patterns. Its Python security rules also propagate taint through local assignments and expressions. For most sinks other than outbound HTTP requests, they conservatively treat ordinary function parameters as untrusted, so review the evidence rather than assuming every result proves an end-to-end request flow:
# Skylos reports shell use for both forms (SKY-D209)
subprocess.call("ls -la", shell=True) # SKY-D209
# A directly recognized request value adds a command-injection result
cmd = request.args.get("cmd")
subprocess.call(cmd, shell=True) # SKY-D209 + SKY-D212
This direct example was reproduced with Skylos 4.47.1: the static command produced SKY-D209, while the request value assigned in the same function produced both SKY-D209 and CRITICAL SKY-D212. This is not a claim of interprocedural call-chain tracing.
Speed
All three tools are usually fast enough for local and CI use on small-to-medium projects. Exact timing depends on file count, rule configuration, import graph shape, framework usage, hardware, and which analyzers are enabled.
The practical rule: run the tool on your repository and measure it in your CI environment. If the scan is meant to block PRs, benchmark the exact command you plan to enforce rather than relying on generic timing tables.
CI/CD Integration
Bandit
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- run: python -m pip install bandit==1.9.4
- run: bandit -r src/
This pins the current scanner and preserves Bandit's normal nonzero finding exit. Bandit also supports severity and confidence thresholds, baselines, and SARIF. Its official Action uploads SARIF, but its current v1 runtime installs an older Python-3.8-compatible Bandit and its wrapper does not fail the step on Bandit's finding exit code; account for that if you use the Action instead.
Vulture
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: gtkacz/vulture-action@1.0.0
with:
src: "src/"
args: "--min-confidence 80"
Vulture also documents pre-commit integration. Its nonzero finding exit can fail the Action step; it does not generate Skylos's separate configurable gate and PR-review workflow.
Skylos
# Generates a pull-request gate; --no-upload skips the Skylos Cloud job
skylos cicd init --no-upload
It generates a GitHub Actions workflow with:
- Security + quality scanning on changed lines
- GitHub annotations and PR review comments
- Quality gate (configurable thresholds)
- A "Skylos Done" job that runs your tests, when a pytest project is found
skylos cicd init writes a GitHub Actions pull-request gate that needs no Skylos account or API key. By default it also adds a "Skylos Cloud upload" job for pushes to your default branch, which fails until the Skylos GitHub App is installed on the repository. Without Cloud, run skylos cicd init --no-upload.
When to Use What
Use Bandit if:
- You only need security scanning
- You want a configurable Python security plugin system
- You already have a dead code tool
- You want severity/confidence thresholds, baselines, or SARIF output
Use Vulture if:
- You only need dead code detection
- You value confidence scoring
- You're comfortable configuring indirect framework uses through decorator ignores, name ignores, or whitelists
Use Skylos if:
- You want security + dead code + quality in one tool
- You use Django, Flask, FastAPI, or Pydantic (framework awareness matters)
- You want CI/CD integration with PR comments out of the box
- You want local taint evidence alongside pattern findings
- You want to detect AI-generated code problems
Use Bandit + Vulture if:
- You want established tools with large communities
- You're comfortable managing two tools and their configurations
- You don't need quality metrics or PR comments
Quick Start Comparison
Bandit
pip install bandit
bandit -r src/
Vulture
pip install vulture
vulture src/
Skylos
pip install skylos
skylos . --danger --quality --format rich
Final Thoughts
There's no single "best" tool. Bandit focuses on Python security plugins. Vulture focuses on dead-code candidates and confidence. Skylos combines security, dead code, quality, and generated CI workflows; teams should compare the actual findings on their own repository.
Pick the scope you need, run it on a representative repository, and measure which findings your team would actually act on before making the job blocking.
Facts were rechecked on October 9, 2026 against the Bandit documentation, Bandit B602 behavior, Bandit's GitHub Action, Vulture documentation, and Skylos 4.47.1.
Related
- Deadcode vs Vulture vs Skylos
- Best Python SAST Tools in 2026
- Semgrep vs Skylos for Python
- Bandit vs CodeQL vs Semgrep for Python
- How to Detect Dead Code in Python
- How to Secure GitHub Actions for Python Repos
- Python Security Scanner for GitHub Actions
All tools mentioned are open source. Bandit | Vulture | Skylos | Skylos Docs | Install