AI-Generated requirements.txt and Vulnerable Packages
AI pins real packages but outdated vulnerable versions in requirements.txt — different from slopsquatting, and invisible without an explicit audit.
AI coding assistants write requirements.txt entries from training data — not from the live PyPI index. The version they pin is the one most common in their training corpus, which was typically current 12 to 24 months before the model’s training cutoff. By the time your team installs those dependencies, some may carry documented vulnerabilities. The package name is correct. The install succeeds. Nothing flags it until an audit runs.
This is a distinct problem from slopsquatting, where an AI hallucinates a package name that doesn’t exist on PyPI — or that exists as a typosquat registered by a threat actor. Slopsquatting is visible the moment installation fails. The vulnerable-version problem stays invisible by default.
How AI Picks Package Versions
AI coding assistants generate requirements.txt entries from training data, not from the live PyPI index: they write the version they most commonly saw during training, which is often a version that was popular 12–24 months before the model’s training cutoff and may have known vulnerabilities documented after that date. BrassCoders’s AI-pattern scanner catches phantom imports — packages that don’t exist on PyPI — but real packages at vulnerable versions install cleanly and pass every import check.
Models are trained on code snapshots with a fixed cutoff date. When you ask one to scaffold a Flask app, it writes Flask==2.3.2 because that version appeared in thousands of GitHub repos in its training window, not because it queried PyPI this morning. The same pattern applies to requests, cryptography, and Pillow — any dependency with an active CVE history becomes a candidate.
The gap matters most for packages that ship frequent security patches. cryptography discloses CVEs several times per year. Pillow has accumulated over 20 CVEs across its release history. If an AI recommends cryptography==41.0.3 and the current release is 43.x with several security fixes in between, your environment runs known-vulnerable code from day one.
The install doesn’t fail. The import works. The tests pass. That’s what makes this category of risk so easy to miss.
Real Packages, Vulnerable Versions: The Distinct Threat
BrassCoders’s AI-pattern scanner catches phantom imports — packages that don’t exist on PyPI — but a real package at a vulnerable version passes every import check and installs cleanly. The vulnerability is invisible until an explicit dependency audit runs.
Slopsquatting gets most of the attention because it’s dramatic: the AI invents a package name, a threat actor registers that name on PyPI with malicious code, and the next developer who follows the AI’s instructions runs the attack payload. The supply-chain angle makes for compelling security coverage.
The vulnerable-version pattern is quieter but affects a much broader class of packages. Every package with a CVE history is a candidate. The package exists on PyPI with a clean reputation, the version the AI pinned was safe when the AI was trained, and nothing in the standard development workflow surfaces the gap.
The PyPI Advisory Database, maintained by the Python Packaging Authority, tracks known vulnerabilities against specific package versions. pip-audit queries it in seconds. Your AI assistant has never heard of the CVEs published after its training cutoff.
Automating the Check AI Gets Wrong
pip-audit, the PyPA’s official dependency auditing tool, cross-references your requirements.txt against the PyPI Advisory Database and GitHub Advisory Database in seconds — it catches what AI assistants miss by running against the live vulnerability index rather than training data. BrassCoders and pip-audit cover different layers and both belong in CI.
pip-audit is straightforward to install and run:
pip install pip-audit
pip-audit
Run it after pip install -r requirements.txt so it scans what’s actually installed. No authentication needed. No API key. pip-audit exits with code 1 on any finding, which makes failing the CI build trivial to configure. Failures surface the CVE ID, the affected version, the fixed version, and a severity score — everything you need to triage the finding in under a minute.
One thing pip-audit doesn’t cover: the security bugs in the code your team writes. Hardcoded credentials, unsafe subprocess calls, SQL injection patterns — those are code-layer risks that live inside your own modules. That’s where BrassCoders takes over.
Where BrassCoders Fits in the Dependency Audit Stack
BrassCoders runs in CI and catches code-layer security bugs in the packages you write — SQL injection, hardcoded credentials, unsafe subprocess calls. pip-audit runs against your dependency manifest and catches known vulnerabilities in packages you import. Both belong in the same CI pipeline.
BrassCoders runs 12 static-analysis scanners against your source code: Bandit, Pylint, Pyre/Pysa, Semgrep, ast-grep, detect-secrets, and six custom detectors. The AI-coder bug benchmark shows BrassCoders catching 11 of 12 AI-generated bugs in a reproducible corpus, compared to Bandit’s 6 of 12. That coverage is across your code, not your dependency manifest.
The six custom detectors add coverage for secret patterns, PII/privacy risks, AI-generated phantom imports, performance anti-patterns, content-moderation risks, and JavaScript/TypeScript. They run on the same local machine as the upstream scanners, with no outbound network calls in the OSS core.
The OSS core is free (Apache 2.0) and installs with:
pip install brasscoders
brasscoders scan .
BrassCoders Paid, at $12/dev/month, adds an AI-powered enrichment pass that deduplicates findings and ranks them by project-specific relevance. It sends only already-redacted findings and a project signature to the gateway — never raw source code.
Pair both tools in the same CI job. pip-audit catches the outdated, vulnerable dependency the AI pinned. BrassCoders catches the SQL injection the AI wrote into the route handler. Neither replaces the other.
pip install brasscoders. Add pip-audit right after pip install -r requirements.txt. Between the two, you cover code-layer bugs and dependency-level CVEs.
Frequently Asked Questions
What's the difference between this and slopsquatting?
Slopsquatting is when an AI hallucinates a package name that doesn't exist on PyPI — or exists as a typosquat. The requirements.txt vulnerability problem is different: the package exists, the name is correct, but the version pinned has a known CVE. BrassCoders catches the phantom-import case; pip-audit catches the vulnerable-version case.
Does BrassCoders check for vulnerable package versions?
No — BrassCoders scans Python source code for security patterns in the code you write. It does not check whether your dependencies have known CVEs. Use pip-audit or Safety for dependency vulnerability scanning alongside BrassCoders.
How often does AI pin a vulnerable version?
Frequency depends on the package and the model's training cutoff. Commonly attacked packages (requests, cryptography, Pillow, Django) have a history of CVEs — a version pinned to 12-18 months ago has a non-trivial chance of having a known vulnerability discovered since then.
Should I use exact pinning or range specifiers in requirements.txt?
Exact pinning (package==1.2.3) locks a specific known version, which is reproducible but requires manual updates when vulnerabilities are discovered. Range specifiers (package>=1.2.3) allow pip to upgrade to a compatible safe version automatically but can introduce breaking changes. Most security guidance recommends exact pinning combined with automated dependency update tooling (Dependabot, Renovate).
How do I add pip-audit to my CI pipeline?
Add pip install pip-audit && pip-audit as a step after dependency installation in your CI workflow. pip-audit exits with code 1 if any known vulnerabilities are found, making it easy to fail the build. BrassCoders scan runs alongside it to cover the code-layer risks pip-audit doesn't see.