Packages (PyPI / npm)
Verify what your build pulls in, before it runs.
Packages are the highest-volume artifact Phylax sees, and the only one where a single command pulls in hundreds of things you did not write. This guide takes you from nothing installed to a verified dependency tree.
1. Install Phylax
The CLI ships on npm and installs the phylax binary.
npm install -g @phyi/cliPrefer not to install globally? Run it without installing:
npx @phyi/cli --versionConfirm it is on your PATH:
phylax --version2. Authenticate (optional)
Every read endpoint is public, so verification works with no account at all. You only need a token when you want your organization’s policies applied to the verdict rather than the public default.
phylax auth loginphylax whoami3. Verify, then install
This is the core loop. Verify the exact name and version you are about to add, read the verdict, and only then install it.
Step 1. Verify the package you intend to add.
phylax verify package pkg:npm/express@4.18.2Step 2. Read the result.
verdict: allowrisk score: 12 / 100provenance: verifiedattestation: availableStep 3. Install the same version you just verified.
npm install express@4.18.2Step 1. Verify the package you intend to add.
phylax verify package pkg:pypi/requests@2.32.3Step 2. Read the result.
verdict: allowrisk score: 9 / 100provenance: verifiedattestation: availableStep 3. Install the same version you just verified.
pip install requests==2.32.3npm install express and pip install requests resolve to whatever is newest at that
moment, which may not be what you verified a second ago. A verdict is about one exact
version, so install that exact version.
4. Verify a whole lockfile
Verifying one package at a time is fine while you are adding a dependency. For an existing project, point Phylax at the lockfile instead: it resolves every pinned dependency and returns the strictest verdict in the tree.
phylax verify lockfile ./package-lock.json --fail-on blockphylax verify lockfile ./requirements.txt --fail-on blockUse the lockfile, not the manifest. A manifest records ranges; a lockfile records what you will actually install. The exit code carries the strictest verdict, so this drops straight into CI/CD Pipelines with no extra wiring.
Package references
Phylax identifies packages by package URL, the same
pkg: string used by SBOM formats. It became an Ecma standard, ECMA-427, in December 2025, so
the reference you pass Phylax is the one your SBOM, scanner and registry already agree on.
pkg:npm/express@4.18.2pkg:pypi/requests@2.32.3pkg:npm/%40asyncapi/specs@6.8.1| Part | Example | Notes |
|---|---|---|
| Type | npm, pypi | The ecosystem. Determines how the rest is parsed. |
| Namespace | @asyncapi | npm scope or group. Percent-encode the @ as %40. |
| Name | express | Registry name, case-sensitive for npm. |
| Version | 4.18.2 | Omit to resolve the latest published version. |
The bare form works too. phylax verify package npm/express@4.18.2 is accepted and expanded
for you.
What Phylax checks
Name confusion is folded into the provenance check. Typosquatting remains the cheapest attack
on both registries: one 2024 campaign published over 500 variations
of names like requests, TensorFlow and BeautifulSoup, and reqeusts reads as requests
to a tired human every time.
Reading a verdict
- verdict:
- allow
- risk score:
- 12 / 100
- provenance:
- verified
- attestation:
- available
| Field | Meaning |
|---|---|
verdict | allow, warn or block. Drives the exit code. |
risk score | 0 to 100. Higher is worse. Thresholds are set by policy. |
provenance | Whether the artifact traces to a verified publisher and build. |
attestation | Whether a signed attestation exists to fetch and verify offline. |
Install is the dangerous step
Worth internalising: for packages, installing is not a separate, safe action that happens after verification. On both ecosystems, installing can execute code from the package before you have imported a single line of it.
Lifecycle scripts (preinstall, postinstall, prepare) run automatically with your full
user privileges. They have been the lowest-friction malware path on npm for years.
npm v12, shipped July 2026, blocks install scripts, Git dependencies and remote sources by default. On anything older, set it yourself:
ignore-scripts=truenpm ci --ignore-scriptsA source distribution runs setup.py at install time, as your user. A wheel does not: it is a
build output, so there is no build step to hijack. pip prefers a compatible wheel but
silently falls back to an sdist when none exists.
Make that fallback explicit rather than silent:
pip install --only-binary=:all: --require-hashes -r requirements.txtIf a dependency has no wheel this fails loudly and you get to decide, instead of running a
stranger’s setup.py because a build matrix picked an unusual Python version.
It blocks lifecycle hooks. It does not block malicious code in the package’s runtime source,
a hostile bin entry, or an import-time payload. The
AsyncAPI compromise of 14 July 2026
trojanized @asyncapi/specs with a payload that fired the moment the module was imported,
which no script-blocking setting would have stopped. Separately,
PackageGate (January 2026)
disclosed six zero-days across npm, pnpm, vlt and Bun that achieved execution through Git
operations during install even when lifecycle scripts were disabled.
Where the two ecosystems differ
| npm | PyPI | |
|---|---|---|
| Install-time execution | Lifecycle scripts | setup.py in an sdist |
| Safe artifact form | No equivalent | Wheel (.whl) |
| Turn it off | ignore-scripts=true, default in v12+ | --only-binary=:all: |
| Pin the tree | package-lock.json + npm ci | --require-hashes |
| Still exposed to | Import-time and runtime code, bin entries | Import-time and runtime code |
Defence in depth
No single control here is sufficient, and the recent incidents are precisely the cases where one control failed. These compose:
- Verify the lockfile, not the manifest. Ranges resolve differently tomorrow.
- Commit the lockfile and install from it with
npm cior--require-hashes. - Disable install-time execution, and do not treat that as having solved the problem.
- Add a publication cooldown. Refuse versions published in the last N days, so you are never the one who finds the malicious release. Most compromised versions are pulled within hours.
- Restrict CI egress. A payload that cannot reach its collection endpoint is a failed payload, and this is the control that holds when the others are bypassed.
Related guides
Enforce all of this on every build with CI/CD Pipelines, check packages at the moment an agent loads them with the Agent Runtime Gate, or see every flag and exit code on the Phylax CLI page.