Have you ever carefully read the ingredient label on your bubble tea cup? Most people haven't. You're buying "bubble tea," not "water, sugar, tapioca starch, black tea, creamer, and caramel coloring." But that label exists, and it's legally required, because one day a certain ingredient might cause problems, and then someone needs to be able to immediately answer, "Did this drink contain that ingredient?"
Software projects also have component lists, but most projects don't. A Python project might have only eight direct dependencies, but the unpacked environment contains thirty-one. Questions like "which version," "where it comes from," "what license it's under," and "has its content been changed" are usually unasked, but they'll come up when a vulnerability is discovered in a component or when you want to hand over the project to someone else. Three open-source tools can pre-write the answers: pip-tools, cyclonedx-bom, and pip-licenses.
Tools live in their own environment
The first rule: These three tools are not included in the runtime venv. They have their own small environment, and their versions are fixed, even the pip version.
The reason is simple: if tools and projects live together, the tools' interdependence will also be included in the project's ingredient list. It's like adding "the scale for weighing" to the ingredient list. They live next door, come over to measure when needed, and then go back.
Starting with a dry run
pip 22.2 and later versions havepip install --dry-run --report report.json Without installing anything, it only parses out which packages and versions will be installed on the platform, and the hash of each wheel, outputting it as JSON. From this report, a lock file is generated, with each package's version on one line and each wheel on another.--hash=sha256:… pip-toolspip-compile --generate-hashes They are doing the same thing, the difference is that it comes fromrequirements.in Analysis.
Next, create a disposable verification environment and use it.pip install --require-hashes -r requirements.lock.txt Really pretend once, then run.pip check This step proves that the locked file can be installed on this platform, and it's not just that the parsed result looks reasonable. It's like a recipe; no matter how beautifully written, it's useless if you haven't actually cooked it once.
SBOM and license list grow from the same environment
Cyclonedx-BOM providescyclonedx-py environment To generate a CycloneDX format SBOM for a Python environment, the current specification version is 1.7, plus...--output-reproducible It can produce files with identical byte order in the same environment. SBOM is an abbreviation for Software Bill of Materials, which is the formal name of the ingredient list.
pip-licenses lists the licenses for each package in the same environment.--with-license-file Include the original authorization document as well. After formalizing both documents, proceed with version control.
I will then generate a human-readable third-party declaration from the license list: package, version, direct or indirect dependencies, SPDX license, review results, and source URL. Most are MIT, BSD, and Apache-2.0, which are automatically approved; conditional licenses like MPL-2.0 are marked as manually reviewed, with the reasons written next to them. When I first generated this list, I was shocked because I had no idea I had installed half of the packages; they were brought in by packages from ...
Check mode and update mode
Batch files can be processed in two ways. The default is a check: regenerate all artifacts and compare them with those in version control; any differences cause the batch to fail. This ensures that changes to the locked file, such as manual modification or altered parsing results, are detected before committing.--update The update involves rewriting the lock file, SBOM, and declarations. Both modes are then run with a dedicated regression test.
I really like the design of "the default is to check, and you have to explicitly state if you want to update". It makes "accidental changes" impossible, and you have to clearly tell it "I want to change it now".
The image also requires a hash.
Included with the official image installation kit--require-hashes --only-binary=:all: The former requires that the hashes of each wheel in the lock file match, while the latter prohibits on-site compilation from source code. Cross-platform compatibility requires separate lock files for each platform; the wheels decoded on Windows differ from those on Linux.
What happens if the mismatch doesn't work? The installation stops immediately. This sounds inconvenient, but think about what it prevents: someone swapping out the packages between you and PyPI. This has actually happened, it's not an urban legend. Having an extra check to stop if there's a mismatch is much better than seeing news reports later that your package has been compromised.
Mini-drama: Do people actually watch these things?
"How often do you open this SBOM file that you mentioned?"
"Almost never."
"Then why do it?"
"Because its value isn't when I open it, but on the day I need it. If a certain software package has a vulnerability, I can answer in a minute, 'Did I use it? Which version? Directly or indirectly?' Without it, I'd spend an entire afternoon."
"So it's like insurance."
"It's like insurance, and the premium is in a batch file; once it's written, you don't have to pay it again."
"What if that day never comes?"
"Then I'll never have to open it again; that's the best outcome."
The gap worth mentioning
This process compares the kits, not what you write in the SBOM (Skimming Materials). I had a mistake in the project name field of one of my SBOMs, which the tool didn't detect because it only looks at the kit list. Fields written by humans still need to be checked by humans. The ingredient list can be generated automatically, but someone still needs to remember to update the store name on the cup.
Why has everyone suddenly started talking about SBOM in recent years?
The concept of a component specification sheet (SBOM) has existed in the software industry for a long time, but it only truly became a mainstream topic after 2020. At the end of that year, the SolarWinds incident occurred, where a company's update mechanism was compromised by a backdoor, affecting thousands of customers, including several government agencies. In 2021, the US issued an executive order requiring software sold to the federal government to include a SBOM. Since then, having an SBOM has gone from "it's good to have one" to "you'll be questioned if you don't."
The xz-utils incident in 2024 was even more frightening. A compression library used by almost every Linux distribution was backdoored by an account that had spent two years gaining the maintainers' trust, and it almost made it into the official release of a mainstream distribution. It was discovered because an engineer noticed a half-second delay in SSH login and went to investigate why. Half a second. This incident made everyone realize how much of what you rely on is maintained by people you don't even know.
The two main formats for SBOM are CycloneDX and SPDX. The former comes from OWASP, and the latter from the Linux Foundation; both are open standards. This article uses CycloneDX, not for any particular reason, but simply because the Python ecosystem provides more convenient tool support.
Three common misunderstandings
The first misconception is that "a locked file is simply a hard-coded version of requirements.txt". A hard-coded version only guarantees that the version number is the same, not that the content is the same. Packages with the same version number can be re-uploaded (PyPI now prohibits this, but other sources may not), only hashing can guarantee that you are installing the same file.
The second misconception is that "SBOM only needs to be generated once." Every time dependencies change, it needs to be regenerated, otherwise it will describe an environment that no longer exists. This is why it's included in batch files and inspection processes, allowing it to change along with the code.
The third misconception is that "the licensing list is a legal matter." Legal professionals understand the difference between MIT and GPL, but they don't know whether your project has actually delivered the software to the user, and that determines whether obligations have been triggered. Engineers and legal professionals each know half the story; the list is the common language for communication between the two sides.
A small exercise you can try over the weekend
Find your own Python project, as long as it has...requirements.txt That's fine. Create a clean venv.pip install --dry-run --report report.json -r requirements.txt Open that JSON file and take a look. Count them.install There are several kits in the array, and yourequirements.txt Compare the number of rows in each row.
Then in another venvpip install pip-licenses Run around your project environmentpip-licenses --format=markdown You'll get a table; check if there are any license names you haven't heard of. Most people will encounter at least one "What is this?" moment the first time they do this. That "What is this?" is the very reason this whole process exists.
Postscript
I actually started looking at the ingredient lists on bubble tea labels later on. After that, the things I bought didn't change; I just knew what I was drinking. The ingredient lists on software are probably similar; they don't improve your program, they just let you know what you're running. And in a world where everything...pip install In this era, knowing what you're running for is a rare kind of clarity.
Project page: github.com/jazzband/pip-tools , github.com/CycloneDX/cyclonedx-python , github.com/raimon49/pip-licenses