phpwarden 2.3.0__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.
Files changed (119) hide show
  1. {phpwarden-2.3.0/src/phpwarden.egg-info → phpwarden-2.4.0}/PKG-INFO +31 -12
  2. phpwarden-2.3.0/PKG-INFO → phpwarden-2.4.0/README.md +28 -31
  3. {phpwarden-2.3.0 → phpwarden-2.4.0}/pyproject.toml +4 -4
  4. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/__init__.py +1 -1
  5. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/cli.py +71 -2
  6. phpwarden-2.4.0/src/phpwarden/sast/dbflow.py +720 -0
  7. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/engine.py +11 -4
  8. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/taint.py +50 -3
  9. phpwarden-2.4.0/src/phpwarden/shield/EDGE.md +108 -0
  10. phpwarden-2.4.0/src/phpwarden/shield/README.md +111 -0
  11. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/shield/agent/phpwarden-shield.php +237 -7
  12. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/shield/policy.py +56 -10
  13. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/shield/report.py +19 -3
  14. phpwarden-2.4.0/src/phpwarden/thirdparty/__init__.py +1 -0
  15. phpwarden-2.4.0/src/phpwarden/thirdparty/trivy.py +188 -0
  16. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/verdict.py +56 -4
  17. phpwarden-2.3.0/README.md → phpwarden-2.4.0/src/phpwarden.egg-info/PKG-INFO +50 -9
  18. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden.egg-info/SOURCES.txt +7 -0
  19. phpwarden-2.4.0/tests/test_sast_dbflow.py +106 -0
  20. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_shield_dashboard.py +9 -0
  21. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_shield_runtime.py +155 -0
  22. phpwarden-2.4.0/tests/test_trivy_import.py +131 -0
  23. {phpwarden-2.3.0 → phpwarden-2.4.0}/LICENSE +0 -0
  24. {phpwarden-2.3.0 → phpwarden-2.4.0}/setup.cfg +0 -0
  25. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/code.py +0 -0
  26. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/code_rules.py +0 -0
  27. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/data/php-cve-feed.json +0 -0
  28. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/deps.py +0 -0
  29. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/detect.py +0 -0
  30. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/engine.py +0 -0
  31. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/env.py +0 -0
  32. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/feed.py +0 -0
  33. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/guard.py +0 -0
  34. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/integrity.py +0 -0
  35. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/probe.py +0 -0
  36. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/report.py +0 -0
  37. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/rules.py +0 -0
  38. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sarif.py +0 -0
  39. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/__init__.py +0 -0
  40. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/ast.py +0 -0
  41. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/authz.py +0 -0
  42. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/callgraph.py +0 -0
  43. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/lexer.py +0 -0
  44. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/models/__init__.py +0 -0
  45. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/models/laravel.py +0 -0
  46. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/models/php_builtins.py +0 -0
  47. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/models/symfony.py +0 -0
  48. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/models/wordpress.py +0 -0
  49. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/parser.py +0 -0
  50. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/project.py +0 -0
  51. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/symbols.py +0 -0
  52. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/sast/types.py +0 -0
  53. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/shield/__init__.py +0 -0
  54. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/shield/library.py +0 -0
  55. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/shield/matcher.py +0 -0
  56. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/version.py +0 -0
  57. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/vulndb.py +0 -0
  58. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden/wordpress.py +0 -0
  59. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden.egg-info/dependency_links.txt +0 -0
  60. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden.egg-info/entry_points.txt +0 -0
  61. {phpwarden-2.3.0 → phpwarden-2.4.0}/src/phpwarden.egg-info/top_level.txt +0 -0
  62. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_account_deletion.py +0 -0
  63. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_accounts.py +0 -0
  64. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_advisories.py +0 -0
  65. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_agency.py +0 -0
  66. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_assess.py +0 -0
  67. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_assignment.py +0 -0
  68. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_audit.py +0 -0
  69. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_billing.py +0 -0
  70. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_business_metrics.py +0 -0
  71. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_cli_edges.py +0 -0
  72. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_code.py +0 -0
  73. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_connect_flow.py +0 -0
  74. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_dashboard_states.py +0 -0
  75. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_deps_and_rules.py +0 -0
  76. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_detection_validation.py +0 -0
  77. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_developer.py +0 -0
  78. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_discussion.py +0 -0
  79. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_e2e_readiness.py +0 -0
  80. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_email.py +0 -0
  81. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_engine.py +0 -0
  82. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_engine_feed.py +0 -0
  83. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_feed_license.py +0 -0
  84. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_feed_subscription.py +0 -0
  85. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_fleet.py +0 -0
  86. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_free_feed.py +0 -0
  87. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_google_oauth.py +0 -0
  88. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_guard_live.py +0 -0
  89. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_integrity.py +0 -0
  90. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_kev_alert.py +0 -0
  91. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_kev_gaps.py +0 -0
  92. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_livefeed.py +0 -0
  93. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_mentions.py +0 -0
  94. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_probe.py +0 -0
  95. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_probe_cli.py +0 -0
  96. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_probe_dashboard.py +0 -0
  97. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_reachability.py +0 -0
  98. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_report_proof.py +0 -0
  99. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_saas.py +0 -0
  100. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_saas_billing.py +0 -0
  101. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_sarif.py +0 -0
  102. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_sast_layer5.py +0 -0
  103. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_sast_models.py +0 -0
  104. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_sast_parser.py +0 -0
  105. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_sast_project.py +0 -0
  106. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_sast_taint.py +0 -0
  107. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_server.py +0 -0
  108. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_settings.py +0 -0
  109. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_shield.py +0 -0
  110. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_shield_cli.py +0 -0
  111. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_shield_plan.py +0 -0
  112. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_slack.py +0 -0
  113. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_sso.py +0 -0
  114. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_store_connections.py +0 -0
  115. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_team.py +0 -0
  116. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_triage.py +0 -0
  117. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_waf_dryrun.py +0 -0
  118. {phpwarden-2.3.0 → phpwarden-2.4.0}/tests/test_webhook_idempotency.py +0 -0
  119. {phpwarden-2.3.0 → 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.3.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.
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 and WordPress
32
- installs against one bundled, offline advisory feed. Zero runtime dependencies
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
@@ -144,7 +145,26 @@ error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
144
145
  error. Full table: `phpwarden --help`.
145
146
 
146
147
 
147
- ## New in 2.3.0 and 2.2
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).
148
168
 
