Every dependency you install runs with your code's full permissions ā reading your files, making network calls, doing anything your own code could do. This lesson covers the specific practices (scanning, hash verification, minimizing unnecessary dependencies) that keep that trust from becoming a liability.
1Full Trust, By Default: What a Dependency Can Actually Do
It's easy to think of a dependency as 'just some code that provides a function I call' ā but the reality is more consequential: once pip install-ed and imported, a dependency's code runs with *exactly* the same permissions as your own code, with no default sandboxing, no restricted filesystem access, no restricted network access, nothing preventing it from doing anything your own code could do. A dependency can read your environment variables (including any secrets stored there, tying directly back to the Secrets Management lesson), read arbitrary files on disk, make outbound network requests to anywhere, or execute arbitrary further code ā all without your own application code needing to explicitly grant any of that access.
This is precisely why a real, well-documented and increasingly common attack vector targets the software supply chain rather than your own code directly: compromising a popular package (through a maintainer account takeover, a malicious update slipped past review, or a deliberately similarly-named 'typosquatting' package) gives an attacker the exact same broad access your own application has, distributed automatically to every downstream project that installs it ā a single compromise reaching potentially thousands of systems, rather than needing to attack each one individually.
This doesn't mean dependencies should be avoided ā the entire modern software ecosystem depends on reusing well-tested, community-maintained code rather than reimplementing everything from scratch. It means the trust extended to every dependency (and every one of *its* dependencies, recursively, down the full tree) is a genuine, real security consideration, not merely a correctness or licensing one ā and the depth of the dependency tree doesn't reduce that responsibility: a vulnerability three levels deep in a transitive dependency you never directly named is still fully your production risk.
# When you run: pip install some-package
# 'some-package' can, when imported and used, do ANYTHING your
# own code could do -- read your filesystem, make network requests,
# access environment variables (including secrets) -- there's no
# sandboxing by default at allFull filesystem/network/environment access ā identical to your own code
2Vulnerability Scanning: Finding Known Issues Directly
pip-audit (and similar tools ā safety, and uv's own increasingly integrated scanning capabilities) checks your actual, currently-installed dependency versions against a maintained, continuously-updated database of *known*, publicly-disclosed vulnerabilities (sourced from advisories like GitHub's Security Advisory database, referenced by the GHSA-... identifiers in its output). This is a direct, mechanical, and genuinely actionable check ā not a guess or a heuristic, but a lookup against documented, real vulnerabilities specific to the exact package and version you have installed.
The output's specificity is what makes it genuinely useful rather than merely alarming: it names the exact affected package and its currently-installed version, references the specific vulnerability by its tracked identifier (letting you read the actual advisory for details on severity and exploitability), and ā critically ā states the exact fixed version to upgrade to, turning a vague 'you might have a security problem' into a concrete, actionable 'upgrade requests from 2.25.0 to 2.31.0 or later.'
Running this kind of scan regularly ā as part of CI, ideally on every dependency change and on a scheduled basis independent of code changes (since a package that was safe when installed can have a new vulnerability discovered and disclosed *after* installation, with no code change on your end at all) ā closes the loop on dependency risk: knowing about a vulnerability the moment it's disclosed and publicly tracked, rather than discovering it only when it's actually exploited.
$ pip install pip-audit
$ pip-audit
Found 2 known vulnerabilities in 1 package
Name Version ID Fix Versions
------- -------- --------------- -------------
requests 2.25.0 GHSA-abcd-1234 2.31.0Exact package, exact version, exact fix ā a directly actionable finding
3Hash Verification: Defending Against a Compromised Registry, Not Just Version Drift
The Dependency Management lesson covered lock file hashes primarily through the lens of *reproducibility* ā guaranteeing the exact same bytes install on every machine, every time. There's a second, distinctly security-focused reason those hashes matter: they defend against a compromised package registry (or a compromised connection to it) silently serving different bytes than what was originally resolved and verified, for what claims to be the same package name and version.
Without hash verification, pip install package==1.0.0 trusts that whatever bytes the registry currently serves for "package" version "1.0.0" are genuinely the same bytes that existed when that version was first published and reviewed ā an assumption that a compromised registry, a compromised CDN in front of it, or a man-in-the-middle attacker on an insecure connection could violate, serving malicious, tampered content under a legitimate, trusted-sounding package name and version number. pip install --require-hashes (and uv sync's default, built-in behavior) verifies the downloaded bytes' cryptographic hash against what's recorded in the lock file *before* installing, refusing to proceed if they don't match exactly ā catching this specific tampering scenario regardless of how convincing the tampered package's name and version claim to be.
This is a genuine defense-in-depth measure, distinct from and complementary to vulnerability scanning: scanning catches *known*, already-disclosed vulnerabilities in legitimate package versions; hash verification catches *tampering* ā a package claiming to be a specific, previously-verified version but actually containing different, unverified, potentially malicious content. Both matter, and neither substitutes for the other.
# A lock file with hashes doesn't just guarantee the SAME VERSION --
# it guarantees the EXACT SAME BYTES, verified cryptographically
# uv sync / pip install --require-hashes both verify this automatically
# Protects against: a compromised package index serving tampered
# content for a package name/version that should be unchangedCatches a compromised registry serving different bytes under a trusted name
4Step-by-Step Breakdown
A dependency's own dependency's own dependency having a known vulnerability is still YOUR production incident ā the depth of the tree doesn't reduce the responsibility.
Every installed dependency runs with your code's FULL permissions -- reading files, making network calls, anything your own code could do.
Checkpoint: Does a Python dependency run in any kind of restricted, sandboxed environment by default?
- āNo ā an installed dependency runs with your code's full permissions, with no default sandboxing or restriction
- āYes ā pip automatically sandboxes installed packages, restricting their filesystem and network access
pip-audit (or uv's built-in equivalent) scans your installed dependencies against a database of KNOWN vulnerabilities -- a direct, actionable check.
Checkpoint: What does pip-audit's output actually tell you, beyond just "there is a vulnerability somewhere"?
- āThe specific package, its currently-installed version, the specific vulnerability ID, AND the exact version that fixes it
- āOnly a generic warning that "some dependency" might have an issue, without further detail
Lock file hash verification (from the Dependency Management lesson) also defends against a compromised package registry silently serving different bytes than what you originally resolved.
That completes Python Security ā secrets, safe file handling, secure serialization, pickle-specific risks, and now dependency security. Next, Python Developer Tools covers the tooling ecosystem that helps enforce all of this automatically.
Confirm Real Dependency Permissions. Finish dependency_is_sandboxed_by_default(): every installed dependency runs with full permissions.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Best Practices
Run a dependency vulnerability scanner (pip-audit or equivalent) regularly in CI, not just at initial dependency-adding time
A package safe when first installed can have a new vulnerability disclosed later with no code change on your end ā scanning on a schedule, not just at install time, catches vulnerabilities discovered after the fact.
Use a lock file with hash verification (uv sync, pip --require-hashes) for every install, not just for reproducibility
Hash verification is also a security control, defending against a compromised registry silently serving tampered bytes under a legitimate package name and version.
Frequent Bugs
Treating dependency scanning as a one-time check performed only when a dependency is first added, missing vulnerabilities disclosed later against already-installed, unchanged dependency versions.
Run vulnerability scanning on a recurring schedule in CI (not just triggered by dependency changes), since new vulnerabilities in existing, already-installed versions are disclosed continuously, independent of your own code changes.
Real-World Examples
Integrating Dependency Vulnerability Scanning Into CI
A team wants their CI pipeline to automatically catch known vulnerabilities in their dependency tree, both on every pull request and on a recurring schedule to catch newly disclosed issues.
# .github/workflows/security.yml (conceptual)
# on: [pull_request, schedule: cron '0 6 * * *']
# steps:
# - run: pip install pip-audit
# - run: pip-audit --require-hashes -r requirements.txt
# # Fails the build if any KNOWN vulnerability is found in the
# # resolved dependency tree, on every PR AND on a daily schedule