Dependency Vulnerabilities
Table of Contents
Understanding Dependency Vulnerabilities
When you build backend applications, you almost always depend on external libraries and frameworks. These dependencies save you time, but they also import other people's code and, with it, other people's bugs and security issues.
This chapter explains how dependency vulnerabilities appear, why they are dangerous for a backend, and how to manage them in practice.
What Are Dependencies?
A dependency is any external package, library, or framework that your project needs in order to run.
Common types of dependencies for a backend:
| Type | Example for Python backends |
|---|---|
| Web framework | FastAPI, Django, Flask |
| Database drivers | psycopg2, asyncpg, mysqlclient |
| ORMs | SQLAlchemy, Tortoise ORM |
| Utility libraries | requests, httpx, pydantic, python-jose |
| Background workers | celery, rq |
| Cloud SDKs | boto3, google-cloud-storage |
| Testing tools | pytest, faker |
Each dependency often has its own dependencies. This creates a dependency tree, also called the dependency graph.
Example requirements.txt:
fastapi==0.115.0
uvicorn[standard]==0.30.0
sqlalchemy==2.0.34
psycopg2-binary==2.9.9
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4Installing these packages will pull in many more packages automatically. Any one of them can contain a vulnerability.
What Are Dependency Vulnerabilities?
A dependency vulnerability is a security weakness in one of the external packages you use. If your application uses a vulnerable version of a package, then your app is vulnerable too, even if your own code is perfect.
Typical examples:
- A HTTP client library that is vulnerable to SSRF (server-side request forgery)
- A JSON Web Token library that accepts invalid signatures
- A ZIP file parser that allows directory traversal (writing files outside the target directory)
- A templating engine that allows remote code execution when misused
A backend is only as secure as its weakest dependency version.
Even if the vulnerability is in a rarely used part of the library, an attacker may find a way to trigger it.
Sources of Dependency Vulnerabilities
- Coding mistakes in the library itself
For example, forgetting to validate input or incorrectly using cryptography primitives. - Unsafe defaults
A library may ship with insecure default configuration, such as disabled certificate verification. - Outdated algorithms or protocols
For example, using MD5 or SHA1 for security, or old SSL/TLS versions. - Transitive vulnerabilities
A direct dependency depends on another vulnerable package. You do not see that package in yourrequirements.txt, but it is still in your environment. - Malicious packages
Sometimes an attacker uploads a package that looks similar to a popular one and contains malware.
Why Dependency Vulnerabilities Matter in Backends
Backends often:
- Process untrusted data from the internet
- Access sensitive data (databases, secrets, user data)
- Run on servers that are always online
That combination makes dependency vulnerabilities particularly dangerous.
Common Risk Scenarios
- Deserialization vulnerabilities
A library that deserializes user input (for example pickles, XML, YAML) might allow attackers to execute code. - Authentication and cryptography bugs
If a dependency that handles tokens, passwords, or encryption is flawed, attacker may bypass authentication or decrypt data. - Template injection
An unsafe templating engine can allow user input to be executed as code if not properly sandboxed. - File handling issues
Libraries that handle ZIP, TAR, image files, or PDFs may allow directory traversal or arbitrary file writes. - Command injection through wrappers
A library that wraps shell commands incorrectly might let user input escape and run arbitrary commands on your server.
How Vulnerabilities Get Discovered and Disclosed
Most dependency vulnerabilities are discovered by:
- Security researchers
- The library maintainers
- Bug bounty programs
- Automated scanners
Then they are usually:
- Assigned a CVE ID (Common Vulnerabilities and Exposures)
- Published in security advisories and databases
- Fixed in a new version of the package
Some key databases and sources:
| Source | Description |
|---|---|
| CVE database | Standardized IDs for vulnerabilities |
| NVD (NIST) | National Vulnerability Database with CVE details |
| GitHub Security Advisories | Package specific advisories |
| PyPI advisories | Security information for Python packages |
| Vendor blogs | Posts from framework or library maintainers |
Upgrading to the fixed version is usually the main mitigation.
Direct vs Transitive Dependencies
Understanding direct and transitive dependencies is important for assessing risk.
| Type | Description | Example |
|---|---|---|
| Direct dependency | You explicitly list it in your project | fastapi in requirements.txt |
| Transitive dependency | Installed because a direct dependency needs it | starlette installed via fastapi |
Example: dependency tree for fastapi (simplified):
fastapi==0.115.0
ββ starlette==0.37.0
ββ pydantic==2.9.0
ββ annotated-types==0.7.0
If there is a vulnerability in starlette or annotated-types, your application is still affected, even though you never added them manually.
You are responsible for vulnerabilities in all dependencies, including transitive ones.
Example: A Realistic Vulnerability Scenario
Imagine you use a JSON Web Token (JWT) library to verify access tokens:
from jose import jwt
def verify_token(token: str, secret: str) -> dict:
payload = jwt.decode(token, secret, algorithms=["HS256"])
return payloadNow suppose a vulnerability is discovered in this library:
- Under some conditions, tokens using
nonealgorithm are accepted without verification. - This is published as a CVE and fixed in version
3.4.0. - You still use version
3.2.0because you have not updated in a year.
An attacker can craft a token that bypasses signature validation and gets admin access.
Mitigation steps:
- Check if your version is affected.
- Read the advisory to understand impact and exploitation conditions.
- Upgrade to a safe version (for example
3.4.0or later). - Redeploy your backend.
- Consider revoking or rotating any long-lived tokens.
Tools for Detecting Dependency Vulnerabilities
You do not want to manually track security news for every package. Instead, use automated tools.
Ecosystem Specific Tools
For Python, typical tools are:
| Tool | Purpose |
|---|---|
pip-audit | Scan your installed packages or requirements for known vulnerabilities using Python advisory DB |
safety | Check requirements against a vulnerability database |
pip-tools | Helps manage pinned dependencies, not a scanner but essential for control |
Example usage of pip-audit:
pip install pip-audit
pip-auditSample output:
Found 1 known vulnerability in 1 package
Name Version ID Fix Versions
------- ------- --------- ------------
requests 2.19.1 PYSEC-2018-99 >=2.20.0
You then update requests to at least 2.20.0.
GitHub and GitLab Dependabot and Security Scans
If your code is hosted on GitHub or GitLab, you can enable:
- Automatic detection of vulnerable dependencies
- Security alerts and automated pull requests that bump versions
- Dependency graph visualization
Typical workflow:
- Dependabot opens a pull request:
- "Update
urllib3from 1.26.3 to 1.26.19" - Links to CVEs and advisories
- You review change, run tests, and merge if safe.
Good Practices for Managing Dependencies
Dependency management is one of the most important parts of backend security. Adopt these practices from the start.
1. Pin Versions
Always pin exact versions in your production dependencies instead of using unbounded ranges.
Bad:
fastapi
sqlalchemy>=2.0Better:
fastapi==0.115.0
sqlalchemy==2.0.34This ensures:
- Reproducible environments
- You know exactly what versions are running in production
- Upgrades are explicit and reviewable
Use a separate file for development and testing dependencies, like:
requirements.txtfor applicationrequirements-dev.txtfor tools such aspytest,black,mypy
2. Separate Direct Dependencies from Lock Files
A common pattern:
- Maintain a short
requirements.inorpyproject.tomlwith direct dependencies. - Generate a full, pinned
requirements.txtor lock file that includes all transitive dependencies.
Tools like pip-tools or poetry can help with this.
Example with pip-tools:
requirements.in:
fastapi
uvicorn[standard]
sqlalchemy
psycopg2-binaryGenerate the full list:
pip-compile requirements.in
This produces a requirements.txt with all versions pinned, including transitive ones.
Always deploy with a fully pinned dependency list or lock file. Never deploy with floating versions in production.
3. Regular Dependency Updates
Do not wait years between updates. Instead:
- Schedule regular updates, for example every 1 to 4 weeks.
- Run security scans at least weekly, ideally on each commit.
- Prefer small, frequent updates rather than huge one-time upgrades.
A simple practice:
- Once a week, run:
pip-audit- If vulnerabilities are found, upgrade the affected packages.
- Run tests and deploy.
4. Apply Security Patches Quickly
Treat security fixes differently from ordinary feature updates:
- If a security advisory affects a package you use, prioritize updating.
- Even if it is a minor version bump, run tests and deploy as soon as possible.
- For critical vulnerabilities, deploy a hotfix.
Many teams classify updates:
| Type | Response time target |
|---|---|
| Critical security | Same day or next business day |
| High severity | Within a few days |
| Medium / low | In regular scheduled update window |
5. Avoid Unmaintained Packages
Before adding a new dependency, check:
- Last release date
- Number of open issues and pull requests
- Activity in the issue tracker
- Whether security issues are addressed promptly
If a package has not been updated in many years, and there are open security issues, consider alternatives.
6. Minimize Dependencies
Each dependency is potential attack surface. Ask:
- Do I really need this package?
- Can I implement this with the standard library?
- Am I using only 5 percent of a huge library?
For example:
- Instead of adding a whole utility library just to parse URLs, use the standard
urllib.parse. - Instead of a massive "kitchen sink" framework, use focused libraries when possible.
Less code from others often means fewer vulnerabilities for you.
Handling Vulnerabilities When They Are Found
At some point, you will discover that your project uses a vulnerable dependency. Have a simple playbook.
Step 1: Confirm and Assess
- Check if your project actually uses the vulnerable code path.
- Read the advisory details:
- What versions are affected
- What configurations are required for exploitation
- Impact: data leak, code execution, privilege escalation, etc.
Even if you think it is unlikely, treat it as serious. Attackers might find exotic ways to reach vulnerable code.
Step 2: Update to a Fixed Version
- Update to the minimum safe version mentioned in the advisory.
- Prefer a minimal bump, for example from
1.2.3to1.2.5, instead of jumping across many major versions during a quick patch. - Update your pinned dependency file and lock file.
Example:
pip install "requests>=2.31.0" --upgrade
pip freeze > requirements.txtOr with a tool:
pip-compile --upgrade-package requests requirements.inStep 3: Test and Deploy
- Run your unit and integration tests.
- If the change is small and tests pass, deploy a hotfix.
- If the update is large, consider more thorough regression testing.
Step 4: Post-Update Checks
Depending on the severity:
- Consider invalidating sessions or tokens if the vulnerability relates to authentication.
- Rotate secrets (API keys, database passwords) if the vulnerability might expose them.
- Review logs for suspicious activity near the time of possible exploitation.
Preventing Malicious Package Attacks
Some attacks are not bugs but intentionally malicious packages. Common patterns include:
- Typo-squatting:
reqeustsinstead ofrequests - Brand-jacking: a package with the same name as another ecosystem
- Compromised maintainers: attackers publish a malicious update in a legitimate project
Defenses:
- Double check package names
Verify you are installing the correct package, especially if you typed it by hand. - Check the source repository
Confirm the package points to a legitimate GitHub or similar repository. - Avoid copy pasting random
pip installcommands from unknown sources
Prefer official documentation. - Pin versions and review updates
If a popular package suddenly adds suspicious code, a code review of changes can catch it.
Dependency Vulnerabilities in CI/CD Pipelines
Your CI/CD system is a good place to integrate dependency security checks.
Typical pipeline steps:
- Install dependencies from the lock file.
- Run unit tests.
- Run security scans:
pip-auditorsafety- Optional: secret scanning, static analysis
- Fail the pipeline if:
- There are high or critical known vulnerabilities.
- Require a human to review and approve version changes.
This leads to a consistent rule:
Never deploy a backend that your CI pipeline reports as containing high or critical dependency vulnerabilities.
If you must deploy a temporary exception, document it and track it in your issue system.
Dependency Vulnerabilities and Containers
If you use Docker (covered later in the course), you also need to think about vulnerabilities in:
- The base image (for example the OS packages)
- Python packages inside the image
- System level libraries (OpenSSL, libxml2, etc.)
Common practices:
- Use official, minimal base images, for example
python:3.12-slim. - Regularly rebuild images with updated base images, not just update Python packages.
- Run container image scanners that check OS level vulnerabilities (for example Trivy, Grype).
Your dependency stack becomes:
| Layer | Examples |
|---|---|
| Application code | Your Python files |
| Python dependencies | FastAPI, SQLAlchemy, requests |
| System libraries | OpenSSL, libc, zlib |
| OS packages | Debian/Ubuntu/Alpine base image |
Vulnerabilities can exist at any layer.
Summary
Dependency vulnerabilities are one of the main ways a backend becomes insecure. You must:
- Understand that all direct and transitive dependencies can be vulnerable.
- Pin versions and use lock files for reproducible environments.
- Use automated tools and CI to detect vulnerabilities early.
- Update and patch promptly when advisories are published.
- Minimize and choose dependencies carefully.
- Integrate security scanning into deployment pipelines and container builds.
Managing dependencies well is a core responsibility of a backend developer, not just a task for security specialists.
Views: 8
KAHIBARO