safe-pip-scanner 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- safe_pip_scanner-1.4.0/CHANGELOG.md +308 -0
- safe_pip_scanner-1.4.0/CONTRIBUTING.md +93 -0
- safe_pip_scanner-1.4.0/LICENSE +21 -0
- safe_pip_scanner-1.4.0/MANIFEST.in +7 -0
- safe_pip_scanner-1.4.0/PKG-INFO +446 -0
- safe_pip_scanner-1.4.0/README.md +405 -0
- safe_pip_scanner-1.4.0/pyproject.toml +77 -0
- safe_pip_scanner-1.4.0/safe_pip/__init__.py +45 -0
- safe_pip_scanner-1.4.0/safe_pip/__main__.py +13 -0
- safe_pip_scanner-1.4.0/safe_pip/cli.py +2289 -0
- safe_pip_scanner-1.4.0/safe_pip/code_scanner.py +413 -0
- safe_pip_scanner-1.4.0/safe_pip/dashboard.py +548 -0
- safe_pip_scanner-1.4.0/safe_pip/db.py +335 -0
- safe_pip_scanner-1.4.0/safe_pip/display.py +466 -0
- safe_pip_scanner-1.4.0/safe_pip/local_scorer.py +614 -0
- safe_pip_scanner-1.4.0/safe_pip/osv.py +245 -0
- safe_pip_scanner-1.4.0/safe_pip/policy.py +302 -0
- safe_pip_scanner-1.4.0/safe_pip/py.typed +0 -0
- safe_pip_scanner-1.4.0/safe_pip/pypistats.py +118 -0
- safe_pip_scanner-1.4.0/safe_pip/release_analyzer.py +242 -0
- safe_pip_scanner-1.4.0/safe_pip/sarif.py +213 -0
- safe_pip_scanner-1.4.0/safe_pip/sbom.py +144 -0
- safe_pip_scanner-1.4.0/safe_pip/scan_cache.py +147 -0
- safe_pip_scanner-1.4.0/safe_pip/scanner.py +879 -0
- safe_pip_scanner-1.4.0/safe_pip/threat_feed.py +298 -0
- safe_pip_scanner-1.4.0/safe_pip/typosquat.py +569 -0
- safe_pip_scanner-1.4.0/safe_pip/watch.py +893 -0
- safe_pip_scanner-1.4.0/safe_pip_scanner.egg-info/PKG-INFO +446 -0
- safe_pip_scanner-1.4.0/safe_pip_scanner.egg-info/SOURCES.txt +49 -0
- safe_pip_scanner-1.4.0/safe_pip_scanner.egg-info/dependency_links.txt +1 -0
- safe_pip_scanner-1.4.0/safe_pip_scanner.egg-info/entry_points.txt +2 -0
- safe_pip_scanner-1.4.0/safe_pip_scanner.egg-info/requires.txt +11 -0
- safe_pip_scanner-1.4.0/safe_pip_scanner.egg-info/top_level.txt +1 -0
- safe_pip_scanner-1.4.0/setup.cfg +4 -0
- safe_pip_scanner-1.4.0/tests/test_advanced.py +360 -0
- safe_pip_scanner-1.4.0/tests/test_build.py +173 -0
- safe_pip_scanner-1.4.0/tests/test_cli.py +517 -0
- safe_pip_scanner-1.4.0/tests/test_code_scanner.py +306 -0
- safe_pip_scanner-1.4.0/tests/test_coverage_gaps.py +1061 -0
- safe_pip_scanner-1.4.0/tests/test_dashboard.py +269 -0
- safe_pip_scanner-1.4.0/tests/test_db.py +241 -0
- safe_pip_scanner-1.4.0/tests/test_display.py +199 -0
- safe_pip_scanner-1.4.0/tests/test_integration.py +293 -0
- safe_pip_scanner-1.4.0/tests/test_osv.py +303 -0
- safe_pip_scanner-1.4.0/tests/test_policy.py +288 -0
- safe_pip_scanner-1.4.0/tests/test_pypistats.py +178 -0
- safe_pip_scanner-1.4.0/tests/test_sarif.py +254 -0
- safe_pip_scanner-1.4.0/tests/test_scanner.py +573 -0
- safe_pip_scanner-1.4.0/tests/test_threat_feed.py +206 -0
- safe_pip_scanner-1.4.0/tests/test_typosquat.py +263 -0
- safe_pip_scanner-1.4.0/tests/test_watch.py +616 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to safe-pip are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## [1.4.0] — 2026-06-14
|
|
9
|
+
|
|
10
|
+
### 🎯 Scan Accuracy Improvements
|
|
11
|
+
|
|
12
|
+
#### Self-referential Info findings suppressed
|
|
13
|
+
- Scanning `requests` showed `● Info Network call in __init__.py: requests.get(` — flagging
|
|
14
|
+
the requests package for using `requests.get()` internally is a false positive.
|
|
15
|
+
- **Fix** (`code_scanner.py`): Info findings whose text references the scanned package's
|
|
16
|
+
own name followed by a dot are filtered out. e.g. "requests.get" while scanning "requests".
|
|
17
|
+
|
|
18
|
+
#### `burst releases` tag no longer shown for trusted packages
|
|
19
|
+
- Well-known packages (numpy, requests, django, boto3) have many release bursts by nature.
|
|
20
|
+
Showing `[red]burst releases[/red]` for them was noise that undermined trust in the signal.
|
|
21
|
+
- **Fix** (`display.py`): burst and dormancy-break tags are suppressed when `score=0` and
|
|
22
|
+
`decision=INSTALL` — i.e. packages the scorer has fully cleared. The signals remain in
|
|
23
|
+
the underlying data and still fire for suspicious packages.
|
|
24
|
+
|
|
25
|
+
#### Author field fixed for modern PyPI metadata
|
|
26
|
+
- All packages showed `author: unknown` because PyPI's v2 API moved the author to
|
|
27
|
+
`author_email` as `"Display Name <email>"` format, and `maintainers[]` list.
|
|
28
|
+
- First fix extracted the wrong part (`me` from `Kenneth Reitz <me@kennethreitz.org>`).
|
|
29
|
+
- **Final fix** (`scanner.py`): takes the display name *before* `<`, requires `<` to be
|
|
30
|
+
present (ignores bare email addresses), then falls back to `maintainers[0].name`.
|
|
31
|
+
- Result: `requests` now shows `author: Kenneth Reitz` instead of `author: me`.
|
|
32
|
+
|
|
33
|
+
### 🛡 Threat Intelligence Expansion
|
|
34
|
+
|
|
35
|
+
#### Threat feed: 66 → 143 entries (+77)
|
|
36
|
+
New attack families: boto3/AWS (credential theft), SQLAlchemy, click/rich/typer,
|
|
37
|
+
pydantic/fastapi, paramiko/fabric (infrastructure), pytest/CI tools (supply chain),
|
|
38
|
+
pyyaml, pillow/opencv, discord bots, and confirmed malicious packages from documented
|
|
39
|
+
PyPI incidents (xmrig, ascii2text, discordspy, ultrarequests, pptest, dpp-discord).
|
|
40
|
+
|
|
41
|
+
#### KNOWN_DANGEROUS: 5 → 15 entries (+10)
|
|
42
|
+
Added: `ecdsa` (WARN — timing attacks), `rsa` (WARN — CVE-2020-25658), `colourama`,
|
|
43
|
+
`python3-dateutil`, `ascii2text`, `ultrarequests`, `xmrig`, `discordspy`, `pptest`.
|
|
44
|
+
|
|
45
|
+
#### Scoring fix: KNOWN_DANGEROUS floor applied after reputation bonuses
|
|
46
|
+
- `ecdsa` and `rsa` were scoring 26 (INSTALL) because a long-standing-reputation bonus
|
|
47
|
+
(−5) was subtracting from the floor (31), pulling them below the WARN threshold.
|
|
48
|
+
- **Fix**: floor is now re-applied after all bonuses, guaranteeing `score ≥ 31` → WARN.
|
|
49
|
+
|
|
50
|
+
### ⚙ Infrastructure
|
|
51
|
+
|
|
52
|
+
#### Claude model updated to `claude-sonnet-4-6`
|
|
53
|
+
- Was pinned to `claude-sonnet-4-5`. Updated to current release.
|
|
54
|
+
|
|
55
|
+
#### AI prompt: false-positive avoidance rules added
|
|
56
|
+
- Added explicit instructions to the AI scoring prompt:
|
|
57
|
+
- Skip self-referential findings (e.g. `requests.get` inside `requests`)
|
|
58
|
+
- Don't penalise HTTP client libraries for making HTTP calls
|
|
59
|
+
- Don't penalise deployment tools for subprocess usage
|
|
60
|
+
- `import winreg` in cross-platform packages = INFO only, not a risk signal
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## [1.3.0] — 2026-06-13
|
|
66
|
+
|
|
67
|
+
### 🐛 Bug Fixes
|
|
68
|
+
|
|
69
|
+
#### Removed `requests` as a runtime dependency — critical fix
|
|
70
|
+
- `safe-pip` exited immediately on startup if `requests` was not installed, even
|
|
71
|
+
though `scanner.py` already used stdlib `urllib` for all HTTP calls.
|
|
72
|
+
- **Root cause**: `osv.py`, `pypistats.py`, and `code_scanner.py` all imported
|
|
73
|
+
`requests` directly, and `__init__.py` listed it as a hard required dep.
|
|
74
|
+
- **Fix**: all three modules converted to `urllib.request` / `urllib.error`.
|
|
75
|
+
`requests` is no longer imported anywhere in the package.
|
|
76
|
+
- **Fix**: removed `"requests"` from `_REQUIRED` in `__init__.py`.
|
|
77
|
+
- **Fix**: `doctor` command now lists `requests` as *optional*.
|
|
78
|
+
- After this fix the following all work with `requests` uninstalled:
|
|
79
|
+
```
|
|
80
|
+
safe-pip scan requests
|
|
81
|
+
safe-pip scan flask --deps
|
|
82
|
+
safe-pip update-db
|
|
83
|
+
python -m safe_pip scan flask
|
|
84
|
+
safe-pip watch status
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Decision reason no longer shows Info-level findings
|
|
88
|
+
- `Decision: ✓ INSTALL Network call in __init__.py: requests.get(` was appearing
|
|
89
|
+
for the `requests` package — an internal diagnostic surfacing as the headline verdict.
|
|
90
|
+
- **Fix**: `local_scorer.py` now excludes `category="Info"` findings when selecting
|
|
91
|
+
the decision reason. Only real risk signals (Reputation, CVE, Code, Maintenance)
|
|
92
|
+
are eligible. Info findings still appear in the full findings list.
|
|
93
|
+
- After fix: `Decision: ✓ INSTALL 'requests' is a well-known, widely-trusted PyPI package`
|
|
94
|
+
|
|
95
|
+
#### Cache hits now display `(cached)` instead of replaying the original scan time
|
|
96
|
+
- Repeated scans showed identical times (e.g. `6.64s` three times), making it appear
|
|
97
|
+
the cache wasn't working. The cache **was** working — the stored `elapsed` from the
|
|
98
|
+
first scan was being redisplayed on every hit.
|
|
99
|
+
- **Fix** (`scanner.py`): cache hits return a shallow copy with `elapsed` set to the
|
|
100
|
+
actual lookup time and `from_cache: true` added.
|
|
101
|
+
- **Fix** (`display.py`): when `from_cache` is true, the header shows `(cached)`;
|
|
102
|
+
live scans show `X.XXs` (minimum `0.01s`).
|
|
103
|
+
- Result:
|
|
104
|
+
```
|
|
105
|
+
Scan 1: requests v2.34.2 score 0/100 LOW ✓ INSTALL 6.64s
|
|
106
|
+
Scan 2: requests v2.34.2 score 0/100 LOW ✓ INSTALL (cached)
|
|
107
|
+
Scan 3: requests v2.34.2 score 0/100 LOW ✓ INSTALL (cached)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### `--no-cache` flag now forwarded correctly from `scan` command
|
|
111
|
+
- `safe-pip scan requests --no-cache` was silently ignored — parsed but never passed
|
|
112
|
+
to `_run_scan`. Fixed: `_run_scan(pkg, cfg, scan_output, no_cache=no_cache)`.
|
|
113
|
+
|
|
114
|
+
#### Cache write errors now logged at WARNING level
|
|
115
|
+
- `set_cached()` swallowed all write errors silently (`log.debug`), masking failures.
|
|
116
|
+
- Changed to `log.warning` so errors surface under `--debug`.
|
|
117
|
+
- `json.dumps()` now called before opening the file to avoid corrupt partial writes.
|
|
118
|
+
|
|
119
|
+
#### Removed duplicate `scan_cache` import inside `scan()`
|
|
120
|
+
- `scanner.py` imported `cache_key, get_cached, set_cached` at module level and again
|
|
121
|
+
inside `scan()`. The redundant inner import was removed.
|
|
122
|
+
|
|
123
|
+
### ✨ New
|
|
124
|
+
|
|
125
|
+
#### `safe-pip cache debug <package>` — cache self-diagnostic
|
|
126
|
+
- Diagnose cache state for any package:
|
|
127
|
+
```
|
|
128
|
+
safe-pip cache debug requests
|
|
129
|
+
```
|
|
130
|
+
Shows: cache directory, key, file path, file age, TTL validity, and whether
|
|
131
|
+
`get_cached()` returns a hit. If the file exists but parsing fails, reports
|
|
132
|
+
the exact JSON error.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## [1.1.0] — 2026-06-02
|
|
137
|
+
|
|
138
|
+
### 🐛 False Positive Fixes
|
|
139
|
+
|
|
140
|
+
#### Dormancy break — no longer a CODE WARNING for established packages
|
|
141
|
+
- Packages with ≥10 releases before a dormancy gap (e.g. `numpy`) now generate
|
|
142
|
+
an **INFO** finding instead of a `medium` warning. Established packages that
|
|
143
|
+
go quiet for a year then revive are almost never malicious.
|
|
144
|
+
- New message: *"likely legitimate revival of established package"* vs the old
|
|
145
|
+
generic *"possible maintainer takeover"* which was triggering for `numpy` and
|
|
146
|
+
other long-lived libraries.
|
|
147
|
+
|
|
148
|
+
#### Hardcoded IP — OID/ASN.1 version strings no longer flagged
|
|
149
|
+
- Improved IP-detection regex: first octet must now be ≥ 20 to avoid matching
|
|
150
|
+
X.509 OID strings like `2.5.29.9` found in `cryptography` and similar TLS libs.
|
|
151
|
+
- Added context-aware downgrade: if the surrounding line contains ASN/OID/X.509
|
|
152
|
+
context keywords, or if the second octet is ≤ 9 (typical OID), the finding is
|
|
153
|
+
downgraded to **INFO** — shown but never scored.
|
|
154
|
+
- Eliminates false positives: `beautifulsoup4` `dammit.py: 13.2.3.1` and
|
|
155
|
+
`cryptography` `_oid.py: 2.5.29.9` now produce at most an INFO note.
|
|
156
|
+
|
|
157
|
+
### ✨ New Features
|
|
158
|
+
|
|
159
|
+
#### "Did you mean?" for typosquats
|
|
160
|
+
- When a package is detected as a likely typosquat, the scan output now shows:
|
|
161
|
+
`Did you mean: requests (run: pip install requests)`
|
|
162
|
+
- Available in both Rich terminal and JSON output (`did_you_mean` field).
|
|
163
|
+
|
|
164
|
+
#### Suggested replacement for known deprecated/unsafe packages
|
|
165
|
+
- `pycrypto` → `pycryptodome`, `nose` → `pytest`, `mock` → `unittest.mock`, and
|
|
166
|
+
10 other known-deprecated packages now display:
|
|
167
|
+
`Suggested replacement: pycryptodome (run: pip install pycryptodome)`
|
|
168
|
+
- Available in both Rich terminal and JSON output (`suggested_replacement` field).
|
|
169
|
+
|
|
170
|
+
#### JSON output (`--output json` / `safe-pip scan --output json`)
|
|
171
|
+
- JSON output now includes two new fields:
|
|
172
|
+
- `"did_you_mean"`: canonical name if typosquat detected, else `null`
|
|
173
|
+
- `"suggested_replacement"`: replacement package name if known, else `null`
|
|
174
|
+
|
|
175
|
+
#### `--fail-on-high` for CI/CD pipelines (`scan-file`)
|
|
176
|
+
- `safe-pip scan-file requirements.txt --fail-on-high`
|
|
177
|
+
- **Exit 0** = all packages are LOW or MEDIUM risk (pass)
|
|
178
|
+
- **Exit 1** = at least one package is HIGH risk (BLOCK)
|
|
179
|
+
- Complements the existing `--fail-on-warn` (exit 1 on any WARN or BLOCK).
|
|
180
|
+
- Documented in README CI/CD section with GitHub Actions example.
|
|
181
|
+
|
|
182
|
+
#### HTML report (`--html report.html`)
|
|
183
|
+
- `safe-pip scan-file requirements.txt --html report.html`
|
|
184
|
+
- Generates a self-contained dark-theme HTML report with:
|
|
185
|
+
- Summary cards (safe / warned / blocked / total)
|
|
186
|
+
- Horizontal bar chart of risk distribution
|
|
187
|
+
- Full results table with score, verdict, CVE count, top finding, decision
|
|
188
|
+
- "Did you mean?" and "Suggested replacement" inline in the table
|
|
189
|
+
- No external dependencies — fully self-contained single file.
|
|
190
|
+
|
|
191
|
+
#### `--fail-on-high` wired into `batch` command too
|
|
192
|
+
- `batch` now accepts `--fail-on-high` independently of `--fail-on-warn`.
|
|
193
|
+
- Previously `scan-file --fail-on-high` incorrectly conflated both flags; now:
|
|
194
|
+
- `--fail-on-warn` → exit 1 on WARN **or** BLOCK
|
|
195
|
+
- `--fail-on-high` → exit 1 on BLOCK only (WARN = exit 0)
|
|
196
|
+
- Both flags can be combined for custom gate logic.
|
|
197
|
+
|
|
198
|
+
#### `--json` shorthand on `scan`
|
|
199
|
+
- `safe-pip scan requests --json` is now equivalent to `safe-pip scan requests --output json`.
|
|
200
|
+
- Cleaner for shell pipelines: `safe-pip scan requests --json | jq .decision`
|
|
201
|
+
|
|
202
|
+
#### `--deps` — dependency tree analysis
|
|
203
|
+
- `safe-pip scan requests --deps` scans declared dependencies (one level deep).
|
|
204
|
+
- Reads `requires_dist` from PyPI metadata, extracts package names, and runs
|
|
205
|
+
a full scan on each transitive dependency.
|
|
206
|
+
- Rich terminal output shows a tree:
|
|
207
|
+
```
|
|
208
|
+
Dependencies of requests:
|
|
209
|
+
├─ urllib3 score 5 ✓ INSTALL
|
|
210
|
+
├─ certifi score 3 ✓ INSTALL
|
|
211
|
+
└─ idna score 4 ✓ INSTALL
|
|
212
|
+
```
|
|
213
|
+
- JSON output emits a separate `{"deps_of": "requests", "dependencies": [...]}` object.
|
|
214
|
+
- Returns exit 1 if any dependency is risky (respects `--fail-on-warn`/`--fail-on-high`).
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
- New top-level command that downloads and caches:
|
|
220
|
+
- Typosquat blocklist
|
|
221
|
+
- Malicious package list with CVE mappings
|
|
222
|
+
- Top-8000 PyPI packages feed
|
|
223
|
+
- Progress bar with per-source entry counts.
|
|
224
|
+
- Replaces `safe-pip update` for users who want explicit control over DB freshness.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
### 🎉 First stable release
|
|
231
|
+
|
|
232
|
+
#### Core scanner
|
|
233
|
+
- **No API key required** — full functionality with local rule-based scorer
|
|
234
|
+
- **7-stage pipeline**: PyPI metadata → typosquat → CVE → download stats → release anomaly → static code analysis → risk scoring
|
|
235
|
+
- **PackageNotFoundError** — non-existent clean packages forwarded to real pip automatically
|
|
236
|
+
- **Claude AI upgrade path** — set `ANTHROPIC_API_KEY` for enhanced analysis
|
|
237
|
+
|
|
238
|
+
#### Typosquat detection
|
|
239
|
+
- Levenshtein distance engine against 8,000+ top PyPI packages (live feed, 24h cache)
|
|
240
|
+
- Keyboard-proximity, homoglyph, vowel-swap, and affix-padding checks
|
|
241
|
+
- 70+ curated known-malicious entries (requestss, colourama, langchian, etc.)
|
|
242
|
+
- `safe-pip update` — refresh threat feed on demand
|
|
243
|
+
|
|
244
|
+
#### Static code analysis (NEW)
|
|
245
|
+
- Downloads wheel/sdist **without installing** and scans Python source
|
|
246
|
+
- Detects: encoded payloads, reverse shells, data exfiltration, credential leaks
|
|
247
|
+
- Detects: hardcoded external IPs, dynamic exec/eval, install hook abuse
|
|
248
|
+
- INFO-level findings for legitimate patterns (ctypes, requests.get, private IPs)
|
|
249
|
+
|
|
250
|
+
#### Release anomaly detection (NEW)
|
|
251
|
+
- Burst pattern detection (3+ releases within 24 hours)
|
|
252
|
+
- High velocity scoring (>1 release/day)
|
|
253
|
+
- Dormancy break detection (inactive years → sudden activity = possible takeover)
|
|
254
|
+
- Maintainer reputation scoring
|
|
255
|
+
|
|
256
|
+
#### CVE analysis
|
|
257
|
+
- Real-time OSV.dev lookups — critical/high/medium/low severity
|
|
258
|
+
- Skips OSV for packages not on PyPI (eliminates false positives)
|
|
259
|
+
- Version-aware: only flags CVEs affecting the actual installed version
|
|
260
|
+
|
|
261
|
+
#### SBOM generation (NEW)
|
|
262
|
+
- `safe-pip sbom` — CycloneDX 1.4 JSON output
|
|
263
|
+
- Accepted by GitHub Dependency Review, FOSSA, Snyk, and enterprise tools
|
|
264
|
+
- Includes PURL, license, score, verdict, findings per component
|
|
265
|
+
|
|
266
|
+
#### Watch mode
|
|
267
|
+
- `safe-pip watch enable` — intercepts `pip install` system-wide
|
|
268
|
+
- **PowerShell**: function alias in profile (PS5 + PS7)
|
|
269
|
+
- **CMD**: pip.bat written beside pip.exe
|
|
270
|
+
- **Admin CMD**: UAC elevation to install system-level shim
|
|
271
|
+
- `safe-pip watch disable` — clean removal from all locations
|
|
272
|
+
- `safe-pip watch status` — shows coverage per terminal type
|
|
273
|
+
|
|
274
|
+
#### Dashboard
|
|
275
|
+
- `safe-pip dashboard` — local browser UI at localhost:7676
|
|
276
|
+
- Stats: total scans, verdict breakdown, blocked count, average score
|
|
277
|
+
- Charts: verdict donut, 30-day timeline (install/warn/block)
|
|
278
|
+
- Table: sortable, filterable, searchable scan history
|
|
279
|
+
- Auto-refreshes every 60 seconds
|
|
280
|
+
|
|
281
|
+
#### CLI improvements
|
|
282
|
+
- `safe-pip scan requirements.txt` — auto-detects requirements files
|
|
283
|
+
- `safe-pip scan-file requirements.txt` — explicit shorthand
|
|
284
|
+
- Package aliases: `sklearn→scikit-learn`, `cv2→opencv-python`, `PIL→pillow`, etc.
|
|
285
|
+
- Clean error messages — no tracebacks shown to users
|
|
286
|
+
- `--output rich|plain|json` on all scan commands
|
|
287
|
+
- `--sarif FILE` — SARIF 2.1.0 output for GitHub Security tab
|
|
288
|
+
|
|
289
|
+
#### False positive fixes
|
|
290
|
+
- `requests.get()` → INFO only (not scored)
|
|
291
|
+
- `import ctypes` → INFO only (not scored)
|
|
292
|
+
- `0.0.0.0`, `127.x`, `10.x`, `192.168.x`, RFC1918 → excluded from IP detection
|
|
293
|
+
- `compile(expression, "<string>")` → INFO (Python built-in math eval)
|
|
294
|
+
- `tensorflow-gpu`, `tensorflow-cpu`, `tensorflow-intel` → trusted (official Google)
|
|
295
|
+
- Release anomalies suppressed for trusted packages (numpy, etc.)
|
|
296
|
+
|
|
297
|
+
#### Test suite
|
|
298
|
+
- **605 tests** across 12 test files
|
|
299
|
+
- `tests/safe_packages.txt` — 31 trusted packages verified INSTALL
|
|
300
|
+
- `tests/malicious_packages.txt` — 22 attack packages verified BLOCK
|
|
301
|
+
- `tests/edge_cases.txt` — aliases, deprecated-legit, CVE-warning packages
|
|
302
|
+
- Integration tests cover full pipeline end-to-end
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## [0.x.x] — Pre-release development
|
|
307
|
+
|
|
308
|
+
Internal development iterations — see git history.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Contributing to safe-pip
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing! This document covers how to set
|
|
4
|
+
up a development environment, run tests, and submit changes.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Development setup
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Clone and install in editable mode with dev dependencies
|
|
12
|
+
git clone https://github.com/safe-pip/safe-pip
|
|
13
|
+
cd safe-pip
|
|
14
|
+
pip install -e ".[dev]"
|
|
15
|
+
|
|
16
|
+
# Set your Anthropic API key (required for scanner tests that call Claude)
|
|
17
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Running tests
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# All tests
|
|
24
|
+
pytest
|
|
25
|
+
|
|
26
|
+
# Specific file
|
|
27
|
+
pytest tests/test_cli.py -v
|
|
28
|
+
|
|
29
|
+
# With coverage
|
|
30
|
+
pip install pytest-cov
|
|
31
|
+
pytest --cov=safe_pip --cov-report=term-missing
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Linting
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install ruff
|
|
38
|
+
ruff check safe_pip/ tests/ --select E,F,W --ignore E501
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Project structure
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
safe_pip/
|
|
45
|
+
├── __init__.py # version
|
|
46
|
+
├── cli.py # Click CLI — all commands
|
|
47
|
+
├── scanner.py # Core pipeline: PyPI → typosquat → OSV → AI
|
|
48
|
+
├── typosquat.py # Levenshtein similarity engine + threat DB
|
|
49
|
+
├── osv.py # OSV CVE database client
|
|
50
|
+
├── pypistats.py # pypistats.org download stats client
|
|
51
|
+
├── policy.py # Rule evaluation engine + config loader
|
|
52
|
+
├── display.py # Rich terminal UI
|
|
53
|
+
├── db.py # SQLite persistence layer
|
|
54
|
+
├── sarif.py # SARIF 2.1.0 report generator
|
|
55
|
+
└── py.typed # PEP 561 marker
|
|
56
|
+
|
|
57
|
+
tests/
|
|
58
|
+
├── test_build.py # Package metadata + CLI smoke tests
|
|
59
|
+
├── test_cli.py # CLI command tests (Click test runner)
|
|
60
|
+
└── test_db.py # SQLite layer tests (in-memory DB)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Adding a new scanning signal
|
|
64
|
+
|
|
65
|
+
1. Add your logic to `scanner.py` (fetch data) or `typosquat.py` (similarity)
|
|
66
|
+
2. Pass the result into `_build_prompt()` so Claude sees it
|
|
67
|
+
3. Add a corresponding policy `condition` string to `policy.py`
|
|
68
|
+
4. Add a stage label to `display.STAGE_LABELS`
|
|
69
|
+
5. Write tests in `tests/test_cli.py` or a new `tests/test_<module>.py`
|
|
70
|
+
|
|
71
|
+
## Adding a new CLI command
|
|
72
|
+
|
|
73
|
+
1. Define a new `@main.command()` in `cli.py`
|
|
74
|
+
2. Add it to the `--help` assertion in `tests/test_build.py::TestCLIIntegration::test_help_flag`
|
|
75
|
+
3. Document it in `README.md` and `CHANGELOG.md`
|
|
76
|
+
|
|
77
|
+
## Submitting a pull request
|
|
78
|
+
|
|
79
|
+
1. Fork the repo and create a feature branch: `git checkout -b feat/my-feature`
|
|
80
|
+
2. Make your changes and ensure all tests pass: `pytest`
|
|
81
|
+
3. Ensure lint passes: `ruff check safe_pip/ tests/ --select E,F,W --ignore E501`
|
|
82
|
+
4. Update `CHANGELOG.md` under `[Unreleased]`
|
|
83
|
+
5. Open a PR — the CI pipeline will run automatically
|
|
84
|
+
|
|
85
|
+
## Reporting bugs
|
|
86
|
+
|
|
87
|
+
Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md).
|
|
88
|
+
Please include the output of `safe-pip --version` and the full error message.
|
|
89
|
+
|
|
90
|
+
## Security issues
|
|
91
|
+
|
|
92
|
+
Do **not** open a public issue for security vulnerabilities. Email the
|
|
93
|
+
maintainers directly — contact details are in the GitHub repository.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 safe-pip contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|