Skip to main content

Dependency risk: penthera deps

Starter. For “is this package safe to add?”

Dependency risk: what can your packages do?

A lockfile change is code you did not write, about to run with your app’s access to files, network, environment and CI secrets. penthera deps reads package-lock.json, statically scans every installed package, and tells you what each one can do, and which packages are new or gained capabilities in an upgrade.
Every capability carries file:line evidence. It is a regex-and-tokenizer pass, not a full parser: deterministic, never executes package code, and fast enough for every PR (0.4 s for Penthera’s own 61 packages, 5 to 16 s for real app trees of 500 to 1,700 packages). A capability is what code can do, not proof that it is malicious; the findings carry confidence labels (potential for obfuscation and typosquats). Only installed code is read. A package that is in the lockfile but not in node_modules (or .venv) or npm’s cache is listed as not read, never as clean. On a fresh clone that is every package:
The lockfile checks still run: known-bad versions on every entry, typosquat names on your direct dependencies. Offline by default. Old versions for the diff come from the base tree’s node_modules or from npm’s local cache (~/.npm/_cacache), looked up by the lockfile’s integrity hash and verified byte for byte. --online is the only network use: it asks the npm registry for publish dates, publisher changes and deprecations, and fetches old tarballs the cache does not have (verified the same way). To comment the capability diff on every PR that changes the lockfile, use the GitHub Action below.

npm and Python

The same command reads Python projects: uv.lock, poetry.lock, pdm.lock, Pipfile.lock or requirements*.txt (pinned and --hash lines), and the installed .venv / venv (or --venv <path>) through site-packages/*.dist-info RECORD files, without running pip or Python. A repo with both a package-lock.json and Python inputs gets one combined report.
Capabilities and evidence are the same as for npm (subprocess/os.system, socket/requests/httpx/aiohttp, os.environ bulk access, eval/exec/compile, computed __import__, native .so/.pyd, AI-agent indicators), plus what is specific to Python: setup.py custom commands (install-time code; risky when it downloads and runs: a shell next to network code, a download or send command, a recon command like whoami, pip install of more packages, or decoded code, in setup.py or in the helper modules it imports, three levels deep, including packages under src/. Running git, a compiler or a build tool, or exec of a local version file, is not risky), code that runs on import (module-level code, or a local function it calls, that makes a network call or runs a download or send command: medium; tests, scripts, examples and files outside a package do not count), a chat bot token or a webhook URL with its key in a file with network code (high), a file that downloads a program and starts it or makes it executable within 15 lines, or runs a response with exec (high, “runs code it downloads”), statements hidden after 150 or more spaces, and exec of an encoded literal through a renamed decoder, .pth files with import lines and a shipped sitecustomize.py or usercustomize.py (they run on every interpreter start, so installing the package is enough to run them: medium, high when the code they load is risky), including ones a wheel places at the site-packages root from <name>-<version>.data/purelib/ or platlib/, as the malicious litellm 1.107.0 did, unpickling, exec of a base64/zlib-decoded payload, installed files that no longer match their RECORD hash, and .pth files no distribution owns. Old versions for the diff come from pip’s HTTP cache (matched by the lockfile’s sha256) or uv’s cache (by recorded hash), otherwise they are marked “not compared”. Typosquats are checked against the top 8,000 PyPI projects, PEP 503 normalized. Known-malicious PyPI versions, dropped files and endpoints come from the ioc-pypi-2026 rule pack and match whether or not the package is installed. --online registry checks are npm-only for now.

License policy

Without the flag, penthera deps applies the licenses section of penthera.policy.json when there is one:
Licenses come from the lockfile (npm records them in v2 and v3), else from the installed package.json. Expressions are parsed as SPDX: MIT OR GPL-3.0-only passes permissive because one branch is enough, while MIT AND GPL-3.0-only does not. Deprecated ids (GPL-3.0, GPL-2.0+) are matched to their current form, so a deny of AGPL-3.0-only also catches a package that says AGPL-3.0. A breach and UNLICENSED (no rights granted) are high. A missing, custom (SEE LICENSE IN ...) or non-SPDX license is medium, or high with "unknown": "block". allowPackages exempts packages your legal review has cleared. Findings use category license and appear in every output format, and --sbom records each component’s license. Back to the README · All commands