149
169
  - **`phpwarden probe URL`**: a live check of a running site using GET and HEAD
150
170
  requests only. It looks for exposed files (`.env`, `.git`, backups), open debug
@@ -161,7 +181,6 @@ error. Full table: `phpwarden --help`.
161
181
  Follows data stored in the session or WordPress options (second-order).
162
182
  89 rules. On the same pinned apps it found more than Semgrep and Psalm with far
163
183
  fewer findings on clean framework code ([comparison](https://github.com/oroma-alfred/phpwarden/tree/main/bench/sast-compare-2026-11)).
164
- Data that round-trips through the database is not followed.
165
184
  - **`phpwarden shield`**: one PHP file loaded before your app that blocks exploit
166
185
  attempts against the exploited vulnerabilities this site actually has, scoped
167
186
  to their entry points. Logs until you promote rules (7-day burn-in, explicit
@@ -248,7 +267,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
248
267
  ```yaml
249
268
  repos:
250
269
  - repo: https://github.com/oroma-alfred/phpwarden
251
- rev: v2.3.0 # pin to a tag with `pre-commit autoupdate`
270
+ rev: v2.4.0 # pin to a tag with `pre-commit autoupdate`
252
271
  hooks:
253
272
  - id: phpwarden-deps
254
273
  # args: ["--no-reachability"] # faster: skip the reachability pass
@@ -259,14 +278,14 @@ timer recipes are in [`docs/recipes.md`](docs/recipes.md).
259
278
 
260
279
  ## The feed
261
280
 
262
- The bundled community feed (2026.11.13.3 in this release) works forever, offline:
281
+ The bundled community feed (2026.11.14.2 in this release) works forever, offline:
263
282
 
264
283
  - **~21,300 advisories**: ~8,800 Composer (from [OSV.dev](https://osv.dev) and
265
284
  [FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
266
285
  ~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
267
- and 145 PHP engine/FPM CVEs, including hand-curated entries with per-branch
268
- fixed versions, exposure conditions and detection signatures. About 19,700 carry an
269
- EPSS score; 41 distinct vulnerabilities were in KEV when this feed was built.
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.
270
289
  - **License-independent by construction.** Every source is open or
271
290
  public-domain (OSV, NVD, FriendsOfPHP, php-src release notes, CISA KEV, FIRST EPSS). No commercial
272
291
  vendor's feed is used, so coverage cannot be revoked or priced away.
@@ -1,25 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: phpwarden
3
- Version: 2.3.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
- 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
1
  # phpwarden
24
2
 
25
3
  **PHP vulnerability scanner that ranks findings by real-world exploitation,
@@ -28,8 +6,9 @@ exploited (CISA KEV, as of 2026-11-14). phpwarden puts those first, then sorts
28
6
  by EPSS probability, then CVSS — so a `composer.lock` with 30 findings tells
29
7
  you which one to fix today.
30
8
 
31
- It scans the PHP engine/FPM version, Composer dependencies and WordPress
32
- installs against one bundled, offline advisory feed. Zero runtime dependencies
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
33
12
  (Python 3.8+), Apache-2.0.
34
13
 
35
14
  **Reproducible benchmark:** on four real PHP stacks, phpwarden found 295 of 295
@@ -144,7 +123,26 @@ error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
144
123
  error. Full table: `phpwarden --help`.
145
124
 
146
125
 
147
- ## New in 2.3.0 and 2.2
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).
148
146
 
149
147
  - **`phpwarden probe URL`**: a live check of a running site using GET and HEAD
150
148
  requests only. It looks for exposed files (`.env`, `.git`, backups), open debug
@@ -161,7 +159,6 @@ error. Full table: `phpwarden --help`.
161
159
  Follows data stored in the session or WordPress options (second-order).
162
160
  89 rules. On the same pinned apps it found more than Semgrep and Psalm with far
163
161
  fewer findings on clean framework code ([comparison](https://github.com/oroma-alfred/phpwarden/tree/main/bench/sast-compare-2026-11)).
164
- Data that round-trips through the database is not followed.
165
162
  - **`phpwarden shield`**: one PHP file loaded before your app that blocks exploit
166
163
  attempts against the exploited vulnerabilities this site actually has, scoped
167
164
  to their entry points. Logs until you promote rules (7-day burn-in, explicit
@@ -248,7 +245,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
248
245
  ```yaml
249
246
  repos:
250
247
  - repo: https://github.com/oroma-alfred/phpwarden
251
- rev: v2.3.0 # pin to a tag with `pre-commit autoupdate`
248
+ rev: v2.4.0 # pin to a tag with `pre-commit autoupdate`
252
249
  hooks:
253
250
  - id: phpwarden-deps
254
251
  # args: ["--no-reachability"] # faster: skip the reachability pass
@@ -259,14 +256,14 @@ timer recipes are in [`docs/recipes.md`](docs/recipes.md).
259
256
 
260
257
  ## The feed
261
258
 
262
- The bundled community feed (2026.11.13.3 in this release) works forever, offline:
259
+ The bundled community feed (2026.11.14.2 in this release) works forever, offline:
263
260
 
264
261
  - **~21,300 advisories**: ~8,800 Composer (from [OSV.dev](https://osv.dev) and
265
262
  [FriendsOfPHP](https://github.com/FriendsOfPHP/security-advisories)),
266
263
  ~12,300 WordPress (from [NVD](https://nvd.nist.gov): plugin, core and theme),
267
- and 145 PHP engine/FPM CVEs, including hand-curated entries with per-branch
268
- fixed versions, exposure conditions and detection signatures. About 19,700 carry an
269
- EPSS score; 41 distinct vulnerabilities were in KEV when this feed was built.
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.
270
267
  - **License-independent by construction.** Every source is open or
271
268
  public-domain (OSV, NVD, FriendsOfPHP, php-src release notes, CISA KEV, FIRST EPSS). No commercial
272
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.3.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."
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", "shield/agent/*.php"]
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.3.0"
9
+ __version__ = "2.4.0"
@@ -546,8 +546,29 @@ def _assess(args):
546
546
  if any("homepage unreachable" in n for n in probe_result.get("notes", [])):
547
547
  sys.stderr.write(f"error: could not reach {url}\n")
548
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)"
549
569
  try:
550
- 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)
551
572
  except OSError as e: # unreadable access log
552
573
  sys.stderr.write(f"error: {e}\n")
553
574
  return 4
@@ -563,6 +584,31 @@ def _assess(args):
563
584
  return vd.exit_code(res)
564
585
 
565
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
+
566
612
  def _shield(args):
567
613
  from pathlib import Path as _P
568
614
  from phpwarden.shield import policy as sp, report as sr
@@ -572,7 +618,9 @@ def _shield(args):
572
618
  sys.stderr.write(f"error: not a directory: {args.path}\n")
573
619
  return 4
574
620
  pol = sp.build_policy_for_path(str(root), FeedManager().current(), php_version=args.php_version,
575
- php_sapi=args.php_sapi, log_path=args.log)
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 [])
576
624
  out = args.out or "phpwarden-shield.policy.json"
577
625
  j, php = sp.write_policy(pol, out)
578
626
  if args.json:
@@ -754,6 +802,15 @@ def main(argv=None):
754
802
  _report_args(wpc)
755
803
  wpc.set_defaults(func=_wordpress)
756
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
+
757
814
  sh = sub.add_parser("shield", help="protection inside PHP: block exploits of the vulnerabilities this site "
758
815
  "actually has (log-only until you promote rules)")
759
816
  shs = sh.add_subparsers(dest="shield_cmd", required=True, parser_class=_Parser)
@@ -763,6 +820,12 @@ def main(argv=None):
763
820
  shp.add_argument("--php-version", help="the production PHP version, for engine CVE rules")
764
821
  shp.add_argument("--php-sapi", choices=["fpm", "cgi", "cli"], help="the production SAPI")
765
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)")
766
829
  shp.add_argument("--json", action="store_true", help="print the policy as JSON")
767
830
  shi = shs.add_parser("install", help="show how to load the agent (and copy it with --dest)")
768
831
  shi.add_argument("--dest", metavar="DIR", help="copy phpwarden-shield.php into this folder")
@@ -789,6 +852,12 @@ def main(argv=None):
789
852
  asx.add_argument("--baseline", metavar="FILE",
790
853
  help="an earlier --out file: report what is fixed and verified, still open, and new")
791
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")
792
861
  asx.set_defaults(func=_assess)
793
862
 
794
863
  cd = sub.add_parser("code", help="scan your own PHP code for high-signal security bugs "