phpwarden 2.1.0__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 (83) hide show
  1. {phpwarden-2.1.0 → phpwarden-2.2.0}/PKG-INFO +114 -34
  2. {phpwarden-2.1.0 → phpwarden-2.2.0}/README.md +112 -32
  3. {phpwarden-2.1.0 → phpwarden-2.2.0}/pyproject.toml +2 -2
  4. phpwarden-2.2.0/src/phpwarden/__init__.py +9 -0
  5. {phpwarden-2.1.0 → 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.0 → phpwarden-2.2.0}/src/phpwarden/data/php-cve-feed.json +20335 -19726
  9. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/feed.py +62 -9
  10. {phpwarden-2.1.0 → 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.0 → phpwarden-2.2.0}/src/phpwarden.egg-info/PKG-INFO +114 -34
  16. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden.egg-info/SOURCES.txt +19 -0
  17. phpwarden-2.2.0/tests/test_account_deletion.py +138 -0
  18. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_advisories.py +123 -2
  19. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_agency.py +71 -1
  20. phpwarden-2.2.0/tests/test_assess.py +152 -0
  21. phpwarden-2.2.0/tests/test_business_metrics.py +45 -0
  22. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_cli_edges.py +7 -0
  23. phpwarden-2.2.0/tests/test_code.py +418 -0
  24. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_e2e_readiness.py +2 -2
  25. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_feed_license.py +2 -2
  26. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_feed_subscription.py +1 -1
  27. phpwarden-2.2.0/tests/test_free_feed.py +108 -0
  28. phpwarden-2.2.0/tests/test_kev_alert.py +97 -0
  29. phpwarden-2.2.0/tests/test_kev_gaps.py +52 -0
  30. phpwarden-2.2.0/tests/test_livefeed.py +76 -0
  31. phpwarden-2.2.0/tests/test_probe.py +196 -0
  32. phpwarden-2.2.0/tests/test_probe_cli.py +99 -0
  33. phpwarden-2.2.0/tests/test_probe_dashboard.py +312 -0
  34. phpwarden-2.2.0/tests/test_sarif.py +80 -0
  35. phpwarden-2.2.0/tests/test_shield_plan.py +36 -0
  36. phpwarden-2.2.0/tests/test_waf_dryrun.py +76 -0
  37. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_wordpress_and_report.py +2 -1
  38. phpwarden-2.1.0/src/phpwarden/__init__.py +0 -8
  39. phpwarden-2.1.0/src/phpwarden/rules.py +0 -152
  40. {phpwarden-2.1.0 → phpwarden-2.2.0}/LICENSE +0 -0
  41. {phpwarden-2.1.0 → phpwarden-2.2.0}/setup.cfg +0 -0
  42. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/deps.py +0 -0
  43. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/detect.py +0 -0
  44. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/engine.py +0 -0
  45. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/env.py +0 -0
  46. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/integrity.py +0 -0
  47. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/report.py +0 -0
  48. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/version.py +0 -0
  49. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/vulndb.py +0 -0
  50. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden/wordpress.py +0 -0
  51. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden.egg-info/dependency_links.txt +0 -0
  52. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden.egg-info/entry_points.txt +0 -0
  53. {phpwarden-2.1.0 → phpwarden-2.2.0}/src/phpwarden.egg-info/top_level.txt +0 -0
  54. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_accounts.py +0 -0
  55. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_assignment.py +0 -0
  56. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_audit.py +0 -0
  57. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_billing.py +0 -0
  58. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_connect_flow.py +0 -0
  59. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_dashboard_states.py +0 -0
  60. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_deps_and_rules.py +0 -0
  61. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_detection_validation.py +0 -0
  62. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_developer.py +0 -0
  63. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_discussion.py +0 -0
  64. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_email.py +0 -0
  65. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_engine.py +0 -0
  66. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_engine_feed.py +0 -0
  67. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_fleet.py +0 -0
  68. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_google_oauth.py +0 -0
  69. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_guard_live.py +0 -0
  70. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_integrity.py +0 -0
  71. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_mentions.py +0 -0
  72. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_reachability.py +0 -0
  73. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_saas.py +0 -0
  74. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_saas_billing.py +0 -0
  75. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_server.py +0 -0
  76. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_settings.py +0 -0
  77. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_shield.py +0 -0
  78. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_slack.py +0 -0
  79. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_sso.py +0 -0
  80. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_store_connections.py +0 -0
  81. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_team.py +0 -0
  82. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_triage.py +0 -0
  83. {phpwarden-2.1.0 → phpwarden-2.2.0}/tests/test_webhook_idempotency.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phpwarden
