Releases: nyudenkov/pysentry
Release list
v0.5.0
v0.5.0 "Policy"
This release is built around one thesis: PySentry should never silently report less than it should. It adds a small security-policy layer β per-group thresholds, package-wide ignores, and an explicit partial-scan policy β fixes a silent OSV truncation, and ships a quality-of-life pass over the human and CI output.
π‘οΈ Security Policy & Completeness
Per-group fail thresholds
Set fail_on per dependency group, overriding the global one for findings that reach that group (config-only; requires a group-aware lock file β uv.lock, poetry.lock, or pylock.toml):
[defaults]
fail_on = "medium" # production default
[groups.dev]
fail_on = "critical" # tolerate lower-severity advisories in dev-only depsThresholds resolve strictest-wins per context: a finding's effective threshold is the lowest across every context that reaches it. A group-only package takes its group threshold outright (so it can be looser than global), while a package that also ships to production keeps the global fail_on as a floor a permissive group can only tighten. Closes #151.
Ignore an entire package
[ignore].packages suppresses every finding for the named packages β useful for first-party or vendored internal packages. Names are compared with full PEP 503 normalization. Suppressed findings are still reported (tagged as suppressed in every format) but never trigger the non-zero exit:
[ignore]
packages = ["internal-first-party-lib"]Closes #149.
Explicit partial-scan policy
When a vulnerability source fails to fetch but at least one other succeeds, the scan is incomplete. PySentry now treats this as a first-class, fail-closed condition: by default the run prints its findings plus a partial-scan marker and exits 2. Pass --no-fail-on-partial (or set [sources].fail_on_partial = false) to continue on the sources that succeeded. If every source fails, the run is always a hard error.
π§ Improvements
- OSV pagination β no silent truncation. The OSV provider now follows every
next_page_tokenpage. Previously a package with many advisories could be silently truncated to the first page β a false negative. All pages are always collected. - Compact output by default. Human output is now compact by default β a summary plus a one-line table row per finding. Pass
--detailedfor full descriptions, CVSS, and references (the previous intermediate "normal" level is removed). JSON, SARIF, and Markdown are unchanged. - "Why it failed" and suppression summary lines. Human, Markdown, and JSON reports now state why the run exits non-zero β how many findings met the effective
fail_onthreshold and which threshold tripped β plus a separate line for how many findings policy suppressed. CI logs are now self-explanatory. - Smarter fix recommendation. For advisories fixed on multiple release lines, the recommended upgrade is now the smallest version strictly greater than the installed one (the least-disruptive safe upgrade) instead of an arbitrary branch. Backport-only advisories are noted as such.
- Compact job summary in GitHub Actions. In Actions, PySentry writes a compact Markdown report to the run's job summary (scan counts, severity breakdown, policy/partial state, findings table) β now the primary results surface on pull requests, where the SARIF upload is skipped by default (fork PRs get a read-only token that can't upload to Code Scanning). Set
upload-sarif-on-pr: 'true'to re-enable it for same-repo PRs.
π¦ Install
# Python
pip install pysentry-rs==0.5.0
# Rust / Cargo
cargo install pysentry --version 0.5.0Prebuilt binaries for Linux, macOS, and Windows are attached below and verified against SHA256SUMS.
Full changelog: v0.4.9...v0.5.0
Docs: https://docs.pysentry.com
v0.4.9
PySentry becomes a first-class CI citizen: an official GitHub Action, SARIF reports GitHub can actually anchor, distinct exit codes, and a hardened release pipeline.
β¨ New Features
First-Party GitHub Action
The action downloads the prebuilt release binary for the runner platform, verifies it against SHA256SUMS, runs the audit, and uploads a SARIF report to GitHub Code Scanning β findings appear in the Security tab and on pull requests:
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: nyudenkov/pysentry@v0.4.9
with:
fail-on: highInputs map 1:1 to CLI flags (path, fail-on, sources, format, output, ignore, plus a raw args passthrough). The SARIF report is uploaded even when the audit fails, so findings always reach the Security tab before the job exits non-zero. See the new CI guide β it covers other CI systems too.
--service-url: Custom OSV-Compatible Endpoint
Corporate and air-gapped environments can point the OSV provider at a self-hosted or mirrored OSV-compatible endpoint:
pysentry-rs --sources osv --service-url https://osv.internal.example.comOnly valid with --sources osv; also available as a config file option.
Distinct Exit Code for System Errors
Exit code 1 previously meant both "vulnerabilities found" and "the audit never ran". System errors (bad configuration, network failure, parse failure) now exit 2; findings at or above the --fail-on threshold keep 1. Gate on any non-zero exit as before, or handle the two cases separately.
π§ Improvements
- SARIF reports carry line numbers for every source. Previously only
pyproject.tomlanduv.lockresults had a source line; GitHub Code Scanning could not anchor the rest. Location scanning now covers everything the parsers read:requirements.txt(including multi-file audits),poetry.lock,pylock.toml,Pipfile.lock,Pipfile, and β insidepyproject.tomlβ extras and PEP 735 dependency groups. - Maintenance checks skip non-registry dependencies β no more pointless PyPI queries for Git/path/URL installs.
- Own supply chain hardened: release assets ship with
SHA256SUMS(verified by the Action),cargo auditin CI is pinned with a reviewed ignore list,quinn-protobumped to 0.11.15 (RUSTSEC-2026-0185). - OSV and PyPI clients send a
pysentry/{version}user-agent, matching the rest of the codebase.
π Bug Fixes
- SARIF: multi-file requirements audits emitted a bogus URI (
"requirements.txt, dev-requirements.txt"); each finding now resolves to its own real file. - SARIF: substring package matching could anchor
jinja2to aflask-jinja2line; names are compared through full PEP 503 normalization. - Advisory aliases were double-reported in single-source audits; aliased advisories (same CVE under GHSA and PYSEC IDs) are collapsed into one finding.
Note: v0.4.8 was tagged but never published due to a release-pipeline failure (fixed in this release); v0.4.9 is the same content. Install: uvx pysentry-rs, pip install pysentry-rs, or cargo install pysentry. Full changelog: https://docs.pysentry.com/changelog
v0.4.7
v0.4.7 "Hardening"
β¨ New Features
PEP 723 Inline Script Metadata
PySentry can now audit single-file Python scripts that declare dependencies using PEP 723 inline metadata:
# /// script
# dependencies = [
# "requests==2.31.0",
# "click==8.1.7",
# ]
# ///Run PySentry directly against the script:
pysentry-rs script.pyPinned dependencies are audited directly under --no-resolver; unpinned dependencies are resolved through the configured resolver when resolution is enabled. This makes PySentry work with modern uv run script.py style workflows without requiring a separate project directory.
Directory scans can also include PEP 723 scripts with --include-scripts or [defaults] include_scripts = true. Script-origin findings are marked with the script path in human output, for example direct @ tools/audit.py.
Transitive Findings Show Their Top-Level Dependency
Human output now explains why a vulnerable transitive package is present by showing the top-level dependency that pulled it in:
urllib3 2.0.0 [transitive] (via requests)
For packages reachable from multiple direct dependencies, PySentry lists up to three roots and summarizes the rest. This is display-only context; it does not change matching, filtering, or exit-code behavior.
Supported lock formats are uv.lock, poetry.lock, and pylock.toml / pylock.<name>.toml. Formats without dependency edges, such as Pipfile.lock and resolved requirements.txt, continue to render as before.
Maintenance Cache TTL
PEP 792 maintenance status checks now have a configurable cache TTL:
pysentry-rs --maintenance-cache-ttl 6# .pysentry.toml
[maintenance]
cache_ttl = 6The default remains 1 hour. This is useful in CI environments that want fewer Simple API requests while still keeping archived, deprecated, and quarantined package status reasonably fresh.
π§ Improvements
Alias-Aware Ignores and Unmatched Ignore Warnings
--ignore and --ignore-while-no-fix now match both the advisory's primary ID and its aliases. Ignoring a CVE now works even when the provider's canonical advisory ID is a GHSA or PYSEC ID:
pysentry-rs --ignore CVE-2024-12345PySentry also logs a warning when an ignore ID did not match any advisory during the run. This catches typoed suppressions that previously looked active but did nothing.
Unknown Config Fields Are Rejected
Configuration files now reject unknown keys instead of silently ignoring them. A typo like formatt = "json" now fails validation rather than leaving format at its default.
This applies to .pysentry.toml and [tool.pysentry] in pyproject.toml.
Friendlier Cache and Database Download Errors
Cache-format mismatches and failed advisory database downloads now surface clearer error messages with better context. PySentry no longer leaks low-level ZIP or serde errors in places where the actionable problem is stale cache data or a provider download failure.
Stricter Internal Safety Checks
The codebase now enforces clippy lints for production unwrap, expect, and indexing usage. Existing intentional exceptions are documented with invariants. This does not change CLI behavior, but it reduces the chance that malformed project data or provider data can crash the binary.
π Bug Fixes
OSV affected.versions Advisories Were Missed
Some OSV advisories enumerate affected releases in affected.versions instead of, or in addition to, range events. PySentry previously ignored that field, so single-source OSV scans could miss advisories whose affected versions were listed explicitly.
PySentry now converts every explicit OSV affected version into an exact inclusive range, so those advisories match correctly.
Warning: If you run PySentry with
--sources osv, affected findings may have been missing from previous reports. Re-run your audit on this release.
PyPI Advisories Without Usable Fix Ranges Were Missed
The PyPI JSON API returns vulnerabilities for the specific package version being queried. When PyPI returned an advisory with no usable fixed_in value, PySentry emitted no affected range, and the matcher treated the advisory as not affecting the installed version.
PySentry now trusts PyPI's per-version response and emits a match-all range for these advisories, preventing them from being silently dropped.
PyPI Multi-Branch Fixes Dropped Later Affected Branches
PyPI advisories can list multiple fixed versions for different release branches. PySentry previously used only the first fixed version, which could miss vulnerabilities in a later branch, such as a package fixed in both 2.31.1 and 3.0.2 while 3.0.1 remained vulnerable.
PySentry now emits one affected range per fixed version, preserving multi-branch fix semantics.
PEP 503 Package Name Normalization
Package names are now normalized using the full PEP 503 rule: lowercase and collapse every run of -, _, and . to a single -. This fixes dotted-name mismatches such as zope.interface, Zope_Interface, and zope-interface referring to the same package.
The PyPA advisory cache key was bumped to pypa-v3 because cached package indexes serialized with the old normalization could miss dotted packages.
Provider Cache Keys Versioned for v0.4.7 Conversions
The OSV and PyPI provider caches now use versioned keys for the v0.4.7 conversion changes. Old cache files deserialize successfully but lack the new exact, wildcard, and multi-branch ranges, so reusing them would preserve the false negatives fixed in this release.
PySentry now writes fresh provider cache entries under new prefixes and ignores the old unversioned entries.
Resolution Cache Key Versioning
Dependency resolution cache files now include a format version in their filename. This prevents future serialized payload changes from colliding with older binaries or older cache entries.
βββββββββββββββββββββββββββββββββββββββ
Full Changelog: v0.4.6...v0.4.7
βββββββββββββββββββββββββββββββββββββββ
v0.4.6
v0.4.6
β¨ New Features
Audit a Single Dependency Group (--group)
The new --group flag scopes an audit to specific dependency groups instead of the whole dependency tree. It is supported for uv (uv.lock), Poetry (poetry.lock), and PEP 751 (pylock.toml) projects. PySentry audits your main dependencies ([project].dependencies / [tool.poetry.dependencies]) plus the selected group(s) and their transitive closure, leaving the rest out:
# Audit main dependencies + the "dev" group only
pysentry-rs --group dev
# Multiple groups (repeatable or comma-separated)
pysentry-rs --group dev --group docs
pysentry-rs --group dev,docsGroup names are read from any of the standard locations:
- PEP 735
[dependency-groups](withinclude-grouprecursion) - PEP 621
[project.optional-dependencies] - Poetry
[tool.poetry.group.*]
Names are matched using PEP 735 normalization, so --group typing-test matches a declared typing_test. An unknown name fails with the list of available groups.
--group requires a lock file. Group filtering relies on a group-aware lock file β uv.lock, poetry.lock, or pylock.toml (including named pylock.<name>.toml variants) β alongside your pyproject.toml. On a project without one, PySentry fails fast with a clear error instead of silently auditing the full dependency set. (Pipfile.lock is not supported β Pipfile has no dependency-group concept.)
--group cannot be combined with --exclude-extra (or config scope = "main"), --requirements-files, or --no-resolver. It can also be set in config:
# .pysentry.toml
[defaults]
groups = ["dev", "docs"]Resolves #151.
π Bug Fixes
fail_on Silently Hid Vulnerabilities Below Its Threshold
fail_on (CLI --fail-on, config defaults.fail_on) is meant to control only the exit code β the severity at which an audit is considered a failure. A regression in v0.4.5 instead wired it into the matcher as a minimum-severity filter, so any vulnerability below the fail_on level was dropped from the report entirely rather than just being excluded from the pass/fail decision.
The effect scaled with the threshold. With the default fail_on = "medium", low-severity findings disappeared from the report. With fail_on = "critical", a project could contain many real high- and medium-severity vulnerabilities and still print β No vulnerabilities found! with a clean exit. On one real uv.lock project (90 packages), v0.4.5 reported 0 vulnerabilities under fail_on = "critical" while the project actually had 31, several of them high severity.
PySentry now reports every matched vulnerability regardless of fail_on, and uses fail_on strictly to decide the exit code.
:::warning
If you run PySentry with fail_on set above low (via --fail-on or config), affected vulnerabilities were missing from your reports while the audit may have exited successfully. Re-run your audit on this release.
:::
Regression introduced in v0.4.5; the original decoupling shipped in v0.4.3.
Shared PyPA Cache Crashed Older PySentry Versions
v0.4.5 changed the on-disk format of the cached PyPA advisory database from a raw ZIP archive to JSON, but kept writing it to the same cache file. When an older PySentry (<= 0.4.4) then read that file, it tried to parse the JSON as a ZIP and crashed with Cache operation failed: invalid Zip archive: Could not find EOCD. This bit anyone running multiple PySentry versions against the same cache β for example a project that pins an older pysentry-rs in a dependency group while a newer one is installed elsewhere.
The PyPA database now uses a version-tagged cache file, so different formats never collide. New and old versions keep separate cache files and stop corrupting each other's reads. Already-released versions cannot be retro-fixed; if you are still on <= 0.4.4 and hit this, run once with --no-cache or clear pysentry/vulnerability-db from your cache directory.
scope = "main" / --exclude-extra Ignored Dependency Groups (uv.lock)
On a uv.lock project, --exclude-extra (or config scope = "main") did not exclude PEP 735 [dependency-groups] such as dev β every group member was still scanned, so a vulnerability in a dev-only tool like pytest was reported even though you asked for main dependencies only. uv records group members in uv.lock without marking why they were pulled in, and PySentry did not yet read those group tables.
PySentry now recognizes uv's group encoding and treats [dependency-groups] members as optional, so --exclude-extra and scope = "main" correctly narrow the audit to your main dependencies and their transitive closure.
Resolves #158.
Shared Transitive Dependencies Skipped Under --exclude-extra (uv.lock)
When auditing a uv.lock project with --exclude-extra (or config scope = "main"), a transitive dependency shared between your main dependencies and an optional dependency (a [project.optional-dependencies] extra) β for example a package like certifi reached by both β could be misclassified as optional and excluded from the scan.
:::warning
Because an excluded package is never checked, any vulnerabilities in it were silently missed while the audit still reported clean. If you rely on --exclude-extra or scope = "main" with a uv.lock project, re-run your audit on this release.
:::
PySentry now computes the set of packages reachable from [project].dependencies and subtracts it from the optional set, so a shared transitive stays in scope as long as a main dependency reaches it. Packages reachable only through an extra are still excluded, exactly as before.
This affects uv.lock projects with a companion pyproject.toml; other lock formats already relied on their native optional markers.
Full Changelog: v0.4.5...v0.4.6
v0.4.5
v0.4.5
β¨ New Features
--direct-only Now Works for All Lock File Formats
--direct-only now correctly identifies direct dependencies when used with any lock file format β uv.lock, Pipfile.lock, poetry.lock, and pylock.toml.
PySentry reads the companion manifest alongside the lock file (pyproject.toml, Pipfile) to determine which packages are declared as direct dependencies. When no companion manifest is found, it falls back to lock-graph inference.
pysentry-rs --direct-onlyβ οΈ Breaking Changes
--severity Flag Removed
The --severity display filter, deprecated since v0.4.3, has been removed. Use --fail-on to control exit behavior based on severity level.
severity Config Field Removed
The severity field in [defaults] (.pysentry.toml / [tool.pysentry] in pyproject.toml) has been removed alongside the CLI flag. Remove it from your config if present.
--all / --all-extras Flags Removed
The hidden --all and --all-extras flags have been removed. Extra dependencies are included by default; use --exclude-extra to opt out.
Full Changelog: v0.4.4...v0.4.5
v0.4.4
Look at changelog here http://docs.pysentry.com/changelog
v0.4.3
Merge pull request #142 from nyudenkov/dev v0.4.3
v0.4.2
Release Notes - v0.4.2
β¨ New Features
Compact Output Mode
PySentry now supports a --compact flag that produces a condensed report - ideal for pre-commit hooks and CI pipelines where brevity matters.
What compact mode shows:
- A single summary line (e.g.,
Found 3 vulnerabilities: 1 critical, 2 high) - One line per vulnerability with ID, package, version, and severity
- Fix suggestions
What compact mode omits:
- Full vulnerability descriptions
- Section headers and decorative underlines
# Default output: summary + one-liner per vulnerability + fix suggestions
pysentry
# Compact output: ideal for pre-commit hooks and tight CI output
pysentry --compact
# Detailed output: full descriptions included
pysentry --detailed# .pysentry.toml
[defaults]
compact = trueπ Bug Fixes
pyproject.toml Sub-Table Config Detection
PySentry failed to load configuration from pyproject.toml files that used only sub-table syntax without an explicit bare [tool.pysentry] header
Before (broken):
# pyproject.toml - this config was silently ignored
[tool.pysentry.defaults]
severity = "high"
fail_on = "high"
[tool.pysentry.sources]
enabled = ["pypa", "osv"]After (fixed): PySentry now correctly discovers and loads config files that use sub-tables only ([tool.pysentry.*]) without requiring a bare [tool.pysentry] header.
Resolves #138.
βββββββββββββββββββββββββββββββββββββββ
Full Changelog: v0.4.1...v0.4.2
βββββββββββββββββββββββββββββββββββββββ
v0.4.1
Release Notes - v0.4.1
β¨ New Features
GitHub Actions Native Annotations
PySentry now auto-detects GitHub Actions and emits native workflow annotations for vulnerability findings, giving immediate visibility in job summaries without reading through logs.
Opt-out:
pysentry --no-ci-detect# .pysentry.toml
[defaults]
no_ci_detect = trueCVSS Score Parsing & Version-Aware Merging
PySentry now parses CVSS vectors and scores from vulnerability advisories, using version-aware logic to pick the best available score when merging data from multiple sources.
How it works:
- Parses CVSS vector strings (e.g.,
CVSS:3.1/AV:N/AC:L/...) into numeric scores using thepolycvss(thanks to @pablotron for this amazing library!) - Detects CVSS version (v2, v3, v4) from severity type fields and vector prefixes
- When merging across providers (PyPA, OSV, PyPI), prefers higher CVSS version first (v4 > v3 > v2), then higher score within the same version
- Severity is re-derived from the winning CVSS score for consistency
CVSS-to-severity thresholds:
| CVSS Score | Severity |
|---|---|
| >= 9.0 | Critical |
| >= 7.0 | High |
| >= 4.0 | Medium |
| < 4.0 | Low |
Unknown Severity Level
Vulnerabilities without CVSS scoring data now report as UNKNOWN severity instead of being silently classified. This affects advisories from sources that don't provide CVSS data (e.g., some PyPI JSON API entries) or newly published advisories not yet scored.
Control behavior:
By default vulnerabilities with UNKNOWN level will fail run.
# Don't fail the pipeline on unknown severity vulnerabilities
pysentry --no-fail-on-unknown
# Combine with --fail-on for precise control
pysentry --fail-on high --no-fail-on-unknownAlias-Based Vulnerability Deduplication
When the same vulnerability is reported by multiple sources under different identifiers (e.g., PYSEC-2024-123 from OSV and GHSA-xxxx-yyyy-zzzz from PyPA), PySentry now merges them into a single entry using alias data from advisories.
Benefits:
- Eliminates duplicate vulnerability reports across providers
- Preserves the best data from each source
- Reduced noise β in testing, scan results went from 91 to 67 entries for a real-world project
βββββββββββββββββββββββββββββββββββββββ
Full Changelog: v0.4.0...v0.4.1
βββββββββββββββββββββββββββββββββββββββ
v0.4.0
Release Notes - v0.4.0
Participate in pysentry usage survey
β¨ New Features
Remote Notifications System (#130)
PySentry can now display remote notifications fetched from a GitHub-hosted JSON endpoint. This enables communicating important announcements, surveys, or security advisories directly to users.
Configuration:
# .pysentry.toml
[notifications]
enabled = true # Set to false to disable remote notificationsUnified CLI Verbosity System
# Quiet mode β minimal output
pysentry -q /path/to/project
# Warning level
pysentry -v /path/to/project
# Info level (shows progress)
pysentry -vv /path/to/project
# Debug level (useful for troubleshooting)
pysentry -vvv /path/to/project
# Trace level (maximum verbosity)
pysentry -vvvv /path/to/projectRUST_LOG support: For fine-grained control, the RUST_LOG environment variable takes precedence over -v flags:
RUST_LOG=pysentry=debug pysentry /path/to/project
RUST_LOG=pysentry::parsers=trace pysentry /path/to/projectDocumentation Website
Full documentation is now available at docs.pysentry.com
βββββββββββββββββββββββββββββββββββββββ
Full Changelog: v0.3.16...v0.4.0
βββββββββββββββββββββββββββββββββββββββ