phpwarden 2.1.1__tar.gz → 2.2.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.
Files changed (84) hide show
  1. {phpwarden-2.1.1 → phpwarden-2.2.0}/PKG-INFO +86 -30
  2. {phpwarden-2.1.1 → phpwarden-2.2.0}/README.md +84 -28
  3. {phpwarden-2.1.1 → phpwarden-2.2.0}/pyproject.toml +2 -2
  4. phpwarden-2.2.0/src/phpwarden/__init__.py +9 -0
  5. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/cli.py +274 -28
  6. phpwarden-2.2.0/src/phpwarden/code.py +1813 -0
  7. phpwarden-2.2.0/src/phpwarden/code_rules.py +369 -0
  8. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/data/php-cve-feed.json +360 -37
  9. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/feed.py +62 -9
  10. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/guard.py +5 -4
  11. phpwarden-2.2.0/src/phpwarden/probe.py +902 -0
  12. phpwarden-2.2.0/src/phpwarden/rules.py +259 -0
  13. phpwarden-2.2.0/src/phpwarden/sarif.py +134 -0
  14. phpwarden-2.2.0/src/phpwarden/verdict.py +462 -0
  15. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden.egg-info/PKG-INFO +86 -30
  16. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden.egg-info/SOURCES.txt +16 -0
  17. phpwarden-2.2.0/tests/test_account_deletion.py +138 -0
  18. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_agency.py +8 -0
  19. phpwarden-2.2.0/tests/test_assess.py +152 -0
  20. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_cli_edges.py +7 -0
  21. phpwarden-2.2.0/tests/test_code.py +418 -0
  22. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_e2e_readiness.py +2 -2
  23. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_feed_license.py +2 -2
  24. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_feed_subscription.py +1 -1
  25. phpwarden-2.2.0/tests/test_free_feed.py +108 -0
  26. phpwarden-2.2.0/tests/test_kev_alert.py +97 -0
  27. phpwarden-2.2.0/tests/test_kev_gaps.py +52 -0
  28. phpwarden-2.2.0/tests/test_probe.py +196 -0
  29. phpwarden-2.2.0/tests/test_probe_cli.py +99 -0
  30. phpwarden-2.2.0/tests/test_probe_dashboard.py +312 -0
  31. phpwarden-2.2.0/tests/test_sarif.py +80 -0
  32. phpwarden-2.2.0/tests/test_shield_plan.py +36 -0
  33. phpwarden-2.2.0/tests/test_waf_dryrun.py +76 -0
  34. phpwarden-2.1.1/src/phpwarden/__init__.py +0 -8
  35. phpwarden-2.1.1/src/phpwarden/rules.py +0 -152
  36. phpwarden-2.1.1/tests/test_kev_gaps.py +0 -23
  37. {phpwarden-2.1.1 → phpwarden-2.2.0}/LICENSE +0 -0
  38. {phpwarden-2.1.1 → phpwarden-2.2.0}/setup.cfg +0 -0
  39. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/deps.py +0 -0
  40. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/detect.py +0 -0
  41. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/engine.py +0 -0
  42. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/env.py +0 -0
  43. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/integrity.py +0 -0
  44. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/report.py +0 -0
  45. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/version.py +0 -0
  46. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/vulndb.py +0 -0
  47. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden/wordpress.py +0 -0
  48. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden.egg-info/dependency_links.txt +0 -0
  49. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden.egg-info/entry_points.txt +0 -0
  50. {phpwarden-2.1.1 → phpwarden-2.2.0}/src/phpwarden.egg-info/top_level.txt +0 -0
  51. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_accounts.py +0 -0
  52. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_advisories.py +0 -0
  53. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_assignment.py +0 -0
  54. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_audit.py +0 -0
  55. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_billing.py +0 -0
  56. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_business_metrics.py +0 -0
  57. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_connect_flow.py +0 -0
  58. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_dashboard_states.py +0 -0
  59. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_deps_and_rules.py +0 -0
  60. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_detection_validation.py +0 -0
  61. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_developer.py +0 -0
  62. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_discussion.py +0 -0
  63. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_email.py +0 -0
  64. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_engine.py +0 -0
  65. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_engine_feed.py +0 -0
  66. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_fleet.py +0 -0
  67. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_google_oauth.py +0 -0
  68. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_guard_live.py +0 -0
  69. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_integrity.py +0 -0
  70. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_livefeed.py +0 -0
  71. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_mentions.py +0 -0
  72. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_reachability.py +0 -0
  73. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_saas.py +0 -0
  74. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_saas_billing.py +0 -0
  75. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_server.py +0 -0
  76. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_settings.py +0 -0
  77. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_shield.py +0 -0
  78. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_slack.py +0 -0
  79. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_sso.py +0 -0
  80. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_store_connections.py +0 -0
  81. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_team.py +0 -0
  82. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_triage.py +0 -0
  83. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_webhook_idempotency.py +0 -0
  84. {phpwarden-2.1.1 → phpwarden-2.2.0}/tests/test_wordpress_and_report.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phpwarden
