Read the requirements files, and read the constraints file as a version cap, not as a list of what is installed. The pip guide says a constraints file controls which version is installed, not whether anything is installed. A package listed only in constraints may never ship.
What pip says
The pip user guide describes a requirements file as a list of install arguments placed in a file, used with the dash r option. A constraints file, used with the dash c option, restricts the versions that may be installed. In the guide's words, including a package in a constraints file does not trigger installation of the package. It is installed only when requested directly or pulled in as a dependency. Constraints use a subset of requirements syntax, with names required and no extras or editable entries.
python -m pip install -r requirements.txt -c constraints.txt
Why monorepos use both
A Python monorepo has no single workspace standard, so teams build their own pattern. A common one is a shared constraints file at the root, so every service uses the same versions of common libraries, and a requirements file per service that lists what that service needs.
| File | Says | Scanner should treat it as |
|---|---|---|
| requirements.txt in a service | What this service installs | Inventory input |
| constraints.txt at root | Version caps for everything | Version source, not inventory |
| Lock file such as poetry.lock or uv.lock | Resolved set | Preferred inventory input if present |
Scan mistakes to avoid
- Scanning constraints as inventory. You then report vulnerabilities in packages no service installs. Noise erodes trust.
- Ignoring constraints for versions. A requirements line without a version resolves to the cap in the constraints file. A scanner that reads only the requirements file sees no version.
- Unpinned requirements. If nothing pins the version, results depend on when you resolve. See does pip-audit work on unpinned requirements.
Find the real set
The most reliable approach is to resolve per service, using the same arguments as the deployment, and export the result. Then scan the resolved list.
python -m venv .venv-scan
.venv-scan/bin/python -m pip install -r requirements.txt -c constraints.txt
.venv-scan/bin/python -m pip freeze
The frozen list names each package and version that was actually installed, which is what you want to scan. Do this in a throwaway environment, not on a developer machine.
Group by service
Keep one resolved list per deployable service, and report findings per service. A shared constraints file can make one vulnerable pin appear in every service. That is useful to know, and fixing it once in the shared file is the right move.
Where Safeguard fits
Safeguard scans Python dependencies and builds SBOMs per project, and can open fix pull requests when you turn autonomous remediation on. It is off by default. Try one service with the free scan at https://gold.safeguard.sh/scan.
Quick check
Does a constraints file install packages?
No. It limits the allowed versions of packages that are requested elsewhere.
What should I scan for each service?
The resolved package list produced with that service's own requirements and constraints.
Sources
Last reviewed: 19 October 2026.