3
- Version: 2.1.0
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 the ~21,100 PHP advisories in the feed, 43 are actually being
27
- exploited (CISA KEV, as of 2026-11-12). 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
 
@@ -32,6 +32,13 @@ It scans the PHP engine/FPM version, Composer dependencies and WordPress
32
32
  installs against one bundled, offline advisory feed. Zero runtime dependencies
33
33
  (Python 3.8+), Apache-2.0.
34
34
 
35
+ **Reproducible benchmark:** on four real PHP stacks, phpwarden found 295 of 295
36
+ known vulnerabilities, `composer audit` 286 and OSV.dev 262. Of 297 findings,
37
+ 100 were rated critical or high and 4 were actually exploited or likely to be;
38
+ phpwarden ranked each of those first or second in its project. Method, numbers
39
+ and a script to rerun it:
40
+ [the benchmark write-up](https://phpwarden.io/blog/phpwarden-vs-composer-audit-osv-2026-11).
41
+
35
42
  ```bash
36
43
  $ phpwarden deps composer.lock
37
44
  18 vulnerable package(s) in composer.lock; 0 actively exploited (KEV). Ranked by exploitation risk, then reachability:
@@ -51,6 +58,10 @@ $ phpwarden deps composer.lock
51
58
  | PHP engine / FPM CVE exposure | `scan` | image & network scanners |
52
59
  | Composer dependency CVEs | `deps` | Snyk, `composer audit` |
53
60
  | WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
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` | — |
54
65
  | Exploitation signatures in logs | `detect` | SIEM content |
55
66
  | WAF blocking rules (PHP-specific) | `rules` | the gap a generic WAF leaves |
56
67
  | Fleet exposure dashboard | `report` | the CNAPP's PHP view |
@@ -71,15 +82,18 @@ $ phpwarden deps composer.lock
71
82
  configuration is reported as *possibly* exposed with the exact condition to
72
83
  confirm — not flagged as a certain hit, and not silently dismissed.
73
84
  - **Offline by default.** The feed ships inside the package. Nothing leaves the
74
- 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.
75
88
 
76
89
  ## Install
77
90
 
78
91
  ```bash
79
92
  composer require --dev phpwarden/phpwarden # PHP projects → vendor/bin/phpwarden
80
93
  pipx install phpwarden # Python-tooling box
81
- python3 phpwarden.pyz scan # single file, no install
82
- docker build -t phpwarden . && docker run --rm phpwarden scan --version 8.3.10 --sapi fpm --os linux
94
+ brew install oroma-alfred/tap/phpwarden # macOS / Linux with Homebrew
95
+ curl -LO https://phpwarden.io/phpwarden.pyz && python3 phpwarden.pyz scan # single file, no install
96
+ docker run --rm phpwarden/phpwarden scan --version 8.3.10 --sapi fpm --os linux # Docker Hub, amd64 + arm64
83
97
  ```
84
98
 
85
99
  On modern macOS / Debian / Ubuntu a plain `pip install phpwarden` into the
@@ -90,8 +104,8 @@ system Python is blocked by [PEP 668](https://peps.python.org/pep-0668/); use
90
104
  python3 -m venv ~/.venvs/phpwarden && ~/.venvs/phpwarden/bin/pip install phpwarden
91
105
  ```
92
106
 
93
- Docker usage (mounted projects, CI, dashboard reporting) is in
94
- [`docs/docker.md`](docs/docker.md).
107
+ Docker usage (mounted projects, exit codes, CI, dashboard reporting) is on the
108
+ image's [Docker Hub page](https://hub.docker.com/r/phpwarden/phpwarden).
95
109
 
96
110
  ## Use
97
111
 
@@ -103,21 +117,80 @@ phpwarden wordpress /var/www/html # WordPress core, plugins, themes
103
117
  phpwarden integrity /var/www/html # tampered or injected files (vs wordpress.org checksums)
104
118
  phpwarden detect /var/log/nginx/access.log # exploit signatures in a log
105
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
106
125
  phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
107
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
108
128
  ```
109
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
+
110
136
  Every command takes `--json`. Exit codes are shared across subcommands and
111
- 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`),
112
140
  `2` possibly exposed (`scan`), `3` PHP version unknown (`scan`), `4` input
113
141
  error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
114
142
  error. Full table: `phpwarden --help`.
115
143
 
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
+
174
+ ## Replacing Wordfence CLI
175
+
176
+ Wordfence closed new licences for its CLI in October 2026; Free licences stopped working on 2026-10-14 and Premium
177
+ licences are not renewed. phpwarden runs the same way (on the server, no plugin in the site) and covers:
178
+
179
+ | Wordfence CLI | phpwarden | Notes |
180
+ |---|---|---|
181
+ | `wordfence vuln-scan /path` | `phpwarden wordpress /path` | Same job, ranked by real-world exploitation; JSON/SARIF and CI exit codes. Also scans Composer and the PHP engine. |
182
+ | `wordfence malware-scan /path` | `phpwarden integrity /path` | Compares core and wordpress.org plugin files with official checksums; flags injected files, PHP in uploads and common backdoor code. **Not a signature scanner**: premium plugins and themes cannot be verified and are listed as such. |
183
+ | `wordfence remediate` | – | Reinstall the flagged component with WP-CLI. |
184
+ | `wordfence db-scan` | – | Not covered. |
185
+
186
+ Full guide: https://phpwarden.io/compare/phpwarden-vs-wordfence-cli#replace
187
+
116
188
  ## Send results to the dashboard
117
189
 
118
190
  Any `scan`, `deps`, `wordpress` or `detect` run can post its result to
119
- [app.phpwarden.io](https://app.phpwarden.io) for history across hosts and
120
- 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:
121
194
 
122
195
  ```bash
123
196
  phpwarden scan --report-to https://app.phpwarden.io/v1/reports --report-token <token>
@@ -162,7 +235,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
162
235
  ```yaml
163
236
  repos:
164
237
  - repo: https://github.com/oroma-alfred/phpwarden
165
- rev: main # pin to a tag with `pre-commit autoupdate`
238
+ rev: v2.1.0 # pin to a tag with `pre-commit autoupdate`
166
239
  hooks:
167
240
  - id: phpwarden-deps
168
241
  # args: ["--no-reachability"] # faster: skip the reachability pass
@@ -173,44 +246,51 @@ timer recipes are in [`docs/recipes.md`](docs/recipes.md).
173
246
 
174
247
  ## The feed
175
248
 
176
- 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:
177
250
 
178
- - **~21,100 advisories**: ~8,600 Composer (from [OSV.dev](https://osv.dev) and
251
+ - **~21,300 advisories**: ~8,800 Composer (from [OSV.dev](https://osv.dev) and
179
252
  [FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
180
253
  ~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
181
254
  and 145 PHP engine/FPM CVEs, including hand-curated entries with per-branch
182
- fixed versions, exposure conditions and detection signatures. 19,400+ carry an
183
- 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.
184
257
  - **License-independent by construction.** Every source is open or
185
- 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
186
259
  vendor's feed is used, so coverage cannot be revoked or priced away.
187
260
  - Every build passes `feedbuilder/check_feed.py` (no open-ended range with a
188
261
  known fix, no fixed version inside its own affected range, no duplicate
189
262
  (package|slug, cve)). The feed is assembled by `feedbuilder/` from a curated
190
263
  base plus bulk imports and enrichment; curated entries always win.
191
264
 
192
- A [phpwarden.io](https://phpwarden.io) subscription unlocks the maintained
193
- channel — new advisories and KEV/EPSS refreshes as they are triaged — via
194
- `PHPWARDEN_LICENSE=... phpwarden feed update`. Without a key, phpwarden runs
195
- the community feed and says so.
196
-
197
- Curated engine CVEs with detection signatures:
198
-
199
- | CVE | What | Scope |
200
- |-----|------|-------|
201
- | CVE-2019-11043 | PHP-FPM env_path_info RCE | FPM + nginx, config-gated |
202
- | CVE-2023-3824 | PHAR stack overflow RCE | any SAPI, untrusted PHAR |
203
- | CVE-2024-4577 | PHP-CGI argument injection | Windows + CGI |
204
- | CVE-2024-8926 / 8927 | cgi.force_redirect bypass | CGI / FPM |
205
- | CVE-2024-11236 | DB-quoter integer overflow | any SAPI |
206
- | 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 |
207
287
 
208
288
  ## Continuous guard
209
289
 
210
290
  `scan` and `detect` are point-in-time. For always-on containment of
211
291
  in-progress php2shell-class attacks (OOM on a worker, then that worker reused),
212
292
  run `phpwarden-guard`, which boots in dry-run and never acts until you promote
213
- 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.
214
294
 
215
295
  ## Honest limits
216
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 the ~21,100 PHP advisories in the feed, 43 are actually being
5
- exploited (CISA KEV, as of 2026-11-12). 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
 
@@ -10,6 +10,13 @@ It scans the PHP engine/FPM version, Composer dependencies and WordPress
10
10
  installs against one bundled, offline advisory feed. Zero runtime dependencies
11
11
  (Python 3.8+), Apache-2.0.
12
12
 
13
+ **Reproducible benchmark:** on four real PHP stacks, phpwarden found 295 of 295
14
+ known vulnerabilities, `composer audit` 286 and OSV.dev 262. Of 297 findings,
15
+ 100 were rated critical or high and 4 were actually exploited or likely to be;
16
+ phpwarden ranked each of those first or second in its project. Method, numbers
17
+ and a script to rerun it:
18
+ [the benchmark write-up](https://phpwarden.io/blog/phpwarden-vs-composer-audit-osv-2026-11).
19
+
13
20
  ```bash
14
21
  $ phpwarden deps composer.lock
15
22
  18 vulnerable package(s) in composer.lock; 0 actively exploited (KEV). Ranked by exploitation risk, then reachability:
@@ -29,6 +36,10 @@ $ phpwarden deps composer.lock
29
36
  | PHP engine / FPM CVE exposure | `scan` | image & network scanners |
30
37
  | Composer dependency CVEs | `deps` | Snyk, `composer audit` |
31
38
  | WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
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` | — |
32
43
  | Exploitation signatures in logs | `detect` | SIEM content |
33
44
  | WAF blocking rules (PHP-specific) | `rules` | the gap a generic WAF leaves |
34
45
  | Fleet exposure dashboard | `report` | the CNAPP's PHP view |
@@ -49,15 +60,18 @@ $ phpwarden deps composer.lock
49
60
  configuration is reported as *possibly* exposed with the exact condition to
50
61
  confirm — not flagged as a certain hit, and not silently dismissed.
51
62
  - **Offline by default.** The feed ships inside the package. Nothing leaves the
52
- 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.
53
66
 
54
67
  ## Install
55
68
 
56
69
  ```bash
57
70
  composer require --dev phpwarden/phpwarden # PHP projects → vendor/bin/phpwarden
58
71
  pipx install phpwarden # Python-tooling box
59
- python3 phpwarden.pyz scan # single file, no install
60
- docker build -t phpwarden . && docker run --rm phpwarden scan --version 8.3.10 --sapi fpm --os linux
72
+ brew install oroma-alfred/tap/phpwarden # macOS / Linux with Homebrew
73
+ curl -LO https://phpwarden.io/phpwarden.pyz && python3 phpwarden.pyz scan # single file, no install
74
+ docker run --rm phpwarden/phpwarden scan --version 8.3.10 --sapi fpm --os linux # Docker Hub, amd64 + arm64
61
75
  ```
62
76
 
63
77
  On modern macOS / Debian / Ubuntu a plain `pip install phpwarden` into the
@@ -68,8 +82,8 @@ system Python is blocked by [PEP 668](https://peps.python.org/pep-0668/); use
68
82
  python3 -m venv ~/.venvs/phpwarden && ~/.venvs/phpwarden/bin/pip install phpwarden
69
83
  ```
70
84
 
71
- Docker usage (mounted projects, CI, dashboard reporting) is in
72
- [`docs/docker.md`](docs/docker.md).
85
+ Docker usage (mounted projects, exit codes, CI, dashboard reporting) is on the
86
+ image's [Docker Hub page](https://hub.docker.com/r/phpwarden/phpwarden).
73
87
 
74
88
  ## Use
75
89
 
@@ -81,21 +95,80 @@ phpwarden wordpress /var/www/html # WordPress core, plugins, themes
81
95
  phpwarden integrity /var/www/html # tampered or injected files (vs wordpress.org checksums)
82
96
  phpwarden detect /var/log/nginx/access.log # exploit signatures in a log
83
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
84
103
  phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
85
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
86
106
  ```
87
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
+
88
114
  Every command takes `--json`. Exit codes are shared across subcommands and
89
- 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`),
90
118
  `2` possibly exposed (`scan`), `3` PHP version unknown (`scan`), `4` input
91
119
  error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
92
120
  error. Full table: `phpwarden --help`.
93
121
 
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
+
152
+ ## Replacing Wordfence CLI
153
+
154
+ Wordfence closed new licences for its CLI in October 2026; Free licences stopped working on 2026-10-14 and Premium
155
+ licences are not renewed. phpwarden runs the same way (on the server, no plugin in the site) and covers:
156
+
157
+ | Wordfence CLI | phpwarden | Notes |
158
+ |---|---|---|
159
+ | `wordfence vuln-scan /path` | `phpwarden wordpress /path` | Same job, ranked by real-world exploitation; JSON/SARIF and CI exit codes. Also scans Composer and the PHP engine. |
160
+ | `wordfence malware-scan /path` | `phpwarden integrity /path` | Compares core and wordpress.org plugin files with official checksums; flags injected files, PHP in uploads and common backdoor code. **Not a signature scanner**: premium plugins and themes cannot be verified and are listed as such. |
161
+ | `wordfence remediate` | – | Reinstall the flagged component with WP-CLI. |
162
+ | `wordfence db-scan` | – | Not covered. |
163
+
164
+ Full guide: https://phpwarden.io/compare/phpwarden-vs-wordfence-cli#replace
165
+
94
166
  ## Send results to the dashboard
95
167
 
96
168
  Any `scan`, `deps`, `wordpress` or `detect` run can post its result to
97
- [app.phpwarden.io](https://app.phpwarden.io) for history across hosts and
98
- 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:
99
172
 
100
173
  ```bash
101
174
  phpwarden scan --report-to https://app.phpwarden.io/v1/reports --report-token <token>
@@ -140,7 +213,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
140
213
  ```yaml
141
214
  repos:
142
215
  - repo: https://github.com/oroma-alfred/phpwarden
143
- rev: main # pin to a tag with `pre-commit autoupdate`
216
+ rev: v2.1.0 # pin to a tag with `pre-commit autoupdate`
144
217
  hooks:
145
218
  - id: phpwarden-deps
146
219
  # args: ["--no-reachability"] # faster: skip the reachability pass
@@ -151,44 +224,51 @@ timer recipes are in [`docs/recipes.md`](docs/recipes.md).
151
224
 
152
225
  ## The feed
153
226
 
154
- 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:
155
228
 
156
- - **~21,100 advisories**: ~8,600 Composer (from [OSV.dev](https://osv.dev) and
229
+ - **~21,300 advisories**: ~8,800 Composer (from [OSV.dev](https://osv.dev) and
157
230
  [FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
158
231
  ~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
159
232
  and 145 PHP engine/FPM CVEs, including hand-curated entries with per-branch
160
- fixed versions, exposure conditions and detection signatures. 19,400+ carry an
161
- 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.
162
235
  - **License-independent by construction.** Every source is open or
163
- 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
164
237
  vendor's feed is used, so coverage cannot be revoked or priced away.
165
238
  - Every build passes `feedbuilder/check_feed.py` (no open-ended range with a
166
239
  known fix, no fixed version inside its own affected range, no duplicate
167
240
  (package|slug, cve)). The feed is assembled by `feedbuilder/` from a curated
168
241
  base plus bulk imports and enrichment; curated entries always win.
169
242
 
170
- A [phpwarden.io](https://phpwarden.io) subscription unlocks the maintained
171
- channel — new advisories and KEV/EPSS refreshes as they are triaged — via
172
- `PHPWARDEN_LICENSE=... phpwarden feed update`. Without a key, phpwarden runs
173
- the community feed and says so.
174
-
175
- Curated engine CVEs with detection signatures:
176
-
177
- | CVE | What | Scope |
178
- |-----|------|-------|
179
- | CVE-2019-11043 | PHP-FPM env_path_info RCE | FPM + nginx, config-gated |
180
- | CVE-2023-3824 | PHAR stack overflow RCE | any SAPI, untrusted PHAR |
181
- | CVE-2024-4577 | PHP-CGI argument injection | Windows + CGI |
182
- | CVE-2024-8926 / 8927 | cgi.force_redirect bypass | CGI / FPM |
183
- | CVE-2024-11236 | DB-quoter integer overflow | any SAPI |
184
- | 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 |
185
265
 
186
266
  ## Continuous guard
187
267
 
188
268
  `scan` and `detect` are point-in-time. For always-on containment of
189
269
  in-progress php2shell-class attacks (OOM on a worker, then that worker reused),
190
270
  run `phpwarden-guard`, which boots in dry-run and never acts until you promote
191
- 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.
192
272
 
193
273
  ## Honest limits
194
274
 
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "phpwarden"
7
- version = "2.1.0"
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"