Dependency Security and Vulnerability Scanning in Python: Protecting Your Supply Chain
Your Python application depends on hundreds of packages. You didn’t write most of them. You probably don’t review their code. Yet you trust them with your data, your users’ data, and your infrastructure.
This is the supply chain risk. A vulnerability in a single dependency can compromise your entire application. The 2023 PyPI supply chain attacks demonstrated this reality: attackers uploaded malicious packages with names similar to popular libraries, hoping developers would install them by mistake.
Dependency security isn’t optional. It’s essential. This guide shows you how to implement it.
Understanding Dependency Vulnerabilities
What is a Dependency Vulnerability?
A dependency vulnerability is a security flaw in a package your application uses. It might be:
- Code injection: Attackers can execute arbitrary code
- Data exposure: Sensitive data is leaked
- Denial of service: Attackers can crash your application
- Authentication bypass: Attackers can bypass security controls
Direct vs Transitive Dependencies
Direct dependencies are packages you explicitly install:
# requirements.txt
flask==2.3.2
requests==2.31.0
Transitive dependencies are packages that your dependencies depend on:
flask==2.3.2
โโโ werkzeug==2.3.6
โโโ jinja2==3.1.2
โ โโโ markupsafe==2.1.1
โโโ click==8.1.3
A vulnerability in markupsafe (a transitive dependency) affects your application even though you didn’t directly install it.
Why Python Projects Are Vulnerable
- Large dependency trees: Average Python project has 50+ transitive dependencies
- Rapid development: Security patches may lag behind vulnerability discovery
- Ecosystem scale: PyPI has 500,000+ packages; not all are well-maintained
- Supply chain attacks: Attackers target popular packages
Vulnerability Databases
Where Vulnerabilities Are Tracked
CVE (Common Vulnerabilities and Exposures)
- Official vulnerability database
- Assigned by MITRE
- Example: CVE-2023-12345
GitHub Advisory Database
- Aggregates vulnerabilities from multiple sources
- Includes Python-specific advisories
- Integrated with GitHub’s dependency scanning
PyPI Advisory Database
- Python-specific vulnerability database
- Maintained by Python Packaging Authority
- Integrated into pip-audit
NVD (National Vulnerability Database)
- US government vulnerability database
- Comprehensive but not Python-specific
Python Dependency Scanning Tools
Tool Comparison
| Tool | Type | Speed | Accuracy | Integration | Cost |
|---|---|---|---|---|---|
| pip-audit | CLI | Fast | High | Pre-commit, CI/CD | Free |
| Safety | CLI | Fast | Medium | Pre-commit, CI/CD | Free/Paid |
| Snyk | CLI/Web | Medium | High | GitHub, GitLab, CI/CD | Free/Paid |
| Bandit | CLI | Fast | Medium | Pre-commit, CI/CD | Free |
| OWASP Dependency-Check | CLI | Slow | High | CI/CD | Free |
pip-audit: The Modern Standard
pip-audit is the official Python packaging tool for vulnerability scanning:
# Install pip-audit
pip install pip-audit
# Scan current environment
pip-audit
# Scan requirements file
pip-audit --requirements requirements.txt
# Scan with detailed output
pip-audit --desc
# Generate JSON report
pip-audit --format json > report.json
# Ignore specific vulnerabilities
pip-audit --ignore-ids PYSEC-2023-12345
Safety: Comprehensive Scanning
Safety checks against a vulnerability database:
# Install Safety
pip install safety
# Scan current environment
safety check
# Scan requirements file
safety check --file requirements.txt
# Generate JSON report
safety check --json > report.json
# Check for insecure packages
safety check --insecure
Snyk: Enterprise-Grade Scanning
Snyk provides comprehensive vulnerability scanning with remediation guidance:
# Install Snyk
npm install -g snyk
# Authenticate
snyk auth
# Scan project
snyk test
# Generate report
snyk test --json > report.json
# Monitor for new vulnerabilities
snyk monitor
Bandit: Code Security Scanning
Bandit scans Python code for security issues (not just dependencies):
# Install Bandit
pip install bandit
# Scan Python files
bandit -r .
# Generate JSON report
bandit -r . -f json > report.json
# Exclude test files
bandit -r . --exclude tests/
Implementing Vulnerability Scanning
Step 1: Local Scanning
Start by scanning your project locally:
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Scan for vulnerabilities
pip-audit
# Example output:
# Found 2 vulnerabilities in 1 package
#
# django 3.2.0
# PYSEC-2023-12345: SQL injection vulnerability
# PYSEC-2023-12346: Authentication bypass
Step 2: Pre-commit Hooks
Prevent vulnerable code from being committed:
# Install pre-commit
pip install pre-commit
# Create .pre-commit-config.yaml
cat > .pre-commit-config.yaml << 'EOF'
repos:
- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
rev: v1.3.1
hooks:
- id: python-safety-dependencies-check
- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
hooks:
- id: bandit
args: ['-c', '.bandit']
- repo: https://github.com/hadialqattan/pycln
rev: v2.2.2
hooks:
- id: pycln
args: [--all]
EOF
# Install pre-commit hooks
pre-commit install
# Run manually
pre-commit run --all-files
Step 3: CI/CD Integration
Automate scanning in your CI/CD pipeline:
# GitHub Actions example
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pip-audit safety bandit
- name: Run pip-audit
run: pip-audit
- name: Run Safety
run: safety check
- name: Run Bandit
run: bandit -r . -f json -o bandit-report.json
- name: Upload reports
uses: actions/upload-artifact@v3
with:
name: security-reports
path: |
bandit-report.json
# GitLab CI example
security_scan:
image: python:3.11
script:
- pip install -r requirements.txt
- pip install pip-audit safety bandit
- pip-audit
- safety check
- bandit -r . -f json -o bandit-report.json
artifacts:
reports:
dependency_scanning: bandit-report.json
Step 4: Automated Dependency Updates
Use Dependabot or Renovate to automatically update dependencies:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
reviewers:
- "security-team"
labels:
- "dependencies"
commit-message:
prefix: "chore(deps):"
Interpreting Vulnerability Reports
Understanding Severity Levels
- Critical: Immediate exploitation possible, high impact
- High: Likely exploitation, significant impact
- Medium: Possible exploitation, moderate impact
- Low: Difficult exploitation, minor impact
Example Report
Found 3 vulnerabilities in 2 packages
django 3.2.0
PYSEC-2023-12345: SQL injection in ORM
Severity: HIGH
Affected versions: <3.2.1
Fixed version: 3.2.1
Description: SQL injection vulnerability in Django ORM...
References: https://nvd.nist.gov/vuln/detail/CVE-2023-12345
requests 2.28.0
PYSEC-2023-12346: Improper certificate validation
Severity: MEDIUM
Affected versions: <2.28.1
Fixed version: 2.28.1
Description: Requests library fails to validate certificates...
Prioritization Strategy
- Critical/High severity: Fix immediately
- Medium severity: Fix within sprint
- Low severity: Track and fix in maintenance window
- Unpatched vulnerabilities: Evaluate alternatives or mitigations
Best Practices
1. Pin Dependency Versions
# Bad: No version pinning
# requirements.txt
flask
requests
django
# Good: Pinned versions
# requirements.txt
flask==2.3.2
requests==2.31.0
django==4.2.0
2. Use Lock Files
Lock files ensure reproducible builds:
# pip-tools
pip install pip-tools
pip-compile requirements.in > requirements.txt
# Poetry
poetry lock
# Pipenv
pipenv lock
3. Regular Scanning Schedule
# Weekly scan
0 0 * * 0 pip-audit
# Daily scan in CI/CD
schedule:
- cron: '0 0 * * *'
4. Maintain a Software Bill of Materials (SBOM)
# Generate SBOM with pip-audit
pip-audit --format cyclonedx > sbom.xml
# Generate SBOM with Snyk
snyk sbom --format cyclonedx > sbom.json
5. Document Vulnerability Decisions
# SECURITY.md
## Known Vulnerabilities
### PYSEC-2023-12345: SQL Injection in Django
- **Status**: Accepted Risk
- **Reason**: Vulnerability only affects admin interface, not user-facing code
- **Mitigation**: Restrict admin access to trusted users
- **Review Date**: 2024-01-01
Common Pitfalls
Pitfall 1: Alert Fatigue
Too many false positives lead to ignoring real issues:
# Bad: Scanning everything
pip-audit # Reports 50 vulnerabilities
# Good: Prioritize and filter
pip-audit --desc | grep -i "critical\|high"
Pitfall 2: Ignoring Transitive Dependencies
# Bad: Only checking direct dependencies
pip list
# Good: Understanding full dependency tree
pip-audit --desc # Shows all vulnerabilities
pipdeptree # Visualize dependency tree
Pitfall 3: Breaking Changes During Updates
# Bad: Blindly updating all packages
pip install --upgrade -r requirements.txt
# Good: Testing before updating
pip install --upgrade --dry-run -r requirements.txt
# Review changes, then update selectively
pip install --upgrade package-name==new-version
# Run tests
pytest
Pitfall 4: Not Scanning Development Dependencies
# Bad: Only scanning production dependencies
pip-audit
# Good: Scanning all dependencies
pip-audit --all
Advanced Practices
Custom Vulnerability Policies
# security_policy.py
VULNERABILITY_POLICY = {
'critical': {
'max_age_days': 0, # Fix immediately
'auto_update': True,
},
'high': {
'max_age_days': 7,
'auto_update': True,
},
'medium': {
'max_age_days': 30,
'auto_update': False,
},
'low': {
'max_age_days': 90,
'auto_update': False,
},
}
Monitoring for New Vulnerabilities
# Snyk monitoring
snyk monitor
# GitHub security alerts
# Enable in repository settings
# Email notifications
# Configure in tool settings
Incident Response
# Vulnerability Response Procedure
1. **Detection**: Automated scan detects vulnerability
2. **Assessment**: Evaluate severity and impact
3. **Notification**: Alert security team
4. **Remediation**: Update package or apply workaround
5. **Testing**: Run full test suite
6. **Deployment**: Deploy fix to production
7. **Verification**: Confirm vulnerability is resolved
8. **Documentation**: Document incident and lessons learned
Conclusion
Dependency security is not a one-time taskโit’s an ongoing process. By implementing vulnerability scanning, you protect your application, your users, and your organization.
Key takeaways:
- Understand your dependencies: Know what you’re using and why
- Scan regularly: Automate scanning in development and CI/CD
- Prioritize remediation: Focus on critical and high-severity issues
- Keep dependencies updated: Use automated tools like Dependabot
- Document decisions: Track why you accept or ignore vulnerabilities
- Test thoroughly: Ensure updates don’t break functionality
- Monitor continuously: Watch for new vulnerabilities in production
- Educate your team: Make security a shared responsibility
Dependency security is not a burdenโit’s an investment in your application’s reliability and your users’ trust. Start implementing these practices today.
Resources
- pip-audit Documentation
- Safety Documentation
- Snyk Documentation
- OWASP Dependency-Check
- Python Security Best Practices
- GitHub Advisory Database
- PyPI Advisory Database
- SBOM Specification
Comments