3
- Version: 2.1.1
4
- Summary: PHP vulnerability scanner for Composer, WordPress and the PHP engine — findings ranked by real-world exploitation (CISA KEV, then EPSS), not CVSS. Offline, zero deps.
3
+ Version: 2.2.0
4
+ Summary: PHP vulnerability scanner for the PHP engine, Composer, WordPress, your own code and the live site. Ranked by real-world exploitation (CISA KEV, then EPSS), not CVSS. Offline, zero dependencies.
5
5
  Author: oroma-alfred
6
6
  License-Expression: Apache-2.0
7
7
  Project-URL: Homepage, https://phpwarden.io
@@ -23,8 +23,8 @@ Dynamic: license-file
23
23
  # phpwarden
24
24
 
25
25
  **PHP vulnerability scanner that ranks findings by real-world exploitation,
26
- not CVSS.** Of about 19,500 PHP vulnerabilities in the feed, 41 are known to be
27
- exploited (CISA KEV, as of 2026-11-13). phpwarden puts those first, then sorts
26
+ not CVSS.** Of about 19,500 PHP vulnerabilities in the feed, 54 are known to be
27
+ exploited (CISA KEV, as of 2026-11-14). phpwarden puts those first, then sorts
28
28
  by EPSS probability, then CVSS — so a `composer.lock` with 30 findings tells
29
29
  you which one to fix today.
30
30
 
@@ -59,6 +59,9 @@ $ phpwarden deps composer.lock
59
59
  | Composer dependency CVEs | `deps` | Snyk, `composer audit` |
60
60
  | WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
61
61
  | WordPress file integrity (wordpress.org checksums, injected files) | `integrity` | Wordfence CLI malware-scan (partly) |
62
+ | Your own PHP code (75 rules) | `code` | — (not a PHPStan/Psalm replacement) |
63
+ | Live site from outside: exposed files, debug endpoints | `probe` | — |
64
+ | One ranked verdict across all of the above | `assess` | — |
62
65
  | Exploitation signatures in logs | `detect` | SIEM content |
63
66
  | WAF blocking rules (PHP-specific) | `rules` | the gap a generic WAF leaves |
64
67
  | Fleet exposure dashboard | `report` | the CNAPP's PHP view |
@@ -79,7 +82,9 @@ $ phpwarden deps composer.lock
79
82
  configuration is reported as *possibly* exposed with the exact condition to
80
83
  confirm — not flagged as a certain hit, and not silently dismissed.
81
84
  - **Offline by default.** The feed ships inside the package. Nothing leaves the
82
- machine unless you opt in to `--report-to` or `feed update`.
85
+ machine unless you opt in to `--report-to` or `feed update`. Two commands
86
+ need the network by nature: `integrity` fetches checksums from wordpress.org,
87
+ and `probe` talks only to the site you point it at.
83
88
 
84
89
  ## Install
85
90
 
@@ -87,7 +92,7 @@ $ phpwarden deps composer.lock
87
92
  composer require --dev phpwarden/phpwarden # PHP projects → vendor/bin/phpwarden
88
93
  pipx install phpwarden # Python-tooling box
89
94
  brew install oroma-alfred/tap/phpwarden # macOS / Linux with Homebrew
90
- python3 phpwarden.pyz scan # single file, no install
95
+ curl -LO https://phpwarden.io/phpwarden.pyz && python3 phpwarden.pyz scan # single file, no install
91
96
  docker run --rm phpwarden/phpwarden scan --version 8.3.10 --sapi fpm --os linux # Docker Hub, amd64 + arm64
