The most surprising thing about Snyk’s Python dependency scanning is how little it cares about your installed Python packages.
Let’s see Snyk in action. Imagine you have a project with a pyproject.toml file managed by Poetry.
# pyproject.toml
[tool.poetry]
name = "my-awesome-app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.9"
requests = "^2.28.1"
flask = "^2.2.2"
[tool.poetry.group.dev.dependencies]
pytest = "^7.2.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
And a simple src/main.py:
# src/main.py
import requests
import flask
def main():
print(f"Using requests version: {requests.__version__}")
print(f"Using Flask version: {flask.__version__}")
if __name__ == "__main__":
main()
When you run poetry install and then snyk test, Snyk doesn’t look at the site-packages directory. It reads your pyproject.toml (or Pipfile for Pipenv, or requirements.txt for pip, though it prefers the others) and uses that to determine your project’s dependencies. It then queries its own vulnerability database for known issues associated with those declared dependency versions.
Here’s the mental model: Snyk is fundamentally a declarative security scanner for your code. It trusts that your build tool (Poetry, Pipenv, or even pip freeze if you’re using that) accurately reflects what should be in your environment. Your pyproject.toml is the source of truth. Snyk’s job is to tell you if the intended dependencies have known vulnerabilities.
When you run snyk test, Snyk performs these steps:
- Identify Dependency Manifest: It looks for
pyproject.toml,Pipfile, orrequirements.txtin your project root. - Parse Dependencies: It parses the identified file to extract your project’s direct and transitive dependencies, along with their specified versions. For Poetry, this means reading
[tool.poetry.dependencies]and[tool.poetry.group.dev.dependencies]. For Pipenv, it’s[packages]and[dev-packages]inPipfile. Forrequirements.txt, it’s a line-by-line parse. - Generate Dependency Tree: Snyk constructs a representation of your project’s dependency graph. This is crucial for understanding how vulnerabilities in one package can affect others.
- Query Vulnerability Database: Snyk sends this dependency graph to its vulnerability intelligence platform.
- Report Findings: The platform compares your dependencies against its database of known vulnerabilities and returns any matches, along with remediation advice.
The exact levers you control are the versions specified in your manifest files. For instance, if Snyk reports a vulnerability in requests==2.28.1, you’d update your pyproject.toml:
# pyproject.toml (updated)
[tool.poetry.dependencies]
python = "^3.9"
requests = "^2.29.0" # Updated version
flask = "^2.2.2"
Then, you’d run poetry update requests (or poetry update if you want to update all dependencies) and snyk test again.
What most people don’t realize is that Snyk’s strength lies in its declarative nature. It’s not a runtime security agent monitoring your application’s behavior. It’s a build-time tool that tells you if the ingredients you say you’re using are safe. This means if your requirements.txt has requests==2.28.1 but your pip freeze output (which reflects the actually installed packages) shows requests==2.27.0 due to some external intervention or a broken pip install process, Snyk will still report on 2.28.1 because that’s what it sees in the manifest. The discrepancy between your manifest and your installed packages is a different problem altogether, and one that Snyk doesn’t directly address.
The next logical step after ensuring your dependencies are secure is to integrate Snyk into your CI/CD pipeline to catch vulnerabilities before they reach production.