SafePyGuard reads your requirements.txt and checks every dependency for two kinds of risk:
| Risk | Example | What SafePyGuard does |
|---|---|---|
| ⚖️ Legal | PyQt5 is GPL-3.0, which can force you to open-source your own code |
Classifies each license as Approved / Restricted / Denied / Unknown using your policy |
| 🔓 Security | requests==2.19.0 leaks credentials (CVE-2018-18074) |
Finds known vulnerabilities via OSV.dev, with severity and the version that fixes them |
Both results land in one report, and the exit code tells your CI pipeline whether to block the merge.
Contents
- Demo
- Features
- Installation
- Quick start
- CLI reference
- Policy file
- CI integration
- Python API
- How it works
- Project structure
- Development
- License
Demo
$ safepyguard scan -r examples/requirements-demo.txt
SafePyGuard: examples/requirements-demo.txt
┌──────────┬──────────────────┬──────────────┬────────────┬──────────────────────┐
│ Package │ Version │ License │ Category │ Vulnerabilities │
├──────────┼──────────────────┼──────────────┼────────────┼──────────────────────┤
│ requests │ 2.19.0 │ Apache-2.0 │ APPROVED │ 5 (HIGH) -> 2.33.0 │
│ django │ 3.2.0 │ BSD-3-Clause │ APPROVED │ 38 (CRITICAL) │
│ PyQt5 │ 5.15.0 │ GPL-3.0-only │ DENIED │ none │
│ certifi │ 2024.8.30 │ MPL-2.0 │ RESTRICTED │ none │
│ numpy │ 2.5.4 (unpinned) │ BSD-3-Clause │ APPROVED │ none │
│ click │ 8.1.7 │ BSD-3-Clause │ APPROVED │ none │
└──────────┴──────────────────┴──────────────┴────────────┴──────────────────────┘
Vulnerability details
requests 2.19.0
HIGH GHSA-x84v-xcm2-53pg (CVE-2018-18074) fixed in 2.20.0
Insufficiently Protected Credentials in Requests
...
6 package(s) | licenses: 4 approved, 1 restricted, 1 denied | vulnerabilities: 43 (6 critical, 17 high, ...)
FAILED
- denied license: PyQt5
- vulnerabilities at or above high severity: requests (1), django (22)
Features
License compliance
- Normalizes messy license text (
"Apache 2.0","BSD License","GPLv3+") to standard SPDX IDs - Reads every place a package can declare a license: PEP 639
license_expression, thelicensefield, trove classifiers, even a full license text pasted into metadata - Understands SPDX expressions —
MIT OR Apache-2.0,(MIT AND BSD-3-Clause),WITHexceptions - Configurable TOML policy with wildcards (
GPL-*) and per-package exceptions with a recorded reason
Vulnerability scanning
- Queries OSV.dev in one batch request (no API key needed)
- Merges duplicate advisories (GHSA / PYSEC / CVE) into a single finding
- Severity from GitHub advisories, falling back to a built-in CVSS v3.1 calculator
- Shows the smallest version that fixes each issue
Built for CI
- Exit codes:
0passed ·1policy violated ·2scan could not run - Output as a terminal table, JSON, Markdown (PR comments) or SARIF (GitHub Security tab)
- Ready-made GitHub Action and pre-commit hook
- Concurrent lookups and a 24-hour on-disk cache for fast repeat runs
Installation
Requires Python 3.10+.
pip install safepyguard
Install from a local clone (for development)
git clone https://github.com/glichy-gem/SafePyGuard.git
cd SafePyGuard
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
Quick start
# 1. Scan your project's requirements
safepyguard scan -r requirements.txt
# 2. (Optional) create an editable policy file in the current directory
safepyguard init-policy
# 3. Scan again — safepyguard.toml is picked up automatically
safepyguard scan
Try it on the bundled demo file:
safepyguard scan -r examples/requirements-demo.txt
CLI reference
safepyguard scan [OPTIONS]
| Option | Description | Default |
|---|---|---|
-r, --requirements PATH |
Requirements file to scan | requirements.txt |
-p, --policy PATH |
Policy TOML file | ./safepyguard.toml if present, else built-in |
-f, --format |
table, json, markdown, sarif |
table |
-o, --output PATH |
Write the report to a file | terminal |
--fail-on-license |
Categories that fail the scan, e.g. denied,unknown, or none |
from policy (denied) |
--fail-on-severity |
low, moderate, high, critical, none |
from policy (high) |
--ignore-vuln ID |
Ignore a vulnerability ID or alias (repeatable) | — |
--no-licenses |
Skip the license check | — |
--no-vulns |
Skip the vulnerability check | — |
--no-cache |
Don't use the on-disk cache | — |
--timeout SECONDS |
Network timeout | 15 |
Other commands: safepyguard init-policy [--path FILE] [--force] · safepyguard --version
Exit codes
| Code | Meaning |
|---|---|
0 |
Every dependency passed the policy |
1 |
The policy was violated (CI should fail) |
2 |
The scan could not run (missing file, invalid policy, network down) |
Unpinned requirements such as
numpy>=1.26are checked against the newest matching release (pre-releases and yanked releases are skipped). Pin your dependencies for reproducible results.
Policy file
Generate one with safepyguard init-policy, then edit it:
[licenses]
approved = ["MIT", "BSD-3-Clause", "Apache-2.0", "ISC", "PSF-2.0"]
restricted = ["LGPL-*", "MPL-*"] # allowed, but needs a review
denied = ["GPL-*", "AGPL-*"] # wildcards are supported
[exceptions] # per-package overrides, with a reason for auditors
"pyqt5" = { category = "approved", reason = "Commercial license purchased, LEGAL-123" }
[vulnerabilities]
ignore = ["GHSA-xxxx-xxxx-xxxx"] # accepted risks (IDs or CVE aliases)
[gate]
fail_on_license = ["denied"] # approved | restricted | denied | unknown
fail_on_severity = "high" # low | moderate | high | critical | none
Classification rules
| Situation | Result | Why |
|---|---|---|
MIT OR GPL-3.0-only |
Approved | With OR you may choose, so the most permissive option wins |
MIT AND GPL-3.0-only |
Denied | With AND you must comply with both, so the strictest wins |
| Exact entry vs. wildcard | Exact wins | approved = ["GPL-2.0-only"] overrides denied = ["GPL-*"] |
| Same precision in two lists | Stricter wins | denied > restricted > approved |
| License not in any list / unrecognized | Unknown | Add "unknown" to fail_on_license to require a review |
| Vulnerability with unknown severity | Fails the gate | If severity can't be determined, a human should look |
See examples/policy-strict.toml for a production-style policy.
CI integration
GitHub Actions
- uses: actions/checkout@v4
- uses: glichy-gem/SafePyGuard@v0.1.0
with:
requirements: requirements.txt
fail-on-severity: high
To show findings in the repository's Security tab, export SARIF and upload it — see the complete workflow in examples/github-workflow.yml.
| Action input | Default |
|---|---|
requirements |
requirements.txt |
policy |
auto-detect |
format |
table |
output |
— |
fail-on-license |
from policy |
fail-on-severity |
from policy |
python-version |
3.12 |
pre-commit
repos:
- repo: https://github.com/glichy-gem/SafePyGuard
rev: v0.1.0
hooks:
- id: safepyguard
Any other CI (GitLab, Jenkins, Azure DevOps…)
pip install safepyguard
safepyguard scan -r requirements.txt -f markdown -o safepyguard-report.md
The non-zero exit code fails the job on its own.
Python API
from safepyguard import evaluate, scan
report = scan("requirements.txt", policy="safepyguard.toml")
gate = evaluate(report)
for package in report.packages:
print(package.name, package.version, package.license.category.value)
for vuln in package.vulnerabilities:
print(f" {vuln.severity.label:<9} {vuln.id} fix: {vuln.fix_version}")
print("PASSED" if gate.passed else gate.reasons)
How it works
flowchart LR
A[requirements.txt] --> B[Parse<br/>pins, -r includes, markers]
B --> C[PyPI JSON API<br/>version + license metadata]
C --> D[Normalize<br/>→ SPDX expression]
D --> E[Policy<br/>Approved / Restricted / Denied / Unknown]
B --> F[OSV.dev batch query]
F --> G[Merge duplicates<br/>severity + fix version]
E --> H[Unified report]
G --> H
H --> I{Gate}
I -->|pass| J[exit 0]
I -->|fail| K[exit 1]
H --> L[table · JSON · Markdown · SARIF]
Project structure
SafePyGuard/
├── src/safepyguard/ # the installable package
│ ├── cli.py # command-line interface (click)
│ ├── scanner.py # orchestrates the whole scan
│ ├── gate.py # pass/fail decision and exit codes
│ ├── models.py # data classes: Dependency, Vulnerability, Report…
│ ├── cache.py # 24-hour on-disk JSON cache
│ ├── errors.py # exception hierarchy
│ ├── parsers/
│ │ └── requirements.py # requirements.txt → dependencies
│ ├── sources/
│ │ ├── pypi.py # PyPI JSON API client
│ │ ├── osv.py # OSV.dev client, de-duplication, CVSS scoring
│ │ └── http.py # shared HTTP client settings
│ ├── licenses/
│ │ ├── normalize.py # license text → SPDX expression
│ │ ├── policy.py # TOML policy loading and classification
│ │ └── default_policy.toml # built-in policy
│ └── reporters/ # table, JSON, Markdown and SARIF output
├── tests/
│ ├── unit/ # one test file per module, fully offline
│ ├── integration/ # end-to-end scanner and CLI tests (+ one live test)
│ ├── fakes.py # in-memory fake PyPI and OSV servers
│ └── conftest.py # shared fixtures
├── examples/ # demo requirements, strict policy, CI workflow
├── .github/workflows/ # CI (lint, type-check, tests) and PyPI release
├── action.yml # the reusable GitHub Action
├── .pre-commit-hooks.yaml # the reusable pre-commit hook
├── pyproject.toml # packaging + tool configuration
├── CHANGELOG.md
└── LICENSE
Development
pip install -e ".[dev]"
pytest # 159 offline tests against fake PyPI/OSV servers
pytest -m network # live test against the real APIs
ruff check . && ruff format --check .
mypy # strict type checking
CI runs the test suite on Python 3.10–3.13 on both Ubuntu and Windows.
Releasing: bump src/safepyguard/_version.py, update CHANGELOG.md, then push a tag such as v0.1.0. The release workflow builds the package and publishes it to PyPI via trusted publishing.
License
MIT © 2026 Shivam Yadav
Metadata
Release files for safepyguard 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| safepyguard-0.1.0.tar.gz | 44.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| safepyguard-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 83.3 kB
Release files / safepyguard-0.1.0.tar.gz
| Download URL | safepyguard-0.1.0.tar.gz |
|---|---|
| Size | 44.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f160642db05afcad8135f07fce0ac85b3e69d725bcb22b1bed73dd0a3bb69a5d
|
|
BLAKE2b-256 checksum How to use checksums |
5d07ad55f3a26d52353793d636540a93c8c6e93a82771f793fc031e0eb889596
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logRelease files / safepyguard-0.1.0-py3-none-any.whl
| Download URL | safepyguard-0.1.0-py3-none-any.whl |
|---|---|
| Size | 39.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8badfdab5a45d9d20989e1387027c2549964d5834e427659b517bea30ab7324e
|
|
BLAKE2b-256 checksum How to use checksums |
637d1a2e8d666705a05f477aef42435edee468493d0186474e50215e31074203
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logSecurity for safepyguard 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for safepyguard-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.