phpwarden 2.2.1__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {phpwarden-2.2.1 → phpwarden-2.4.0}/PKG-INFO +50 -19
- {phpwarden-2.2.1 → phpwarden-2.4.0}/README.md +47 -16
- {phpwarden-2.2.1 → phpwarden-2.4.0}/pyproject.toml +4 -4
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/__init__.py +1 -1
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/cli.py +173 -1
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/code.py +122 -6
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/code_rules.py +73 -3
- phpwarden-2.4.0/src/phpwarden/sast/__init__.py +10 -0
- phpwarden-2.4.0/src/phpwarden/sast/ast.py +85 -0
- phpwarden-2.4.0/src/phpwarden/sast/authz.py +284 -0
- phpwarden-2.4.0/src/phpwarden/sast/callgraph.py +627 -0
- phpwarden-2.4.0/src/phpwarden/sast/dbflow.py +720 -0
- phpwarden-2.4.0/src/phpwarden/sast/engine.py +138 -0
- phpwarden-2.4.0/src/phpwarden/sast/lexer.py +354 -0
- phpwarden-2.4.0/src/phpwarden/sast/models/__init__.py +6 -0
- phpwarden-2.4.0/src/phpwarden/sast/models/laravel.py +115 -0
- phpwarden-2.4.0/src/phpwarden/sast/models/php_builtins.py +123 -0
- phpwarden-2.4.0/src/phpwarden/sast/models/symfony.py +77 -0
- phpwarden-2.4.0/src/phpwarden/sast/models/wordpress.py +28 -0
- phpwarden-2.4.0/src/phpwarden/sast/parser.py +1565 -0
- phpwarden-2.4.0/src/phpwarden/sast/project.py +633 -0
- phpwarden-2.4.0/src/phpwarden/sast/symbols.py +184 -0
- phpwarden-2.4.0/src/phpwarden/sast/taint.py +2484 -0
- phpwarden-2.4.0/src/phpwarden/sast/types.py +432 -0
- phpwarden-2.4.0/src/phpwarden/shield/EDGE.md +108 -0
- phpwarden-2.4.0/src/phpwarden/shield/README.md +111 -0
- phpwarden-2.4.0/src/phpwarden/shield/__init__.py +1 -0
- phpwarden-2.4.0/src/phpwarden/shield/agent/phpwarden-shield.php +572 -0
- phpwarden-2.4.0/src/phpwarden/shield/library.py +403 -0
- phpwarden-2.4.0/src/phpwarden/shield/matcher.py +119 -0
- phpwarden-2.4.0/src/phpwarden/shield/policy.py +254 -0
- phpwarden-2.4.0/src/phpwarden/shield/report.py +89 -0
- phpwarden-2.4.0/src/phpwarden/thirdparty/__init__.py +1 -0
- phpwarden-2.4.0/src/phpwarden/thirdparty/trivy.py +188 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/verdict.py +56 -4
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden.egg-info/PKG-INFO +50 -19
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden.egg-info/SOURCES.txt +37 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_code.py +14 -3
- phpwarden-2.4.0/tests/test_sast_dbflow.py +106 -0
- phpwarden-2.4.0/tests/test_sast_layer5.py +82 -0
- phpwarden-2.4.0/tests/test_sast_models.py +85 -0
- phpwarden-2.4.0/tests/test_sast_parser.py +237 -0
- phpwarden-2.4.0/tests/test_sast_project.py +134 -0
- phpwarden-2.4.0/tests/test_sast_taint.py +223 -0
- phpwarden-2.4.0/tests/test_shield_cli.py +51 -0
- phpwarden-2.4.0/tests/test_shield_dashboard.py +168 -0
- phpwarden-2.4.0/tests/test_shield_runtime.py +380 -0
- phpwarden-2.4.0/tests/test_trivy_import.py +131 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/LICENSE +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/setup.cfg +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/data/php-cve-feed.json +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/deps.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/detect.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/engine.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/env.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/feed.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/guard.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/integrity.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/probe.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/report.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/rules.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/sarif.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/version.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/vulndb.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden/wordpress.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden.egg-info/dependency_links.txt +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden.egg-info/entry_points.txt +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/src/phpwarden.egg-info/top_level.txt +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_account_deletion.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_accounts.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_advisories.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_agency.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_assess.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_assignment.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_audit.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_billing.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_business_metrics.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_cli_edges.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_connect_flow.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_dashboard_states.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_deps_and_rules.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_detection_validation.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_developer.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_discussion.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_e2e_readiness.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_email.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_engine.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_engine_feed.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_feed_license.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_feed_subscription.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_fleet.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_free_feed.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_google_oauth.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_guard_live.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_integrity.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_kev_alert.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_kev_gaps.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_livefeed.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_mentions.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_probe.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_probe_cli.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_probe_dashboard.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_reachability.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_report_proof.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_saas.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_saas_billing.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_sarif.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_server.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_settings.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_shield.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_shield_plan.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_slack.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_sso.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_store_connections.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_team.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_triage.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_waf_dryrun.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_webhook_idempotency.py +0 -0
- {phpwarden-2.2.1 → phpwarden-2.4.0}/tests/test_wordpress_and_report.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phpwarden
|
|
3
|
-
Version: 2.
|
|
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.
|
|
3
|
+
Version: 2.4.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; blocks exploits inside PHP (shield). Offline, zero dependencies.
|
|
5
5
|
Author: oroma-alfred
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://phpwarden.io
|
|
8
8
|
Project-URL: Source, https://github.com/oroma-alfred/phpwarden
|
|
9
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
|
|
10
|
+
Keywords: php,composer,wordpress,php-fpm,security,vulnerability,cve,kev,epss,sca,sast,waf,scanner
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Environment :: Console
|
|
13
13
|
Classifier: Intended Audience :: System Administrators
|
|
@@ -28,8 +28,9 @@ 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
|
|
|
31
|
-
It scans the PHP engine/FPM version, Composer dependencies
|
|
32
|
-
|
|
31
|
+
It scans the PHP engine/FPM version, Composer dependencies, WordPress installs
|
|
32
|
+
and your own PHP code, checks live sites from outside, and can block exploits
|
|
33
|
+
inside PHP, against one bundled, offline advisory feed. Zero runtime dependencies
|
|
33
34
|
(Python 3.8+), Apache-2.0.
|
|
34
35
|
|
|
35
36
|
**Reproducible benchmark:** on four real PHP stacks, phpwarden found 295 of 295
|
|
@@ -59,7 +60,8 @@ $ phpwarden deps composer.lock
|
|
|
59
60
|
| Composer dependency CVEs | `deps` | Snyk, `composer audit` |
|
|
60
61
|
| WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
|
|
61
62
|
| WordPress file integrity (wordpress.org checksums, injected files) | `integrity` | Wordfence CLI malware-scan (partly) |
|
|
62
|
-
| Your own PHP code (
|
|
63
|
+
| Your own PHP code: whole-program, Laravel/Symfony/WordPress-aware (89 rules) | `code` | Snyk Code, Semgrep, Psalm taint |
|
|
64
|
+
| Exploits of the vulnerabilities this site has, blocked inside PHP | `shield` | the WAF rule you would have to write |
|
|
63
65
|
| Live site from outside: exposed files, debug endpoints | `probe` | — |
|
|
64
66
|
| One ranked verdict across all of the above | `assess` | — |
|
|
65
67
|
| Exploitation signatures in logs | `detect` | SIEM content |
|
|
@@ -120,7 +122,8 @@ phpwarden rules --format nginx --out php-block.conf # PHP-specific WAF rules (
|
|
|
120
122
|
phpwarden rules --format nginx --dry-run # the same rules, log-only: nothing is blocked
|
|
121
123
|
phpwarden rules --replay /var/log/nginx/access.log # what the rules would have blocked in an existing log
|
|
122
124
|
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
|
|
125
|
+
phpwarden code . # your own PHP, whole program: 89 rules (injection, auth, mass assignment, Blade/Twig, WordPress)
|
|
126
|
+
phpwarden shield policy . && phpwarden shield install # block exploits of what is installed here, inside PHP (log-only first)
|
|
124
127
|
phpwarden assess . --url https://example.com --log access.log # one ranked verdict; --out / --baseline shows what is fixed and verified
|
|
125
128
|
phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
|
|
126
129
|
phpwarden feed status # which feed is active, how many advisories
|
|
@@ -133,7 +136,7 @@ phpwarden feed update # pull a newer feed: free weekly snapshot,
|
|
|
133
136
|
`probe`, `code`, `assess`, `--format sarif`, `rules --dry-run` / `--replay` and
|
|
134
137
|
the no-key `feed update` are new in 2.2.0.
|
|
135
138
|
|
|
136
|
-
Every command takes `--json
|
|
139
|
+
Every scanning command takes `--json` (`shield install` and `shield promote` do not). Exit codes are shared across subcommands and
|
|
137
140
|
built for gating: `0` clean, `1` exposed / vulnerable / signature hits / tampered
|
|
138
141
|
files / high or critical `code` and `probe` findings (`probe` also on any
|
|
139
142
|
known-exploited finding) / anything to act on now or this week (`assess`),
|
|
@@ -142,7 +145,26 @@ error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
|
|
|
142
145
|
error. Full table: `phpwarden --help`.
|
|
143
146
|
|
|
144
147
|
|
|
145
|
-
## New in 2.2.0
|
|
148
|
+
## New in 2.4.0, 2.3.0 and 2.2
|
|
149
|
+
|
|
150
|
+
- **2.4.0: `phpwarden code` follows data through the database and callbacks.**
|
|
151
|
+
Request data saved with raw SQL, `$wpdb`, Eloquent, the query builder or
|
|
152
|
+
Doctrine and read back from the same table is traced to where it is printed
|
|
153
|
+
or queried (stored XSS, second-order SQL injection), and callbacks passed to
|
|
154
|
+
`array_map`, `usort`, `preg_replace_callback`, `add_filter` and the like,
|
|
155
|
+
`$f()` and first-class callables are followed. DVWA: 8/8/7 of 8 types under
|
|
156
|
+
the comparison's scorer (was 7/7/6); clean-code counts unchanged.
|
|
157
|
+
- **2.4.0: shield rate limits.** Per-client limits on WordPress login, password
|
|
158
|
+
reset, xmlrpc and `system.multicall`, Laravel login and reset, component
|
|
159
|
+
writes, and a burst guard. Log-only until promoted, then 429; APCu or file
|
|
160
|
+
store; fails open. `--trust-proxy CIDR`, `--no-limits`, `--limit-path REGEX`.
|
|
161
|
+
0 trips on 6,000 ordinary requests; 6 of 6 abuse scenarios caught. Pairing
|
|
162
|
+
with Cloudflare or nginx: [EDGE.md](src/phpwarden/shield/EDGE.md).
|
|
163
|
+
- **2.4.0: `phpwarden trivy-import` and `assess --trivy FILE` / `--run-trivy`.**
|
|
164
|
+
Trivy's JSON (npm/JS, OS packages, images, IaC, secrets) in the same ranked
|
|
165
|
+
list as PHP findings: KEV, then EPSS (`--kev`, `--epss`), then severity.
|
|
166
|
+
Secrets by file and line only. Never downloads Trivy. See
|
|
167
|
+
[src/phpwarden/thirdparty/README.md](src/phpwarden/thirdparty/README.md).
|
|
146
168
|
|
|
147
169
|
- **`phpwarden probe URL`**: a live check of a running site using GET and HEAD
|
|
148
170
|
requests only. It looks for exposed files (`.env`, `.git`, backups), open debug
|
|
@@ -150,11 +172,20 @@ error. Full table: `phpwarden --help`.
|
|
|
150
172
|
matches those against the feed. Active checks run only after you prove the
|
|
151
173
|
site is yours (DNS TXT record, a file, or a meta tag; `--print-token` shows
|
|
152
174
|
how). Without that it reads the homepage and stops.
|
|
153
|
-
- **`phpwarden code PATH
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
- **`phpwarden code PATH`** (whole-program engine, default in 2.3.0): parses the
|
|
176
|
+
project, resolves namespaces, Composer autoloading, inheritance and types, and
|
|
177
|
+
follows request data across files to SQL, shell, `eval`, `unserialize`,
|
|
178
|
+
includes, redirects and HTTP clients, with a trace per finding (SARIF
|
|
179
|
+
`codeFlows`). Models Laravel, Symfony and WordPress: routes with no auth check,
|
|
180
|
+
mass assignment, raw Blade/Twig output, IDOR hints, CSRF exclusions, nonces.
|
|
181
|
+
Follows data stored in the session or WordPress options (second-order).
|
|
182
|
+
89 rules. On the same pinned apps it found more than Semgrep and Psalm with far
|
|
183
|
+
fewer findings on clean framework code ([comparison](https://github.com/oroma-alfred/phpwarden/tree/main/bench/sast-compare-2026-11)).
|
|
184
|
+
- **`phpwarden shield`**: one PHP file loaded before your app that blocks exploit
|
|
185
|
+
attempts against the exploited vulnerabilities this site actually has, scoped
|
|
186
|
+
to their entry points. Logs until you promote rules (7-day burn-in, explicit
|
|
187
|
+
flag), fails open, has a kill switch, about 0.07 ms per request. Not an edge
|
|
188
|
+
WAF: no DDoS, bot or TLS handling.
|
|
158
189
|
- **`phpwarden assess`**: one ranked list from every view of a site: installed
|
|
159
190
|
versions, your code, the live site from outside, and the access log, with the
|
|
160
191
|
reason for each item ("exploited, reachable, visible from outside, already
|
|
@@ -236,7 +267,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
|
|
|
236
267
|
```yaml
|
|
237
268
|
repos:
|
|
238
269
|
- repo: https://github.com/oroma-alfred/phpwarden
|
|
239
|
-
rev: v2.
|
|
270
|
+
rev: v2.4.0 # pin to a tag with `pre-commit autoupdate`
|
|
240
271
|
hooks:
|
|
241
272
|
- id: phpwarden-deps
|
|
242
273
|
# args: ["--no-reachability"] # faster: skip the reachability pass
|
|
@@ -247,14 +278,14 @@ timer recipes are in [`docs/recipes.md`](docs/recipes.md).
|
|
|
247
278
|
|
|
248
279
|
## The feed
|
|
249
280
|
|
|
250
|
-
The bundled community feed (2026.11.
|
|
281
|
+
The bundled community feed (2026.11.14.2 in this release) works forever, offline:
|
|
251
282
|
|
|
252
283
|
- **~21,300 advisories**: ~8,800 Composer (from [OSV.dev](https://osv.dev) and
|
|
253
284
|
[FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
|
|
254
285
|
~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
|
|
255
|
-
and
|
|
256
|
-
fixed versions, exposure conditions and detection signatures. About 19,
|
|
257
|
-
EPSS score;
|
|
286
|
+
and 146 PHP engine/FPM CVEs, including hand-curated entries with per-branch
|
|
287
|
+
fixed versions, exposure conditions and detection signatures. About 19,800 carry an
|
|
288
|
+
EPSS score; 54 distinct vulnerabilities were in KEV when this feed was built.
|
|
258
289
|
- **License-independent by construction.** Every source is open or
|
|
259
290
|
public-domain (OSV, NVD, FriendsOfPHP, php-src release notes, CISA KEV, FIRST EPSS). No commercial
|
|
260
291
|
vendor's feed is used, so coverage cannot be revoked or priced away.
|
|
@@ -6,8 +6,9 @@ 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
|
|
|
9
|
-
It scans the PHP engine/FPM version, Composer dependencies
|
|
10
|
-
|
|
9
|
+
It scans the PHP engine/FPM version, Composer dependencies, WordPress installs
|
|
10
|
+
and your own PHP code, checks live sites from outside, and can block exploits
|
|
11
|
+
inside PHP, against one bundled, offline advisory feed. Zero runtime dependencies
|
|
11
12
|
(Python 3.8+), Apache-2.0.
|
|
12
13
|
|
|
13
14
|
**Reproducible benchmark:** on four real PHP stacks, phpwarden found 295 of 295
|
|
@@ -37,7 +38,8 @@ $ phpwarden deps composer.lock
|
|
|
37
38
|
| Composer dependency CVEs | `deps` | Snyk, `composer audit` |
|
|
38
39
|
| WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
|
|
39
40
|
| WordPress file integrity (wordpress.org checksums, injected files) | `integrity` | Wordfence CLI malware-scan (partly) |
|
|
40
|
-
| Your own PHP code (
|
|
41
|
+
| Your own PHP code: whole-program, Laravel/Symfony/WordPress-aware (89 rules) | `code` | Snyk Code, Semgrep, Psalm taint |
|
|
42
|
+
| Exploits of the vulnerabilities this site has, blocked inside PHP | `shield` | the WAF rule you would have to write |
|
|
41
43
|
| Live site from outside: exposed files, debug endpoints | `probe` | — |
|
|
42
44
|
| One ranked verdict across all of the above | `assess` | — |
|
|
43
45
|
| Exploitation signatures in logs | `detect` | SIEM content |
|
|
@@ -98,7 +100,8 @@ phpwarden rules --format nginx --out php-block.conf # PHP-specific WAF rules (
|
|
|
98
100
|
phpwarden rules --format nginx --dry-run # the same rules, log-only: nothing is blocked
|
|
99
101
|
phpwarden rules --replay /var/log/nginx/access.log # what the rules would have blocked in an existing log
|
|
100
102
|
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
|
|
103
|
+
phpwarden code . # your own PHP, whole program: 89 rules (injection, auth, mass assignment, Blade/Twig, WordPress)
|
|
104
|
+
phpwarden shield policy . && phpwarden shield install # block exploits of what is installed here, inside PHP (log-only first)
|
|
102
105
|
phpwarden assess . --url https://example.com --log access.log # one ranked verdict; --out / --baseline shows what is fixed and verified
|
|
103
106
|
phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
|
|
104
107
|
phpwarden feed status # which feed is active, how many advisories
|
|
@@ -111,7 +114,7 @@ phpwarden feed update # pull a newer feed: free weekly snapshot,
|
|
|
111
114
|
`probe`, `code`, `assess`, `--format sarif`, `rules --dry-run` / `--replay` and
|
|
112
115
|
the no-key `feed update` are new in 2.2.0.
|
|
113
116
|
|
|
114
|
-
Every command takes `--json
|
|
117
|
+
Every scanning command takes `--json` (`shield install` and `shield promote` do not). Exit codes are shared across subcommands and
|
|
115
118
|
built for gating: `0` clean, `1` exposed / vulnerable / signature hits / tampered
|
|
116
119
|
files / high or critical `code` and `probe` findings (`probe` also on any
|
|
117
120
|
known-exploited finding) / anything to act on now or this week (`assess`),
|
|
@@ -120,7 +123,26 @@ error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
|
|
|
120
123
|
error. Full table: `phpwarden --help`.
|
|
121
124
|
|
|
122
125
|
|
|
123
|
-
## New in 2.2.0
|
|
126
|
+
## New in 2.4.0, 2.3.0 and 2.2
|
|
127
|
+
|
|
128
|
+
- **2.4.0: `phpwarden code` follows data through the database and callbacks.**
|
|
129
|
+
Request data saved with raw SQL, `$wpdb`, Eloquent, the query builder or
|
|
130
|
+
Doctrine and read back from the same table is traced to where it is printed
|
|
131
|
+
or queried (stored XSS, second-order SQL injection), and callbacks passed to
|
|
132
|
+
`array_map`, `usort`, `preg_replace_callback`, `add_filter` and the like,
|
|
133
|
+
`$f()` and first-class callables are followed. DVWA: 8/8/7 of 8 types under
|
|
134
|
+
the comparison's scorer (was 7/7/6); clean-code counts unchanged.
|
|
135
|
+
- **2.4.0: shield rate limits.** Per-client limits on WordPress login, password
|
|
136
|
+
reset, xmlrpc and `system.multicall`, Laravel login and reset, component
|
|
137
|
+
writes, and a burst guard. Log-only until promoted, then 429; APCu or file
|
|
138
|
+
store; fails open. `--trust-proxy CIDR`, `--no-limits`, `--limit-path REGEX`.
|
|
139
|
+
0 trips on 6,000 ordinary requests; 6 of 6 abuse scenarios caught. Pairing
|
|
140
|
+
with Cloudflare or nginx: [EDGE.md](src/phpwarden/shield/EDGE.md).
|
|
141
|
+
- **2.4.0: `phpwarden trivy-import` and `assess --trivy FILE` / `--run-trivy`.**
|
|
142
|
+
Trivy's JSON (npm/JS, OS packages, images, IaC, secrets) in the same ranked
|
|
143
|
+
list as PHP findings: KEV, then EPSS (`--kev`, `--epss`), then severity.
|
|
144
|
+
Secrets by file and line only. Never downloads Trivy. See
|
|
145
|
+
[src/phpwarden/thirdparty/README.md](src/phpwarden/thirdparty/README.md).
|
|
124
146
|
|
|
125
147
|
- **`phpwarden probe URL`**: a live check of a running site using GET and HEAD
|
|
126
148
|
requests only. It looks for exposed files (`.env`, `.git`, backups), open debug
|
|
@@ -128,11 +150,20 @@ error. Full table: `phpwarden --help`.
|
|
|
128
150
|
matches those against the feed. Active checks run only after you prove the
|
|
129
151
|
site is yours (DNS TXT record, a file, or a meta tag; `--print-token` shows
|
|
130
152
|
how). Without that it reads the homepage and stops.
|
|
131
|
-
- **`phpwarden code PATH
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
- **`phpwarden code PATH`** (whole-program engine, default in 2.3.0): parses the
|
|
154
|
+
project, resolves namespaces, Composer autoloading, inheritance and types, and
|
|
155
|
+
follows request data across files to SQL, shell, `eval`, `unserialize`,
|
|
156
|
+
includes, redirects and HTTP clients, with a trace per finding (SARIF
|
|
157
|
+
`codeFlows`). Models Laravel, Symfony and WordPress: routes with no auth check,
|
|
158
|
+
mass assignment, raw Blade/Twig output, IDOR hints, CSRF exclusions, nonces.
|
|
159
|
+
Follows data stored in the session or WordPress options (second-order).
|
|
160
|
+
89 rules. On the same pinned apps it found more than Semgrep and Psalm with far
|
|
161
|
+
fewer findings on clean framework code ([comparison](https://github.com/oroma-alfred/phpwarden/tree/main/bench/sast-compare-2026-11)).
|
|
162
|
+
- **`phpwarden shield`**: one PHP file loaded before your app that blocks exploit
|
|
163
|
+
attempts against the exploited vulnerabilities this site actually has, scoped
|
|
164
|
+
to their entry points. Logs until you promote rules (7-day burn-in, explicit
|
|
165
|
+
flag), fails open, has a kill switch, about 0.07 ms per request. Not an edge
|
|
166
|
+
WAF: no DDoS, bot or TLS handling.
|
|
136
167
|
- **`phpwarden assess`**: one ranked list from every view of a site: installed
|
|
137
168
|
versions, your code, the live site from outside, and the access log, with the
|
|
138
169
|
reason for each item ("exploited, reachable, visible from outside, already
|
|
@@ -214,7 +245,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
|
|
|
214
245
|
```yaml
|
|
215
246
|
repos:
|
|
216
247
|
- repo: https://github.com/oroma-alfred/phpwarden
|
|
217
|
-
rev: v2.
|
|
248
|
+
rev: v2.4.0 # pin to a tag with `pre-commit autoupdate`
|
|
218
249
|
hooks:
|
|
219
250
|
- id: phpwarden-deps
|
|
220
251
|
# args: ["--no-reachability"] # faster: skip the reachability pass
|
|
@@ -225,14 +256,14 @@ timer recipes are in [`docs/recipes.md`](docs/recipes.md).
|
|
|
225
256
|
|
|
226
257
|
## The feed
|
|
227
258
|
|
|
228
|
-
The bundled community feed (2026.11.
|
|
259
|
+
The bundled community feed (2026.11.14.2 in this release) works forever, offline:
|
|
229
260
|
|
|
230
261
|
- **~21,300 advisories**: ~8,800 Composer (from [OSV.dev](https://osv.dev) and
|
|
231
262
|
[FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
|
|
232
263
|
~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
|
|
233
|
-
and
|
|
234
|
-
fixed versions, exposure conditions and detection signatures. About 19,
|
|
235
|
-
EPSS score;
|
|
264
|
+
and 146 PHP engine/FPM CVEs, including hand-curated entries with per-branch
|
|
265
|
+
fixed versions, exposure conditions and detection signatures. About 19,800 carry an
|
|
266
|
+
EPSS score; 54 distinct vulnerabilities were in KEV when this feed was built.
|
|
236
267
|
- **License-independent by construction.** Every source is open or
|
|
237
268
|
public-domain (OSV, NVD, FriendsOfPHP, php-src release notes, CISA KEV, FIRST EPSS). No commercial
|
|
238
269
|
vendor's feed is used, so coverage cannot be revoked or priced away.
|
|
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "phpwarden"
|
|
7
|
-
version = "2.
|
|
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."
|
|
7
|
+
version = "2.4.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; blocks exploits inside PHP (shield). Offline, zero dependencies."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [{ name = "oroma-alfred" }]
|
|
14
|
-
keywords = ["php", "composer", "wordpress", "php-fpm", "security", "vulnerability", "cve", "kev", "epss", "sca", "scanner"]
|
|
14
|
+
keywords = ["php", "composer", "wordpress", "php-fpm", "security", "vulnerability", "cve", "kev", "epss", "sca", "sast", "waf", "scanner"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
17
17
|
"Environment :: Console",
|
|
@@ -39,4 +39,4 @@ package-dir = { "" = "src" }
|
|
|
39
39
|
where = ["src"]
|
|
40
40
|
|
|
41
41
|
[tool.setuptools.package-data]
|
|
42
|
-
phpwarden = ["data/*.json"]
|
|
42
|
+
phpwarden = ["data/*.json", "shield/agent/*.php", "shield/*.md"]
|
|
@@ -6,4 +6,4 @@ real-world exploitation (CISA KEV, then EPSS, then CVSS), and can watch PHP-FPM
|
|
|
6
6
|
for active exploitation. The engine and the bundled feed are open; free users
|
|
7
7
|
get weekly feed updates, and a subscription adds the feed rebuilt twice a day.
|
|
8
8
|
"""
|
|
9
|
-
__version__ = "2.
|
|
9
|
+
__version__ = "2.4.0"
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
phpwarden probe URL safe live HTTP check of a site you own
|
|
14
14
|
phpwarden code PATH scan your own PHP code for high-signal security bugs
|
|
15
15
|
phpwarden assess PATH --url U --log L one ranked verdict from every view
|
|
16
|
+
phpwarden shield policy PATH protection inside PHP for the exploited vulns installed here
|
|
16
17
|
|
|
17
18
|
Exit codes (shared by every subcommand; usable as a CI gate):
|
|
18
19
|
0 nothing applies / clean
|
|
@@ -545,8 +546,29 @@ def _assess(args):
|
|
|
545
546
|
if any("homepage unreachable" in n for n in probe_result.get("notes", [])):
|
|
546
547
|
sys.stderr.write(f"error: could not reach {url}\n")
|
|
547
548
|
return 4
|
|
549
|
+
trivy_fs, trivy_note = None, None
|
|
550
|
+
if args.trivy or args.run_trivy:
|
|
551
|
+
from phpwarden.thirdparty import trivy as tv
|
|
552
|
+
try:
|
|
553
|
+
kev, epss = tv.load_kev(args.kev), tv.load_epss(args.epss)
|
|
554
|
+
docs = [tv.load_report(f) for f in (args.trivy or [])]
|
|
555
|
+
except (OSError, ValueError) as e:
|
|
556
|
+
sys.stderr.write(f"error: {e}\n")
|
|
557
|
+
return 4
|
|
558
|
+
if args.run_trivy and args.path:
|
|
559
|
+
doc, msg = tv.run_trivy_fs(args.path)
|
|
560
|
+
if doc is not None:
|
|
561
|
+
docs.append(doc)
|
|
562
|
+
else:
|
|
563
|
+
trivy_note = msg
|
|
564
|
+
sys.stderr.write(msg + "\n")
|
|
565
|
+
trivy_fs = [f for d in docs for f in tv.findings(d, kev, epss)]
|
|
566
|
+
if docs and not (args.kev or args.epss):
|
|
567
|
+
trivy_note = (trivy_note + "; " if trivy_note else "") + \
|
|
568
|
+
"non-PHP findings ranked by severity only (pass --kev and --epss for exploitation data)"
|
|
548
569
|
try:
|
|
549
|
-
res = vd.assess(args.path, probe_result=probe_result, logfile=args.log, run_code=not args.no_code
|
|
570
|
+
res = vd.assess(args.path, probe_result=probe_result, logfile=args.log, run_code=not args.no_code,
|
|
571
|
+
trivy_findings=trivy_fs, trivy_note=trivy_note)
|
|
550
572
|
except OSError as e: # unreadable access log
|
|
551
573
|
sys.stderr.write(f"error: {e}\n")
|
|
552
574
|
return 4
|
|
@@ -562,6 +584,112 @@ def _assess(args):
|
|
|
562
584
|
return vd.exit_code(res)
|
|
563
585
|
|
|
564
586
|
|
|
587
|
+
def _trivy_import(args):
|
|
588
|
+
from phpwarden.thirdparty import trivy as tv
|
|
589
|
+
try:
|
|
590
|
+
kev, epss = tv.load_kev(args.kev), tv.load_epss(args.epss)
|
|
591
|
+
fs = [f for p in args.files for f in tv.findings(tv.load_report(p), kev, epss)]
|
|
592
|
+
except (OSError, ValueError) as e:
|
|
593
|
+
sys.stderr.write(f"error: {e}\n")
|
|
594
|
+
return 4
|
|
595
|
+
doc = tv.to_report_doc(fs, args.files)
|
|
596
|
+
if args.json:
|
|
597
|
+
print(json.dumps(doc, indent=2))
|
|
598
|
+
else:
|
|
599
|
+
kev_n = sum(1 for f in fs if f["known_exploited"])
|
|
600
|
+
print(f"{len(fs)} finding(s) from {len(args.files)} Trivy report(s); {kev_n} known exploited. "
|
|
601
|
+
f"Ranked by exploitation{'' if (args.kev or args.epss) else ' (no --kev/--epss: by severity)'}:")
|
|
602
|
+
for f in fs[:50]:
|
|
603
|
+
tag = " [KEV]" if f["known_exploited"] else (f" [EPSS {f['epss'] * 100:.0f}%]" if f.get("epss") else "")
|
|
604
|
+
dev = " (dev)" if f["dev"] else ""
|
|
605
|
+
print(f" {f['severity']:<8} {tv.ident(f)}{tag}{dev} {f['title'][:90]}")
|
|
606
|
+
if len(fs) > 50:
|
|
607
|
+
print(f" ... and {len(fs) - 50} more; use --json for all")
|
|
608
|
+
rc = 1 if any(f["known_exploited"] or f["severity"] in ("critical", "high") for f in fs) else 0
|
|
609
|
+
return _combine(rc, _maybe_report(args, doc))
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def _shield(args):
|
|
613
|
+
from pathlib import Path as _P
|
|
614
|
+
from phpwarden.shield import policy as sp, report as sr
|
|
615
|
+
if args.shield_cmd == "policy":
|
|
616
|
+
root = _P(args.path)
|
|
617
|
+
if not root.is_dir():
|
|
618
|
+
sys.stderr.write(f"error: not a directory: {args.path}\n")
|
|
619
|
+
return 4
|
|
620
|
+
pol = sp.build_policy_for_path(str(root), FeedManager().current(), php_version=args.php_version,
|
|
621
|
+
php_sapi=args.php_sapi, log_path=args.log,
|
|
622
|
+
limits=not args.no_limits, limit_paths=args.limit_path or [],
|
|
623
|
+
trust_proxy=args.trust_proxy or [])
|
|
624
|
+
out = args.out or "phpwarden-shield.policy.json"
|
|
625
|
+
j, php = sp.write_policy(pol, out)
|
|
626
|
+
if args.json:
|
|
627
|
+
print(json.dumps(pol, indent=2))
|
|
628
|
+
return 0
|
|
629
|
+
print(f"wrote {j} and {php} ({len(pol['rules'])} rule(s), mode: log)")
|
|
630
|
+
for r in pol["rules"]:
|
|
631
|
+
print(f" + {r['id']:<34} {r.get('cve') or '':<16} {r['component']}: {r['why']}")
|
|
632
|
+
if not pol["rules"]:
|
|
633
|
+
print(" No exploited vulnerability with a known request shape is installed here; the policy only has")
|
|
634
|
+
print(" the generic checks, if any applied. Nothing to protect against beyond those.")
|
|
635
|
+
print("\nNext: `phpwarden shield install` shows how to load the agent. It only logs until you promote rules.")
|
|
636
|
+
return 0
|
|
637
|
+
if args.shield_cmd == "install":
|
|
638
|
+
agent = _P(__file__).resolve().parent / "shield" / "agent" / "phpwarden-shield.php"
|
|
639
|
+
dest = _P(args.dest) if args.dest else None
|
|
640
|
+
if dest:
|
|
641
|
+
try:
|
|
642
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
643
|
+
(dest / "phpwarden-shield.php").write_bytes(agent.read_bytes())
|
|
644
|
+
except OSError as e:
|
|
645
|
+
sys.stderr.write(f"error: {e}\n")
|
|
646
|
+
return 4
|
|
647
|
+
where = (dest / "phpwarden-shield.php").resolve()
|
|
648
|
+
print(f"copied the agent to {where}")
|
|
649
|
+
else:
|
|
650
|
+
where = "/path/to/phpwarden-shield.php"
|
|
651
|
+
print(f"The agent ships with phpwarden at {agent}. Copy it next to your site, or rerun with --dest DIR.")
|
|
652
|
+
print(f"""
|
|
653
|
+
Load it before your application (pick one; nothing is changed for you):
|
|
654
|
+
php.ini or .user.ini: auto_prepend_file={where}
|
|
655
|
+
WordPress: add require '{where}'; as the first line of wp-config.php
|
|
656
|
+
Laravel / other apps: add require '{where}'; at the top of public/index.php
|
|
657
|
+
|
|
658
|
+
Put phpwarden-shield.policy.php (from `phpwarden shield policy`) next to the agent, or set
|
|
659
|
+
PHPWARDEN_SHIELD_POLICY to its path. It starts in log-only mode and fails open on any error.
|
|
660
|
+
Turn it off instantly: create a file named phpwarden-shield.off next to the agent, or set
|
|
661
|
+
PHPWARDEN_SHIELD=off.""")
|
|
662
|
+
return 0
|
|
663
|
+
if args.shield_cmd == "report":
|
|
664
|
+
try:
|
|
665
|
+
with open(args.log, errors="replace") as fh:
|
|
666
|
+
summ = sr.summarize(fh)
|
|
667
|
+
except OSError as e:
|
|
668
|
+
sys.stderr.write(f"error: {e}\n")
|
|
669
|
+
return 4
|
|
670
|
+
print(json.dumps(summ, indent=2, default=list) if args.json else sr.to_text(summ))
|
|
671
|
+
return 0
|
|
672
|
+
if args.shield_cmd == "promote":
|
|
673
|
+
try:
|
|
674
|
+
pol = json.loads(_P(args.policy).read_text())
|
|
675
|
+
with open(args.log, errors="replace") as fh:
|
|
676
|
+
summ = sr.summarize(fh)
|
|
677
|
+
except (OSError, ValueError) as e:
|
|
678
|
+
sys.stderr.write(f"error: {e}\n")
|
|
679
|
+
return 4
|
|
680
|
+
pol, rep = sp.promote(pol, summ, operator_ack=args.i_understand_this_blocks_requests,
|
|
681
|
+
rule_ids=set(args.rule) if args.rule else None)
|
|
682
|
+
for rid in rep["promoted"]:
|
|
683
|
+
print(f" BLOCK {rid}")
|
|
684
|
+
for r in rep["refused"]:
|
|
685
|
+
print(f" still logging {r['id']}: {r['reason']}")
|
|
686
|
+
if rep["promoted"]:
|
|
687
|
+
sp.write_policy(pol, args.policy)
|
|
688
|
+
print(f"\nupdated {args.policy} (and its .php twin). Promoted rules now return 403.")
|
|
689
|
+
return 0
|
|
690
|
+
return EX_USAGE
|
|
691
|
+
|
|
692
|
+
|
|
565
693
|
def _integrity(args):
|
|
566
694
|
from phpwarden import integrity as integ
|
|
567
695
|
res = integ.check(args.path, locale=args.locale, plugins=not args.no_plugins)
|
|
@@ -674,6 +802,44 @@ def main(argv=None):
|
|
|
674
802
|
_report_args(wpc)
|
|
675
803
|
wpc.set_defaults(func=_wordpress)
|
|
676
804
|
|
|
805
|
+
tvi = sub.add_parser("trivy-import", help="read Trivy JSON reports (npm/JS, OS packages, images, IaC), "
|
|
806
|
+
"rank them by exploitation, optionally send them to the dashboard")
|
|
807
|
+
tvi.add_argument("files", nargs="+", metavar="TRIVY_JSON", help="output of trivy fs/image/config --format json")
|
|
808
|
+
tvi.add_argument("--kev", metavar="FILE", help="CISA KEV catalogue JSON")
|
|
809
|
+
tvi.add_argument("--epss", metavar="FILE", help="FIRST EPSS CSV (.csv or .csv.gz)")
|
|
810
|
+
tvi.add_argument("--json", action="store_true", help="machine-readable JSON output")
|
|
811
|
+
_report_args(tvi)
|
|
812
|
+
tvi.set_defaults(func=_trivy_import)
|
|
813
|
+
|
|
814
|
+
sh = sub.add_parser("shield", help="protection inside PHP: block exploits of the vulnerabilities this site "
|
|
815
|
+
"actually has (log-only until you promote rules)")
|
|
816
|
+
shs = sh.add_subparsers(dest="shield_cmd", required=True, parser_class=_Parser)
|
|
817
|
+
shp = shs.add_parser("policy", help="build a policy for the exploited vulnerabilities installed at PATH")
|
|
818
|
+
shp.add_argument("path", nargs="?", default=".", help="project or WordPress root (default: .)")
|
|
819
|
+
shp.add_argument("--out", help="policy file (default phpwarden-shield.policy.json; a .php twin is written too)")
|
|
820
|
+
shp.add_argument("--php-version", help="the production PHP version, for engine CVE rules")
|
|
821
|
+
shp.add_argument("--php-sapi", choices=["fpm", "cgi", "cli"], help="the production SAPI")
|
|
822
|
+
shp.add_argument("--log", help="where the agent should write its log (default: next to the agent)")
|
|
823
|
+
shp.add_argument("--no-limits", action="store_true",
|
|
824
|
+
help="leave out the rate limits (login, password reset, xmlrpc, burst guard)")
|
|
825
|
+
shp.add_argument("--limit-path", action="append", metavar="REGEX",
|
|
826
|
+
help="also rate-limit requests to this path (repeatable)")
|
|
827
|
+
shp.add_argument("--trust-proxy", action="append", metavar="CIDR",
|
|
828
|
+
help="trust forwarded client IPs only from this proxy range, e.g. your CDN (repeatable)")
|
|
829
|
+
shp.add_argument("--json", action="store_true", help="print the policy as JSON")
|
|
830
|
+
shi = shs.add_parser("install", help="show how to load the agent (and copy it with --dest)")
|
|
831
|
+
shi.add_argument("--dest", metavar="DIR", help="copy phpwarden-shield.php into this folder")
|
|
832
|
+
shr = shs.add_parser("report", help="summarise the agent's log")
|
|
833
|
+
shr.add_argument("log", help="phpwarden-shield.log")
|
|
834
|
+
shr.add_argument("--json", action="store_true", help="machine-readable JSON output")
|
|
835
|
+
shm = shs.add_parser("promote", help="switch rules from logging to blocking after a clean burn-in")
|
|
836
|
+
shm.add_argument("policy", help="the policy JSON to update")
|
|
837
|
+
shm.add_argument("--log", required=True, help="the agent log covering the burn-in")
|
|
838
|
+
shm.add_argument("--rule", action="append", metavar="ID", help="only this rule (repeatable)")
|
|
839
|
+
shm.add_argument("--i-understand-this-blocks-requests", action="store_true",
|
|
840
|
+
help="required: promoted rules return 403 to matching requests")
|
|
841
|
+
sh.set_defaults(func=_shield)
|
|
842
|
+
|
|
677
843
|
asx = sub.add_parser("assess", help="one ranked verdict from every view: installed versions, your code, "
|
|
678
844
|
"the live site from outside, and attacks in the access log")
|
|
679
845
|
asx.add_argument("path", nargs="?", help="project or site root (composer.lock, WordPress, PHP code)")
|
|
@@ -686,6 +852,12 @@ def main(argv=None):
|
|
|
686
852
|
asx.add_argument("--baseline", metavar="FILE",
|
|
687
853
|
help="an earlier --out file: report what is fixed and verified, still open, and new")
|
|
688
854
|
asx.add_argument("--json", action="store_true", help="machine-readable JSON output")
|
|
855
|
+
asx.add_argument("--trivy", metavar="FILE", action="append",
|
|
856
|
+
help="add a Trivy JSON report (npm/JS, OS packages, images, IaC); repeatable")
|
|
857
|
+
asx.add_argument("--run-trivy", action="store_true",
|
|
858
|
+
help="run `trivy fs` on the path if Trivy is installed (never downloads it)")
|
|
859
|
+
asx.add_argument("--kev", metavar="FILE", help="CISA KEV catalogue JSON, to rank non-PHP CVEs by exploitation")
|
|
860
|
+
asx.add_argument("--epss", metavar="FILE", help="FIRST EPSS CSV (.csv or .csv.gz) for non-PHP CVEs")
|
|
689
861
|
asx.set_defaults(func=_assess)
|
|
690
862
|
|
|
691
863
|
cd = sub.add_parser("code", help="scan your own PHP code for high-signal security bugs "
|