phpwarden 2.0.0__tar.gz → 2.0.3__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.
- phpwarden-2.0.3/PKG-INFO +228 -0
- phpwarden-2.0.3/README.md +206 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/pyproject.toml +7 -5
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/__init__.py +1 -1
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/cli.py +4 -1
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/data/php-cve-feed.json +135297 -28750
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/guard.py +13 -0
- phpwarden-2.0.3/src/phpwarden.egg-info/PKG-INFO +228 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden.egg-info/SOURCES.txt +5 -0
- phpwarden-2.0.3/tests/test_advisories.py +447 -0
- phpwarden-2.0.3/tests/test_connect_flow.py +103 -0
- phpwarden-2.0.3/tests/test_dashboard_states.py +125 -0
- phpwarden-2.0.3/tests/test_engine_feed.py +70 -0
- phpwarden-2.0.3/tests/test_google_oauth.py +191 -0
- phpwarden-2.0.0/PKG-INFO +0 -177
- phpwarden-2.0.0/README.md +0 -157
- phpwarden-2.0.0/src/phpwarden.egg-info/PKG-INFO +0 -177
- {phpwarden-2.0.0 → phpwarden-2.0.3}/LICENSE +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/setup.cfg +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/deps.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/detect.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/engine.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/env.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/feed.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/report.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/rules.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/version.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/vulndb.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden/wordpress.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden.egg-info/dependency_links.txt +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden.egg-info/entry_points.txt +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/src/phpwarden.egg-info/top_level.txt +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_accounts.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_assignment.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_audit.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_billing.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_cli_edges.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_deps_and_rules.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_detection_validation.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_developer.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_discussion.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_e2e_readiness.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_email.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_engine.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_feed_license.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_feed_subscription.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_fleet.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_guard_live.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_mentions.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_reachability.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_saas.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_saas_billing.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_server.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_settings.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_shield.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_slack.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_sso.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_team.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_triage.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_webhook_idempotency.py +0 -0
- {phpwarden-2.0.0 → phpwarden-2.0.3}/tests/test_wordpress_and_report.py +0 -0
phpwarden-2.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: phpwarden
|
|
3
|
+
Version: 2.0.3
|
|
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.
|
|
5
|
+
Author: oroma-alfred
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://phpwarden.io
|
|
8
|
+
Project-URL: Source, https://github.com/oroma-alfred/phpwarden
|
|
9
|
+
Project-URL: Issues, https://github.com/oroma-alfred/phpwarden/issues
|
|
10
|
+
Keywords: php,composer,wordpress,php-fpm,security,vulnerability,cve,kev,epss,sca,scanner
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Classifier: Topic :: System :: Monitoring
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# phpwarden
|
|
24
|
+
|
|
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
|
|
28
|
+
by EPSS probability, then CVSS — so a `composer.lock` with 30 findings tells
|
|
29
|
+
you which one to fix today.
|
|
30
|
+
|
|
31
|
+
It scans the PHP engine/FPM version, Composer dependencies and WordPress
|
|
32
|
+
installs against one bundled, offline advisory feed. Zero runtime dependencies
|
|
33
|
+
(Python 3.8+), Apache-2.0.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
$ phpwarden deps composer.lock
|
|
37
|
+
18 vulnerable package(s) in composer.lock; 0 actively exploited (KEV). Ranked by exploitation risk, then reachability:
|
|
38
|
+
|
|
39
|
+
symfony/http-kernel 5.4.0 CVE-2022-24894 medium (CVSS 5.9) [EPSS 4% (p90)]
|
|
40
|
+
Symfony storing cookie headers in HttpCache
|
|
41
|
+
fixed in: 4.4.50, 5.4.20, 6.0.20, 6.1.12, 6.2.6
|
|
42
|
+
|
|
43
|
+
phpmailer/phpmailer 6.1.5 CVE-2020-13625 high (CVSS 7.5) [EPSS 4% (p90)]
|
|
44
|
+
...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Surfaces it covers
|
|
48
|
+
|
|
49
|
+
| Surface | Command | Instead of |
|
|
50
|
+
|---------|---------|------------|
|
|
51
|
+
| PHP engine / FPM CVE exposure | `scan` | image & network scanners |
|
|
52
|
+
| Composer dependency CVEs | `deps` | Snyk, `composer audit` |
|
|
53
|
+
| WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
|
|
54
|
+
| Exploitation signatures in logs | `detect` | SIEM content |
|
|
55
|
+
| WAF blocking rules (PHP-specific) | `rules` | the gap a generic WAF leaves |
|
|
56
|
+
| Fleet exposure dashboard | `report` | the CNAPP's PHP view |
|
|
57
|
+
| Continuous worker containment | `phpwarden-guard` daemon | — |
|
|
58
|
+
|
|
59
|
+
## Why it is different
|
|
60
|
+
|
|
61
|
+
- **Exploitation first.** Every advisory with a CVE is joined to
|
|
62
|
+
[CISA KEV](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
|
|
63
|
+
(exploited in the wild, used in ransomware) and
|
|
64
|
+
[FIRST EPSS](https://www.first.org/epss/) (probability of exploitation).
|
|
65
|
+
Findings are ordered KEV, then reachability (`deps`), then EPSS, then CVSS —
|
|
66
|
+
in the text output, the JSON and the dashboard alike.
|
|
67
|
+
- **Version-accurate, per branch.** PHP backports fixes per branch, so a flat
|
|
68
|
+
"you're on 8.3, upgrade" is wrong. phpwarden knows the fixed release for each
|
|
69
|
+
branch of each engine CVE and compares precisely.
|
|
70
|
+
- **It never overclaims.** A CVE that needs a specific SAPI (FPM/CGI), OS or
|
|
71
|
+
configuration is reported as *possibly* exposed with the exact condition to
|
|
72
|
+
confirm — not flagged as a certain hit, and not silently dismissed.
|
|
73
|
+
- **Offline by default.** The feed ships inside the package. Nothing leaves the
|
|
74
|
+
machine unless you opt in to `--report-to` or `feed update`.
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
composer require --dev phpwarden/phpwarden # PHP projects → vendor/bin/phpwarden
|
|
80
|
+
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
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
On modern macOS / Debian / Ubuntu a plain `pip install phpwarden` into the
|
|
86
|
+
system Python is blocked by [PEP 668](https://peps.python.org/pep-0668/); use
|
|
87
|
+
`pipx`, or a virtualenv:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
python3 -m venv ~/.venvs/phpwarden && ~/.venvs/phpwarden/bin/pip install phpwarden
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Docker usage (mounted projects, CI, dashboard reporting) is in
|
|
94
|
+
[`docs/docker.md`](docs/docker.md).
|
|
95
|
+
|
|
96
|
+
## Use
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
phpwarden scan # assess this host (auto-detects version/SAPI/OS)
|
|
100
|
+
phpwarden scan --version 8.3.10 --sapi fpm --os linux
|
|
101
|
+
phpwarden deps composer.lock # Composer dependencies, KEV → reachability → EPSS → CVSS
|
|
102
|
+
phpwarden wordpress /var/www/html # WordPress core, plugins, themes
|
|
103
|
+
phpwarden detect /var/log/nginx/access.log # exploit signatures in a log
|
|
104
|
+
phpwarden rules --format nginx --out php-block.conf # PHP-specific WAF rules (or modsecurity)
|
|
105
|
+
phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
|
|
106
|
+
phpwarden feed status # which feed is active, how many advisories
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Every command takes `--json`. Exit codes are shared across subcommands and
|
|
110
|
+
built for gating: `0` clean, `1` exposed / vulnerable / signature hits,
|
|
111
|
+
`2` possibly exposed (`scan`), `3` PHP version unknown (`scan`), `4` input
|
|
112
|
+
error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
|
|
113
|
+
error. Full table: `phpwarden --help`.
|
|
114
|
+
|
|
115
|
+
## Send results to the dashboard
|
|
116
|
+
|
|
117
|
+
Any `scan`, `deps`, `wordpress` or `detect` run can post its result to
|
|
118
|
+
[app.phpwarden.io](https://app.phpwarden.io) for history across hosts and
|
|
119
|
+
alerts when a finding you already carry lands in KEV:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
phpwarden scan --report-to https://app.phpwarden.io/v1/reports --report-token <token>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The token comes from the project page in the dashboard; it can also be set as
|
|
126
|
+
`PHPWARDEN_REPORT_TOKEN` (and the URL as `PHPWARDEN_REPORT_URL`). Local output
|
|
127
|
+
is always printed first, so an unreachable dashboard never hides a finding.
|
|
128
|
+
|
|
129
|
+
## In CI
|
|
130
|
+
|
|
131
|
+
**GitHub Actions** — the [phpwarden action](https://github.com/oroma-alfred/phpwarden-action)
|
|
132
|
+
scans `composer.lock`, a WordPress path and your production PHP version, writes
|
|
133
|
+
a job-summary table and `composer.lock` line annotations, can upload SARIF to
|
|
134
|
+
the Security tab, and can report to the dashboard:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
steps:
|
|
138
|
+
- uses: actions/checkout@v4
|
|
139
|
+
- uses: oroma-alfred/phpwarden-action@v1
|
|
140
|
+
with:
|
|
141
|
+
fail-on: exploited # fail only on CISA-KEV findings; or critical | high | medium | exposed | never
|
|
142
|
+
report-url: https://app.phpwarden.io/v1/reports
|
|
143
|
+
report-token: ${{ secrets.PHPWARDEN_TOKEN }}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`fail-on` is an input of the action. The bare CLI exits `1` on any vulnerable
|
|
147
|
+
package; see the templates below for how a job uses that.
|
|
148
|
+
|
|
149
|
+
**GitLab CI / Bitbucket Pipelines** — ready-made templates in
|
|
150
|
+
[`integrations/gitlab-ci/`](integrations/gitlab-ci/phpwarden.gitlab-ci.yml) and
|
|
151
|
+
[`integrations/bitbucket/`](integrations/bitbucket/bitbucket-pipelines.yml)
|
|
152
|
+
install the CLI, keep `phpwarden.json` as an artifact, fail the job on exit
|
|
153
|
+
`1` or `2`, and forward to the dashboard when a token variable is set.
|
|
154
|
+
|
|
155
|
+
**Laravel** — `composer require --dev phpwarden/laravel` adds
|
|
156
|
+
`php artisan phpwarden:scan` (same exit codes, plus `--fail-on=exploited`);
|
|
157
|
+
see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
|
|
158
|
+
|
|
159
|
+
**pre-commit** — block a vulnerable `composer.lock` before it is committed:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
repos:
|
|
163
|
+
- repo: https://github.com/oroma-alfred/phpwarden
|
|
164
|
+
rev: main # pin to a tag with `pre-commit autoupdate`
|
|
165
|
+
hooks:
|
|
166
|
+
- id: phpwarden-deps
|
|
167
|
+
# args: ["--no-reachability"] # faster: skip the reachability pass
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Hosts and cron** — WP-CLI, Laravel Forge / Ploi, plain cron and systemd
|
|
171
|
+
timer recipes are in [`docs/recipes.md`](docs/recipes.md).
|
|
172
|
+
|
|
173
|
+
## The feed
|
|
174
|
+
|
|
175
|
+
The bundled community feed (currently 2026.10.31.2) works forever, offline:
|
|
176
|
+
|
|
177
|
+
- **~21,100 advisories**: ~8,600 Composer (from [OSV.dev](https://osv.dev) and
|
|
178
|
+
[FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
|
|
179
|
+
~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
|
|
180
|
+
and 145 PHP engine/FPM CVEs, including hand-curated entries with per-branch
|
|
181
|
+
fixed versions, exposure conditions and detection signatures. 19,400+ carry an
|
|
182
|
+
EPSS score; 42 were in KEV when this feed was built.
|
|
183
|
+
- **License-independent by construction.** Every source is open or
|
|
184
|
+
public-domain (OSV, NVD, FriendsOfPHP, CISA KEV, FIRST EPSS). No commercial
|
|
185
|
+
vendor's feed is used, so coverage cannot be revoked or priced away.
|
|
186
|
+
- Every build passes `feedbuilder/check_feed.py` (no open-ended range with a
|
|
187
|
+
known fix, no fixed version inside its own affected range, no duplicate
|
|
188
|
+
(package|slug, cve)). The feed is assembled by `feedbuilder/` from a curated
|
|
189
|
+
base plus bulk imports and enrichment; curated entries always win.
|
|
190
|
+
|
|
191
|
+
A [phpwarden.io](https://phpwarden.io) subscription unlocks the maintained
|
|
192
|
+
channel — new advisories and KEV/EPSS refreshes as they are triaged — via
|
|
193
|
+
`PHPWARDEN_LICENSE=... phpwarden feed update`. Without a key, phpwarden runs
|
|
194
|
+
the community feed and says so.
|
|
195
|
+
|
|
196
|
+
Curated engine CVEs with detection signatures:
|
|
197
|
+
|
|
198
|
+
| CVE | What | Scope |
|
|
199
|
+
|-----|------|-------|
|
|
200
|
+
| CVE-2019-11043 | PHP-FPM env_path_info RCE | FPM + nginx, config-gated |
|
|
201
|
+
| CVE-2023-3824 | PHAR stack overflow RCE | any SAPI, untrusted PHAR |
|
|
202
|
+
| CVE-2024-4577 | PHP-CGI argument injection | Windows + CGI |
|
|
203
|
+
| CVE-2024-8926 / 8927 | cgi.force_redirect bypass | CGI / FPM |
|
|
204
|
+
| CVE-2024-11236 | DB-quoter integer overflow | any SAPI |
|
|
205
|
+
| CVE-2026-109618 / 109619 | php2shell FPM memory RCE | any SAPI |
|
|
206
|
+
|
|
207
|
+
## Continuous guard
|
|
208
|
+
|
|
209
|
+
`scan` and `detect` are point-in-time. For always-on containment of
|
|
210
|
+
in-progress php2shell-class attacks (OOM on a worker, then that worker reused),
|
|
211
|
+
run `phpwarden-guard`, which boots in dry-run and never acts until you promote
|
|
212
|
+
it after a burn-in. See the guard's DESIGN document.
|
|
213
|
+
|
|
214
|
+
## Honest limits
|
|
215
|
+
|
|
216
|
+
- The deterministic version census is the reliable backbone; behavioural
|
|
217
|
+
detection is best-effort and a clean exploit can leave little trace.
|
|
218
|
+
- `scan` only knows what it can observe. Anything it cannot determine (SAPI, OS,
|
|
219
|
+
app-level conditions) is reported as *possibly* exposed, not safe.
|
|
220
|
+
- The KEV count above is a dated snapshot; `phpwarden feed status` and the
|
|
221
|
+
bundled feed are the source of truth for the version you run.
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
Open-core. The **engine** (`src/phpwarden/`) and the **bundled community feed**
|
|
226
|
+
are **Apache-2.0** (see [`LICENSE`](LICENSE)). The **hosted service** (`saas/`,
|
|
227
|
+
`server/`) and the **maintained feed** are proprietary. Full scope in
|
|
228
|
+
[`LICENSING.md`](LICENSING.md).
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# phpwarden
|
|
2
|
+
|
|
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
|
|
6
|
+
by EPSS probability, then CVSS — so a `composer.lock` with 30 findings tells
|
|
7
|
+
you which one to fix today.
|
|
8
|
+
|
|
9
|
+
It scans the PHP engine/FPM version, Composer dependencies and WordPress
|
|
10
|
+
installs against one bundled, offline advisory feed. Zero runtime dependencies
|
|
11
|
+
(Python 3.8+), Apache-2.0.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
$ phpwarden deps composer.lock
|
|
15
|
+
18 vulnerable package(s) in composer.lock; 0 actively exploited (KEV). Ranked by exploitation risk, then reachability:
|
|
16
|
+
|
|
17
|
+
symfony/http-kernel 5.4.0 CVE-2022-24894 medium (CVSS 5.9) [EPSS 4% (p90)]
|
|
18
|
+
Symfony storing cookie headers in HttpCache
|
|
19
|
+
fixed in: 4.4.50, 5.4.20, 6.0.20, 6.1.12, 6.2.6
|
|
20
|
+
|
|
21
|
+
phpmailer/phpmailer 6.1.5 CVE-2020-13625 high (CVSS 7.5) [EPSS 4% (p90)]
|
|
22
|
+
...
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Surfaces it covers
|
|
26
|
+
|
|
27
|
+
| Surface | Command | Instead of |
|
|
28
|
+
|---------|---------|------------|
|
|
29
|
+
| PHP engine / FPM CVE exposure | `scan` | image & network scanners |
|
|
30
|
+
| Composer dependency CVEs | `deps` | Snyk, `composer audit` |
|
|
31
|
+
| WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
|
|
32
|
+
| Exploitation signatures in logs | `detect` | SIEM content |
|
|
33
|
+
| WAF blocking rules (PHP-specific) | `rules` | the gap a generic WAF leaves |
|
|
34
|
+
| Fleet exposure dashboard | `report` | the CNAPP's PHP view |
|
|
35
|
+
| Continuous worker containment | `phpwarden-guard` daemon | — |
|
|
36
|
+
|
|
37
|
+
## Why it is different
|
|
38
|
+
|
|
39
|
+
- **Exploitation first.** Every advisory with a CVE is joined to
|
|
40
|
+
[CISA KEV](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
|
|
41
|
+
(exploited in the wild, used in ransomware) and
|
|
42
|
+
[FIRST EPSS](https://www.first.org/epss/) (probability of exploitation).
|
|
43
|
+
Findings are ordered KEV, then reachability (`deps`), then EPSS, then CVSS —
|
|
44
|
+
in the text output, the JSON and the dashboard alike.
|
|
45
|
+
- **Version-accurate, per branch.** PHP backports fixes per branch, so a flat
|
|
46
|
+
"you're on 8.3, upgrade" is wrong. phpwarden knows the fixed release for each
|
|
47
|
+
branch of each engine CVE and compares precisely.
|
|
48
|
+
- **It never overclaims.** A CVE that needs a specific SAPI (FPM/CGI), OS or
|
|
49
|
+
configuration is reported as *possibly* exposed with the exact condition to
|
|
50
|
+
confirm — not flagged as a certain hit, and not silently dismissed.
|
|
51
|
+
- **Offline by default.** The feed ships inside the package. Nothing leaves the
|
|
52
|
+
machine unless you opt in to `--report-to` or `feed update`.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
composer require --dev phpwarden/phpwarden # PHP projects → vendor/bin/phpwarden
|
|
58
|
+
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
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
On modern macOS / Debian / Ubuntu a plain `pip install phpwarden` into the
|
|
64
|
+
system Python is blocked by [PEP 668](https://peps.python.org/pep-0668/); use
|
|
65
|
+
`pipx`, or a virtualenv:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python3 -m venv ~/.venvs/phpwarden && ~/.venvs/phpwarden/bin/pip install phpwarden
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Docker usage (mounted projects, CI, dashboard reporting) is in
|
|
72
|
+
[`docs/docker.md`](docs/docker.md).
|
|
73
|
+
|
|
74
|
+
## Use
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
phpwarden scan # assess this host (auto-detects version/SAPI/OS)
|
|
78
|
+
phpwarden scan --version 8.3.10 --sapi fpm --os linux
|
|
79
|
+
phpwarden deps composer.lock # Composer dependencies, KEV → reachability → EPSS → CVSS
|
|
80
|
+
phpwarden wordpress /var/www/html # WordPress core, plugins, themes
|
|
81
|
+
phpwarden detect /var/log/nginx/access.log # exploit signatures in a log
|
|
82
|
+
phpwarden rules --format nginx --out php-block.conf # PHP-specific WAF rules (or modsecurity)
|
|
83
|
+
phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
|
|
84
|
+
phpwarden feed status # which feed is active, how many advisories
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Every command takes `--json`. Exit codes are shared across subcommands and
|
|
88
|
+
built for gating: `0` clean, `1` exposed / vulnerable / signature hits,
|
|
89
|
+
`2` possibly exposed (`scan`), `3` PHP version unknown (`scan`), `4` input
|
|
90
|
+
error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
|
|
91
|
+
error. Full table: `phpwarden --help`.
|
|
92
|
+
|
|
93
|
+
## Send results to the dashboard
|
|
94
|
+
|
|
95
|
+
Any `scan`, `deps`, `wordpress` or `detect` run can post its result to
|
|
96
|
+
[app.phpwarden.io](https://app.phpwarden.io) for history across hosts and
|
|
97
|
+
alerts when a finding you already carry lands in KEV:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
phpwarden scan --report-to https://app.phpwarden.io/v1/reports --report-token <token>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The token comes from the project page in the dashboard; it can also be set as
|
|
104
|
+
`PHPWARDEN_REPORT_TOKEN` (and the URL as `PHPWARDEN_REPORT_URL`). Local output
|
|
105
|
+
is always printed first, so an unreachable dashboard never hides a finding.
|
|
106
|
+
|
|
107
|
+
## In CI
|
|
108
|
+
|
|
109
|
+
**GitHub Actions** — the [phpwarden action](https://github.com/oroma-alfred/phpwarden-action)
|
|
110
|
+
scans `composer.lock`, a WordPress path and your production PHP version, writes
|
|
111
|
+
a job-summary table and `composer.lock` line annotations, can upload SARIF to
|
|
112
|
+
the Security tab, and can report to the dashboard:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
steps:
|
|
116
|
+
- uses: actions/checkout@v4
|
|
117
|
+
- uses: oroma-alfred/phpwarden-action@v1
|
|
118
|
+
with:
|
|
119
|
+
fail-on: exploited # fail only on CISA-KEV findings; or critical | high | medium | exposed | never
|
|
120
|
+
report-url: https://app.phpwarden.io/v1/reports
|
|
121
|
+
report-token: ${{ secrets.PHPWARDEN_TOKEN }}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`fail-on` is an input of the action. The bare CLI exits `1` on any vulnerable
|
|
125
|
+
package; see the templates below for how a job uses that.
|
|
126
|
+
|
|
127
|
+
**GitLab CI / Bitbucket Pipelines** — ready-made templates in
|
|
128
|
+
[`integrations/gitlab-ci/`](integrations/gitlab-ci/phpwarden.gitlab-ci.yml) and
|
|
129
|
+
[`integrations/bitbucket/`](integrations/bitbucket/bitbucket-pipelines.yml)
|
|
130
|
+
install the CLI, keep `phpwarden.json` as an artifact, fail the job on exit
|
|
131
|
+
`1` or `2`, and forward to the dashboard when a token variable is set.
|
|
132
|
+
|
|
133
|
+
**Laravel** — `composer require --dev phpwarden/laravel` adds
|
|
134
|
+
`php artisan phpwarden:scan` (same exit codes, plus `--fail-on=exploited`);
|
|
135
|
+
see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
|
|
136
|
+
|
|
137
|
+
**pre-commit** — block a vulnerable `composer.lock` before it is committed:
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
repos:
|
|
141
|
+
- repo: https://github.com/oroma-alfred/phpwarden
|
|
142
|
+
rev: main # pin to a tag with `pre-commit autoupdate`
|
|
143
|
+
hooks:
|
|
144
|
+
- id: phpwarden-deps
|
|
145
|
+
# args: ["--no-reachability"] # faster: skip the reachability pass
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Hosts and cron** — WP-CLI, Laravel Forge / Ploi, plain cron and systemd
|
|
149
|
+
timer recipes are in [`docs/recipes.md`](docs/recipes.md).
|
|
150
|
+
|
|
151
|
+
## The feed
|
|
152
|
+
|
|
153
|
+
The bundled community feed (currently 2026.10.31.2) works forever, offline:
|
|
154
|
+
|
|
155
|
+
- **~21,100 advisories**: ~8,600 Composer (from [OSV.dev](https://osv.dev) and
|
|
156
|
+
[FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
|
|
157
|
+
~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
|
|
158
|
+
and 145 PHP engine/FPM CVEs, including hand-curated entries with per-branch
|
|
159
|
+
fixed versions, exposure conditions and detection signatures. 19,400+ carry an
|
|
160
|
+
EPSS score; 42 were in KEV when this feed was built.
|
|
161
|
+
- **License-independent by construction.** Every source is open or
|
|
162
|
+
public-domain (OSV, NVD, FriendsOfPHP, CISA KEV, FIRST EPSS). No commercial
|
|
163
|
+
vendor's feed is used, so coverage cannot be revoked or priced away.
|
|
164
|
+
- Every build passes `feedbuilder/check_feed.py` (no open-ended range with a
|
|
165
|
+
known fix, no fixed version inside its own affected range, no duplicate
|
|
166
|
+
(package|slug, cve)). The feed is assembled by `feedbuilder/` from a curated
|
|
167
|
+
base plus bulk imports and enrichment; curated entries always win.
|
|
168
|
+
|
|
169
|
+
A [phpwarden.io](https://phpwarden.io) subscription unlocks the maintained
|
|
170
|
+
channel — new advisories and KEV/EPSS refreshes as they are triaged — via
|
|
171
|
+
`PHPWARDEN_LICENSE=... phpwarden feed update`. Without a key, phpwarden runs
|
|
172
|
+
the community feed and says so.
|
|
173
|
+
|
|
174
|
+
Curated engine CVEs with detection signatures:
|
|
175
|
+
|
|
176
|
+
| CVE | What | Scope |
|
|
177
|
+
|-----|------|-------|
|
|
178
|
+
| CVE-2019-11043 | PHP-FPM env_path_info RCE | FPM + nginx, config-gated |
|
|
179
|
+
| CVE-2023-3824 | PHAR stack overflow RCE | any SAPI, untrusted PHAR |
|
|
180
|
+
| CVE-2024-4577 | PHP-CGI argument injection | Windows + CGI |
|
|
181
|
+
| CVE-2024-8926 / 8927 | cgi.force_redirect bypass | CGI / FPM |
|
|
182
|
+
| CVE-2024-11236 | DB-quoter integer overflow | any SAPI |
|
|
183
|
+
| CVE-2026-109618 / 109619 | php2shell FPM memory RCE | any SAPI |
|
|
184
|
+
|
|
185
|
+
## Continuous guard
|
|
186
|
+
|
|
187
|
+
`scan` and `detect` are point-in-time. For always-on containment of
|
|
188
|
+
in-progress php2shell-class attacks (OOM on a worker, then that worker reused),
|
|
189
|
+
run `phpwarden-guard`, which boots in dry-run and never acts until you promote
|
|
190
|
+
it after a burn-in. See the guard's DESIGN document.
|
|
191
|
+
|
|
192
|
+
## Honest limits
|
|
193
|
+
|
|
194
|
+
- The deterministic version census is the reliable backbone; behavioural
|
|
195
|
+
detection is best-effort and a clean exploit can leave little trace.
|
|
196
|
+
- `scan` only knows what it can observe. Anything it cannot determine (SAPI, OS,
|
|
197
|
+
app-level conditions) is reported as *possibly* exposed, not safe.
|
|
198
|
+
- The KEV count above is a dated snapshot; `phpwarden feed status` and the
|
|
199
|
+
bundled feed are the source of truth for the version you run.
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
Open-core. The **engine** (`src/phpwarden/`) and the **bundled community feed**
|
|
204
|
+
are **Apache-2.0** (see [`LICENSE`](LICENSE)). The **hosted service** (`saas/`,
|
|
205
|
+
`server/`) and the **maintained feed** are proprietary. Full scope in
|
|
206
|
+
[`LICENSING.md`](LICENSING.md).
|
|
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "phpwarden"
|
|
7
|
-
version = "2.0.
|
|
8
|
-
description = "PHP
|
|
7
|
+
version = "2.0.3"
|
|
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."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
|
-
authors = [{ name = "
|
|
14
|
-
keywords = ["php", "php-fpm", "security", "cve", "
|
|
13
|
+
authors = [{ name = "oroma-alfred" }]
|
|
14
|
+
keywords = ["php", "composer", "wordpress", "php-fpm", "security", "vulnerability", "cve", "kev", "epss", "sca", "scanner"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
17
17
|
"Environment :: Console",
|
|
@@ -24,7 +24,9 @@ classifiers = [
|
|
|
24
24
|
dependencies = [] # stdlib only
|
|
25
25
|
|
|
26
26
|
[project.urls]
|
|
27
|
-
Homepage = "https://
|
|
27
|
+
Homepage = "https://phpwarden.io"
|
|
28
|
+
Source = "https://github.com/oroma-alfred/phpwarden"
|
|
29
|
+
Issues = "https://github.com/oroma-alfred/phpwarden/issues"
|
|
28
30
|
|
|
29
31
|
[project.scripts]
|
|
30
32
|
phpwarden = "phpwarden.cli:main"
|
|
@@ -280,7 +280,10 @@ def _risk(known, epss, pct, ransomware=False):
|
|
|
280
280
|
if known:
|
|
281
281
|
parts.append("KEV·ransomware" if ransomware else "KEV")
|
|
282
282
|
if epss is not None:
|
|
283
|
-
|
|
283
|
+
tag = f"EPSS {epss * 100:.0f}%"
|
|
284
|
+
if pct is not None: # percentile is optional in the feed
|
|
285
|
+
tag += f" (p{pct * 100:.0f})"
|
|
286
|
+
parts.append(tag)
|
|
284
287
|
return " [" + ", ".join(parts) + "]" if parts else ""
|
|
285
288
|
|
|
286
289
|
|