92
97
  ```
93
98
 
@@ -112,17 +117,60 @@ phpwarden wordpress /var/www/html # WordPress core, plugins, themes
112
117
  phpwarden integrity /var/www/html # tampered or injected files (vs wordpress.org checksums)
113
118
  phpwarden detect /var/log/nginx/access.log # exploit signatures in a log
114
119
  phpwarden rules --format nginx --out php-block.conf # PHP-specific WAF rules (or modsecurity)
120
+ phpwarden rules --format nginx --dry-run # the same rules, log-only: nothing is blocked
121
+ phpwarden rules --replay /var/log/nginx/access.log # what the rules would have blocked in an existing log
122
+ phpwarden probe https://example.com # live check of a site you own: exposed files, debug endpoints, versions vs the feed
123
+ phpwarden code . # your own PHP: 75 high-signal rules, 74 on by default (injection, unserialize, WordPress handlers)
124
+ phpwarden assess . --url https://example.com --log access.log # one ranked verdict; --out / --baseline shows what is fixed and verified
115
125
  phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
116
126
  phpwarden feed status # which feed is active, how many advisories
127
+ phpwarden feed update # pull a newer feed: free weekly snapshot, or the maintained channel with a key
117
128
  ```
118
129
 
130
+ `scan`, `deps`, `wordpress`, `probe` and `code` also take `--format sarif`
131
+ (SARIF 2.1.0, for GitHub code scanning and other CI dashboards).
132
+
133
+ `probe`, `code`, `assess`, `--format sarif`, `rules --dry-run` / `--replay` and
134
+ the no-key `feed update` are new in 2.2.0.
135
+
119
136
  Every command takes `--json`. Exit codes are shared across subcommands and
120
- built for gating: `0` clean, `1` exposed / vulnerable / signature hits,
137
+ built for gating: `0` clean, `1` exposed / vulnerable / signature hits / tampered
138
+ files / high or critical `code` and `probe` findings (`probe` also on any
139
+ known-exploited finding) / anything to act on now or this week (`assess`),
121
140
  `2` possibly exposed (`scan`), `3` PHP version unknown (`scan`), `4` input
122
141
  error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
123
142
  error. Full table: `phpwarden --help`.
124
143
 
125
144
 
