RoastMyOpsec

Vulnerabilities

requirements.txt vs package.json on Production Origins

July 26, 2025

requirements.txt vs package.json on production origins is two language manifests that must not sit next to index.html. /package.json donates npm names and versions. /requirements.txt (and Pipfile, pyproject.toml, poetry.lock) donates Python packages and pins — the same inventory class for Django, Flask, and FastAPI shops. Neither is a pentest. Both shrink reconnaissance and feed advisory lookups. A private PyPI mirror or npm registry is the public catalog. The marketing CDN is not. RoastMyOpsec probes /package.json with a bounded GET. It does not currently score /requirements.txt, does not parse lockfiles on the URL scan, and does not install packages.

The practical difference

package.json is Node inventory. requirements.txt is a pip constraint list — sometimes a full freeze with hashes, sometimes a loose Django>=4 line. pyproject.toml is the modern cousin. composer.json is the PHP costume. Dockerfile is how the image is built. .env is secrets, not inventory.

A 200 on /requirements.txt is an information leak even when every pin looks boring. Deny the names at the edge.

How to choose what to publish

CI: fail if requirements.txt, Pipfile, pyproject.toml, package.json, or lockfiles appear in a static marketing artifact. App origins that must serve those files are rare — prefer not. CDN: deny those basenames. Keep Python files in the private repo. Staging hostnames that are guessable get the same deny list.

FileTypical leakRoast todayTakeaway
/package.jsonnpm names and versionsBounded GETSee public package.json vs OSV
/requirements.txtpip pinsNot scored; deny anywaySame class
/pyproject.tomlProject metadata and depsNot scoredDeny the name
/.envLive secretsBounded signature GETSee .git vs .env

When requirements.txt still wins (off the public origin)

It wins in a private repo and in CI. It never wins as a downloadable object on the marketing bucket. Pair with Dockerfile vs package.json if the same zip also shipped a container recipe.

What the roast can prove

A reachable /package.json can surface. requirements.txt is not a scored path. Confirm a 404 yourself. Pair with OSV lockfile deepen on a repo you own — that is a different channel than a URL roast.

Common mistakes

The first mistake is copying the whole Django project to S3 because collectstatic lived next to requirements.txt.

The second mistake is a pip freeze that includes private index URLs with tokens in the query string.

The third mistake is Disallow: /requirements.txt and calling it access control.

Free audit the URL you own

RoastMyOpsec is a defensive public-surface roast: headers, cookies, sensitive paths, and more — no exploit payloads. Start with the free audit, then open the vault if the blurred findings look expensive.

Free audit now

FAQ

Is a public requirements.txt a vulnerability?
It is an information leak. Remove it and deny the name. It is not always a credential dump, but it is not harmless.
Does RoastMyOpsec fetch requirements.txt?
Not as a scored path today. It does probe /package.json. Deny Python inventory filenames at the edge anyway.
Does pyproject.toml belong on the CDN?
No. Keep project metadata in the private repo. Use OSV on a repo you own, not a marketing URL.

Sources

Related guides