145
+ ## New in 2.2.0
146
+
147
+ - **`phpwarden probe URL`**: a live check of a running site using GET and HEAD
148
+ requests only. It looks for exposed files (`.env`, `.git`, backups), open debug
149
+ endpoints, and the WordPress, plugin and PHP versions the site reveals, and
150
+ matches those against the feed. Active checks run only after you prove the
151
+ site is yours (DNS TXT record, a file, or a meta tag; `--print-token` shows
152
+ how). Without that it reads the homepage and stops.
153
+ - **`phpwarden code PATH`**: 75 rules (74 on by default) for your own PHP code, following request
154
+ data to `eval`, shell commands, SQL, `unserialize`, includes and redirects.
155
+ It follows data through one level of same-file helper, not across files, and
156
+ does not replace PHPStan or Psalm. `phpwarden code --list-rules` lists every rule.
157
+ - **`phpwarden assess`**: one ranked list from every view of a site: installed
158
+ versions, your code, the live site from outside, and the access log, with the
159
+ reason for each item ("exploited, reachable, visible from outside, already
160
+ fetched"). `--out` then `--baseline` reports what is fixed and verified.
161
+ - **`--format sarif`** on `scan`, `deps`, `wordpress`, `probe` and `code`.
162
+ - **`rules --dry-run` and `rules --replay ACCESS_LOG`**: see what the WAF rules
163
+ would block before they block anything.
164
+ - **Free weekly feed updates**: `phpwarden feed update` without a key pulls a
165
+ weekly snapshot of the maintained feed. A vulnerability newly listed as known
166
+ exploited is released to the free channel straight away.
167
+ - **Coverage commitment**: in-scope CISA KEV entries are in the maintained feed
168
+ within 24 hours of listing, checked on every rebuild and published at
169
+ https://phpwarden.io/feeds/kev-status.json. Details: https://phpwarden.io/sla
170
+ - **Editors**: a VS Code extension in `integrations/vscode` (build and install
171
+ the `.vsix` from that folder; not on the Marketplace yet) and PhpStorm External Tool steps in
172
+ `integrations/phpstorm`.
173
+
126
174
  ## Replacing Wordfence CLI
127
175
 
128
176
  Wordfence closed new licences for its CLI in October 2026; Free licences stopped working on 2026-10-14 and Premium
@@ -140,8 +188,9 @@ Full guide: https://phpwarden.io/compare/phpwarden-vs-wordfence-cli#replace
140
188
  ## Send results to the dashboard
141
189
 
142
190
  Any `scan`, `deps`, `wordpress` or `detect` run can post its result to
143
- [app.phpwarden.io](https://app.phpwarden.io) for history across hosts and
144
- alerts when a finding you already carry lands in KEV:
191
+ [app.phpwarden.io](https://app.phpwarden.io) for history across hosts.
192
+ It marks findings that become known-exploited; email and webhook alerts are on
193
+ Pro and Team, Slack on Team:
145
194
 
146
195
  ```bash
147
196
  phpwarden scan --report-to https://app.phpwarden.io/v1/reports --report-token <token>
@@ -186,7 +235,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
186
235
  ```yaml
187
236
  repos:
188
237
  - repo: https://github.com/oroma-alfred/phpwarden
189
- rev: main # pin to a tag with `pre-commit autoupdate`
238
+ rev: v2.1.0 # pin to a tag with `pre-commit autoupdate`
190
239
  hooks:
191
240
  - id: phpwarden-deps
192
241
  # args: ["--no-reachability"] # faster: skip the reachability pass
@@ -197,44 +246,51 @@ timer recipes are in [`docs/recipes.md`](docs/recipes.md).
197
246
 
198
247
  ## The feed
199
248
 
200
- The bundled community feed (currently 2026.10.31.2) works forever, offline:
249
+ The bundled community feed (2026.11.13.3 in this release) works forever, offline:
201
250
 
202
251
  - **~21,300 advisories**: ~8,800 Composer (from [OSV.dev](https://osv.dev) and
203
252
  [FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
204
253
  ~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
205
254
  and 145 PHP engine/FPM CVEs, including hand-curated entries with per-branch
206
- fixed versions, exposure conditions and detection signatures. 19,400+ carry an
207
- EPSS score; 42 were in KEV when this feed was built.
255
+ fixed versions, exposure conditions and detection signatures. About 19,700 carry an
256
+ EPSS score; 41 distinct vulnerabilities were in KEV when this feed was built.
208
257
  - **License-independent by construction.** Every source is open or
209
- public-domain (OSV, NVD, FriendsOfPHP, CISA KEV, FIRST EPSS). No commercial
258
+ public-domain (OSV, NVD, FriendsOfPHP, php-src release notes, CISA KEV, FIRST EPSS). No commercial
210
259
  vendor's feed is used, so coverage cannot be revoked or priced away.
211
260
  - Every build passes `feedbuilder/check_feed.py` (no open-ended range with a
212
261
  known fix, no fixed version inside its own affected range, no duplicate
213
262
  (package|slug, cve)). The feed is assembled by `feedbuilder/` from a curated
214
263
  base plus bulk imports and enrichment; curated entries always win.
215
264
 
216
- A [phpwarden.io](https://phpwarden.io) subscription unlocks the maintained
217
- channel — new advisories and KEV/EPSS refreshes as they are triaged — via
218
- `PHPWARDEN_LICENSE=... phpwarden feed update`. Without a key, phpwarden runs
219
- the community feed and says so.
220
-
221
- Curated engine CVEs with detection signatures:
222
-
223
- | CVE | What | Scope |
224
- |-----|------|-------|
225
- | CVE-2019-11043 | PHP-FPM env_path_info RCE | FPM + nginx, config-gated |
226
- | CVE-2023-3824 | PHAR stack overflow RCE | any SAPI, untrusted PHAR |
227
- | CVE-2024-4577 | PHP-CGI argument injection | Windows + CGI |
228
- | CVE-2024-8926 / 8927 | cgi.force_redirect bypass | CGI / FPM |
229
- | CVE-2024-11236 | DB-quoter integer overflow | any SAPI |
230
- | CVE-2026-109618 / 109619 | php2shell FPM memory RCE | any SAPI |
265
+ Updating the feed is optional and has two channels:
266
+
267
+ - **Free weekly.** `phpwarden feed update` with no key downloads a weekly
268
+ snapshot of the maintained feed. A vulnerability newly listed as known
269
+ exploited is released to this channel straight away. If the server cannot be
270
+ reached, phpwarden keeps the feed it has and says so. Set
271
+ `PHPWARDEN_FREE_FEED_URL=off` to stop the no-key update from calling out.
272
+ - **Maintained.** A [phpwarden.io](https://phpwarden.io) subscription gives a
273
+ license key; `PHPWARDEN_LICENSE=... phpwarden feed update` then pulls the
274
+ channel that is rebuilt twice a day.
275
+
276
+ Examples of hand-curated engine CVEs:
277
+
278
+ | CVE | What | Scope | Log signature |
279
+ |-----|------|-------|---------------|
280
+ | CVE-2019-11043 | PHP-FPM env_path_info RCE | FPM + nginx, config-gated | yes |
281
+ | CVE-2023-3824 | PHAR stack overflow RCE | any SAPI, untrusted PHAR | no |
282
+ | CVE-2024-4577 | PHP-CGI argument injection | Windows + CGI | yes |
283
+ | CVE-2024-8926 | cgi.force_redirect bypass | Windows + CGI | no |
284
+ | CVE-2024-8927 | cgi.force_redirect bypass | CGI / FPM | no |
285
+ | CVE-2024-11236 | DB-quoter integer overflow | any SAPI | no |
286
+ | CVE-2026-109618 / 109619 | php2shell FPM memory RCE | any SAPI | yes |
231
287
 
232
288
  ## Continuous guard
233
289
 
234
290
  `scan` and `detect` are point-in-time. For always-on containment of
235
291
  in-progress php2shell-class attacks (OOM on a worker, then that worker reused),
236
292
  run `phpwarden-guard`, which boots in dry-run and never acts until you promote
237
- it after a burn-in. See the guard's DESIGN document.
293
+ it after a burn-in. See [`VALIDATION.md`](VALIDATION.md) for what was validated.
238
294
 
239
295
  ## Honest limits
240
296
 
@@ -1,8 +1,8 @@
1
1
  # phpwarden
2
2
 
3
3
  **PHP vulnerability scanner that ranks findings by real-world exploitation,
4
- not CVSS.** Of about 19,500 PHP vulnerabilities in the feed, 41 are known to be
5
- exploited (CISA KEV, as of 2026-11-13). phpwarden puts those first, then sorts
4
+ not CVSS.** Of about 19,500 PHP vulnerabilities in the feed, 54 are known to be
5
+ exploited (CISA KEV, as of 2026-11-14). phpwarden puts those first, then sorts
6
6
  by EPSS probability, then CVSS — so a `composer.lock` with 30 findings tells
7
7
  you which one to fix today.
8
8
 
@@ -37,6 +37,9 @@ $ phpwarden deps composer.lock
37
37
  | Composer dependency CVEs | `deps` | Snyk, `composer audit` |
38
38
  | WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
39
39
  | WordPress file integrity (wordpress.org checksums, injected files) | `integrity` | Wordfence CLI malware-scan (partly) |
40
+ | Your own PHP code (75 rules) | `code` | — (not a PHPStan/Psalm replacement) |
41
+ | Live site from outside: exposed files, debug endpoints | `probe` | — |
42
+ | One ranked verdict across all of the above | `assess` | — |
40
43
  | Exploitation signatures in logs | `detect` | SIEM content |
41
44
  | WAF blocking rules (PHP-specific) | `rules` | the gap a generic WAF leaves |
42
45
  | Fleet exposure dashboard | `report` | the CNAPP's PHP view |
@@ -57,7 +60,9 @@ $ phpwarden deps composer.lock
57
60
  configuration is reported as *possibly* exposed with the exact condition to
58
61
  confirm — not flagged as a certain hit, and not silently dismissed.
59
62
  - **Offline by default.** The feed ships inside the package. Nothing leaves the
60
- machine unless you opt in to `--report-to` or `feed update`.
63
+ machine unless you opt in to `--report-to` or `feed update`. Two commands
64
+ need the network by nature: `integrity` fetches checksums from wordpress.org,
65
+ and `probe` talks only to the site you point it at.
61
66
 
62
67
  ## Install
63
68
 
@@ -65,7 +70,7 @@ $ phpwarden deps composer.lock
65
70
  composer require --dev phpwarden/phpwarden # PHP projects → vendor/bin/phpwarden
66
71
  pipx install phpwarden # Python-tooling box
67
72
  brew install oroma-alfred/tap/phpwarden # macOS / Linux with Homebrew
68
- python3 phpwarden.pyz scan # single file, no install
73
+ curl -LO https://phpwarden.io/phpwarden.pyz && python3 phpwarden.pyz scan # single file, no install
69
74
  docker run --rm phpwarden/phpwarden scan --version 8.3.10 --sapi fpm --os linux # Docker Hub, amd64 + arm64
70
75
  ```
71
76
 
@@ -90,17 +95,60 @@ phpwarden wordpress /var/www/html # WordPress core, plugins, themes
90
95
  phpwarden integrity /var/www/html # tampered or injected files (vs wordpress.org checksums)
91
96
  phpwarden detect /var/log/nginx/access.log # exploit signatures in a log
92
97
  phpwarden rules --format nginx --out php-block.conf # PHP-specific WAF rules (or modsecurity)
98
+ phpwarden rules --format nginx --dry-run # the same rules, log-only: nothing is blocked
99
+ phpwarden rules --replay /var/log/nginx/access.log # what the rules would have blocked in an existing log
100
+ phpwarden probe https://example.com # live check of a site you own: exposed files, debug endpoints, versions vs the feed
101
+ phpwarden code . # your own PHP: 75 high-signal rules, 74 on by default (injection, unserialize, WordPress handlers)
102
+ phpwarden assess . --url https://example.com --log access.log # one ranked verdict; --out / --baseline shows what is fixed and verified
93
103
  phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
94
104
  phpwarden feed status # which feed is active, how many advisories
105
+ phpwarden feed update # pull a newer feed: free weekly snapshot, or the maintained channel with a key
95
106
  ```
96
107
 
108
+ `scan`, `deps`, `wordpress`, `probe` and `code` also take `--format sarif`
109
+ (SARIF 2.1.0, for GitHub code scanning and other CI dashboards).
110
+
111
+ `probe`, `code`, `assess`, `--format sarif`, `rules --dry-run` / `--replay` and
112
+ the no-key `feed update` are new in 2.2.0.
113
+
97
114
  Every command takes `--json`. Exit codes are shared across subcommands and
98
- built for gating: `0` clean, `1` exposed / vulnerable / signature hits,
115
+ built for gating: `0` clean, `1` exposed / vulnerable / signature hits / tampered
116
+ files / high or critical `code` and `probe` findings (`probe` also on any
117
+ known-exploited finding) / anything to act on now or this week (`assess`),
99
118
  `2` possibly exposed (`scan`), `3` PHP version unknown (`scan`), `4` input
100
119
  error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
101
120
  error. Full table: `phpwarden --help`.
102
121
 
103
122
 
123
+ ## New in 2.2.0
124
+
125
+ - **`phpwarden probe URL`**: a live check of a running site using GET and HEAD
126
+ requests only. It looks for exposed files (`.env`, `.git`, backups), open debug
127
+ endpoints, and the WordPress, plugin and PHP versions the site reveals, and
128
+ matches those against the feed. Active checks run only after you prove the
129
+ site is yours (DNS TXT record, a file, or a meta tag; `--print-token` shows
130
+ how). Without that it reads the homepage and stops.
131
+ - **`phpwarden code PATH`**: 75 rules (74 on by default) for your own PHP code, following request
132
+ data to `eval`, shell commands, SQL, `unserialize`, includes and redirects.
133
+ It follows data through one level of same-file helper, not across files, and
134
+ does not replace PHPStan or Psalm. `phpwarden code --list-rules` lists every rule.
135
+ - **`phpwarden assess`**: one ranked list from every view of a site: installed
136
+ versions, your code, the live site from outside, and the access log, with the
137
+ reason for each item ("exploited, reachable, visible from outside, already
138
+ fetched"). `--out` then `--baseline` reports what is fixed and verified.
139
+ - **`--format sarif`** on `scan`, `deps`, `wordpress`, `probe` and `code`.
140
+ - **`rules --dry-run` and `rules --replay ACCESS_LOG`**: see what the WAF rules
141
+ would block before they block anything.
142
+ - **Free weekly feed updates**: `phpwarden feed update` without a key pulls a
143
+ weekly snapshot of the maintained feed. A vulnerability newly listed as known
144
+ exploited is released to the free channel straight away.
145
+ - **Coverage commitment**: in-scope CISA KEV entries are in the maintained feed
146
+ within 24 hours of listing, checked on every rebuild and published at
147
+ https://phpwarden.io/feeds/kev-status.json. Details: https://phpwarden.io/sla
148
+ - **Editors**: a VS Code extension in `integrations/vscode` (build and install
149
+ the `.vsix` from that folder; not on the Marketplace yet) and PhpStorm External Tool steps in
150
+ `integrations/phpstorm`.
151
+
104
152
  ## Replacing Wordfence CLI
105
153
 
106
154
  Wordfence closed new licences for its CLI in October 2026; Free licences stopped working on 2026-10-14 and Premium
@@ -118,8 +166,9 @@ Full guide: https://phpwarden.io/compare/phpwarden-vs-wordfence-cli#replace
118
166
  ## Send results to the dashboard
119
167
 
120
168
  Any `scan`, `deps`, `wordpress` or `detect` run can post its result to
121
- [app.phpwarden.io](https://app.phpwarden.io) for history across hosts and
122
- alerts when a finding you already carry lands in KEV:
169
+ [app.phpwarden.io](https://app.phpwarden.io) for history across hosts.
170
+ It marks findings that become known-exploited; email and webhook alerts are on
171
+ Pro and Team, Slack on Team:
123
172
 
124
173
  ```bash
125
174
  phpwarden scan --report-to https://app.phpwarden.io/v1/reports --report-token <token>
@@ -164,7 +213,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
164
213
  ```yaml
165
214
  repos:
166
215
  - repo: https://github.com/oroma-alfred/phpwarden
167
- rev: main # pin to a tag with `pre-commit autoupdate`
216
+ rev: v2.1.0 # pin to a tag with `pre-commit autoupdate`
168
217
  hooks:
169
218
  - id: phpwarden-deps
170
219
  # args: ["--no-reachability"] # faster: skip the reachability pass
@@ -175,44 +224,51 @@ timer recipes are in [`docs/recipes.md`](docs/recipes.md).
175
224
 
176
225
  ## The feed
177
226
 
178
- The bundled community feed (currently 2026.10.31.2) works forever, offline:
227
+ The bundled community feed (2026.11.13.3 in this release) works forever, offline:
179
228
 
180
229
  - **~21,300 advisories**: ~8,800 Composer (from [OSV.dev](https://osv.dev) and
181
230
  [FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
182
231
  ~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
183
232
  and 145 PHP engine/FPM CVEs, including hand-curated entries with per-branch
184
- fixed versions, exposure conditions and detection signatures. 19,400+ carry an
185
- EPSS score; 42 were in KEV when this feed was built.
233
+ fixed versions, exposure conditions and detection signatures. About 19,700 carry an
234
+ EPSS score; 41 distinct vulnerabilities were in KEV when this feed was built.
186
235
  - **License-independent by construction.** Every source is open or
187
- public-domain (OSV, NVD, FriendsOfPHP, CISA KEV, FIRST EPSS). No commercial
236
+ public-domain (OSV, NVD, FriendsOfPHP, php-src release notes, CISA KEV, FIRST EPSS). No commercial
188
237
  vendor's feed is used, so coverage cannot be revoked or priced away.
189
238
  - Every build passes `feedbuilder/check_feed.py` (no open-ended range with a
190
239
  known fix, no fixed version inside its own affected range, no duplicate
191
240
  (package|slug, cve)). The feed is assembled by `feedbuilder/` from a curated
192
241
  base plus bulk imports and enrichment; curated entries always win.
193
242
 
194
- A [phpwarden.io](https://phpwarden.io) subscription unlocks the maintained
195
- channel — new advisories and KEV/EPSS refreshes as they are triaged — via
196
- `PHPWARDEN_LICENSE=... phpwarden feed update`. Without a key, phpwarden runs
197
- the community feed and says so.
198
-
199
- Curated engine CVEs with detection signatures:
200
-
201
- | CVE | What | Scope |
202
- |-----|------|-------|
203
- | CVE-2019-11043 | PHP-FPM env_path_info RCE | FPM + nginx, config-gated |
204
- | CVE-2023-3824 | PHAR stack overflow RCE | any SAPI, untrusted PHAR |
205
- | CVE-2024-4577 | PHP-CGI argument injection | Windows + CGI |
206
- | CVE-2024-8926 / 8927 | cgi.force_redirect bypass | CGI / FPM |
207
- | CVE-2024-11236 | DB-quoter integer overflow | any SAPI |
208
- | CVE-2026-109618 / 109619 | php2shell FPM memory RCE | any SAPI |
243
+ Updating the feed is optional and has two channels:
244
+
245
+ - **Free weekly.** `phpwarden feed update` with no key downloads a weekly
246
+ snapshot of the maintained feed. A vulnerability newly listed as known
247
+ exploited is released to this channel straight away. If the server cannot be
248
+ reached, phpwarden keeps the feed it has and says so. Set
249
+ `PHPWARDEN_FREE_FEED_URL=off` to stop the no-key update from calling out.
250
+ - **Maintained.** A [phpwarden.io](https://phpwarden.io) subscription gives a
251
+ license key; `PHPWARDEN_LICENSE=... phpwarden feed update` then pulls the
252
+ channel that is rebuilt twice a day.
253
+
254
+ Examples of hand-curated engine CVEs:
255
+
256
+ | CVE | What | Scope | Log signature |
257
+ |-----|------|-------|---------------|
258
+ | CVE-2019-11043 | PHP-FPM env_path_info RCE | FPM + nginx, config-gated | yes |
259
+ | CVE-2023-3824 | PHAR stack overflow RCE | any SAPI, untrusted PHAR | no |
260
+ | CVE-2024-4577 | PHP-CGI argument injection | Windows + CGI | yes |
261
+ | CVE-2024-8926 | cgi.force_redirect bypass | Windows + CGI | no |
262
+ | CVE-2024-8927 | cgi.force_redirect bypass | CGI / FPM | no |
263
+ | CVE-2024-11236 | DB-quoter integer overflow | any SAPI | no |
264
+ | CVE-2026-109618 / 109619 | php2shell FPM memory RCE | any SAPI | yes |
209
265
 
210
266
  ## Continuous guard
211
267
 
212
268
  `scan` and `detect` are point-in-time. For always-on containment of
213
269
  in-progress php2shell-class attacks (OOM on a worker, then that worker reused),
214
270
  run `phpwarden-guard`, which boots in dry-run and never acts until you promote
215
- it after a burn-in. See the guard's DESIGN document.
271
+ it after a burn-in. See [`VALIDATION.md`](VALIDATION.md) for what was validated.
216
272
 
217
273
  ## Honest limits
218
274
 
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "phpwarden"
7
- version = "2.1.1"
8
- description = "PHP vulnerability scanner for Composer, WordPress and the PHP engine — findings ranked by real-world exploitation (CISA KEV, then EPSS), not CVSS. Offline, zero deps."
7
+ version = "2.2.0"
8
+ description = "PHP vulnerability scanner for the PHP engine, Composer, WordPress, your own code and the live site. Ranked by real-world exploitation (CISA KEV, then EPSS), not CVSS. Offline, zero dependencies."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
11
11
  license = "Apache-2.0"
@@ -0,0 +1,9 @@
1
+ """phpwarden: a PHP vulnerability scanner.
2
+
3
+ It checks the PHP engine, Composer dependencies, WordPress, your own code and
4
+ the live site against a feed of known vulnerabilities, ranks findings by
5
+ real-world exploitation (CISA KEV, then EPSS, then CVSS), and can watch PHP-FPM
6
+ for active exploitation. The engine and the bundled feed are open; free users
7
+ get weekly feed updates, and a subscription adds the feed rebuilt twice a day.
8
+ """
9
+ __version__ = "2.2.0"