phpwarden 2.2.0__tar.gz → 2.3.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 (112) hide show
  1. {phpwarden-2.2.0 → phpwarden-2.3.0}/PKG-INFO +23 -10
  2. {phpwarden-2.2.0 → phpwarden-2.3.0}/README.md +22 -9
  3. {phpwarden-2.2.0 → phpwarden-2.3.0}/pyproject.toml +2 -2
  4. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/__init__.py +1 -1
  5. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/cli.py +103 -0
  6. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/code.py +628 -62
  7. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/code_rules.py +73 -3
  8. phpwarden-2.3.0/src/phpwarden/sast/__init__.py +10 -0
  9. phpwarden-2.3.0/src/phpwarden/sast/ast.py +85 -0
  10. phpwarden-2.3.0/src/phpwarden/sast/authz.py +284 -0
  11. phpwarden-2.3.0/src/phpwarden/sast/callgraph.py +627 -0
  12. phpwarden-2.3.0/src/phpwarden/sast/engine.py +131 -0
  13. phpwarden-2.3.0/src/phpwarden/sast/lexer.py +354 -0
  14. phpwarden-2.3.0/src/phpwarden/sast/models/__init__.py +6 -0
  15. phpwarden-2.3.0/src/phpwarden/sast/models/laravel.py +115 -0
  16. phpwarden-2.3.0/src/phpwarden/sast/models/php_builtins.py +123 -0
  17. phpwarden-2.3.0/src/phpwarden/sast/models/symfony.py +77 -0
  18. phpwarden-2.3.0/src/phpwarden/sast/models/wordpress.py +28 -0
  19. phpwarden-2.3.0/src/phpwarden/sast/parser.py +1565 -0
  20. phpwarden-2.3.0/src/phpwarden/sast/project.py +633 -0
  21. phpwarden-2.3.0/src/phpwarden/sast/symbols.py +184 -0
  22. phpwarden-2.3.0/src/phpwarden/sast/taint.py +2437 -0
  23. phpwarden-2.3.0/src/phpwarden/sast/types.py +432 -0
  24. phpwarden-2.3.0/src/phpwarden/shield/__init__.py +1 -0
  25. phpwarden-2.3.0/src/phpwarden/shield/agent/phpwarden-shield.php +342 -0
  26. phpwarden-2.3.0/src/phpwarden/shield/library.py +403 -0
  27. phpwarden-2.3.0/src/phpwarden/shield/matcher.py +119 -0
  28. phpwarden-2.3.0/src/phpwarden/shield/policy.py +208 -0
  29. phpwarden-2.3.0/src/phpwarden/shield/report.py +73 -0
  30. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden.egg-info/PKG-INFO +23 -10
  31. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden.egg-info/SOURCES.txt +31 -0
  32. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_code.py +87 -4
  33. phpwarden-2.3.0/tests/test_report_proof.py +53 -0
  34. phpwarden-2.3.0/tests/test_sast_layer5.py +82 -0
  35. phpwarden-2.3.0/tests/test_sast_models.py +85 -0
  36. phpwarden-2.3.0/tests/test_sast_parser.py +237 -0
  37. phpwarden-2.3.0/tests/test_sast_project.py +134 -0
  38. phpwarden-2.3.0/tests/test_sast_taint.py +223 -0
  39. phpwarden-2.3.0/tests/test_shield_cli.py +51 -0
  40. phpwarden-2.3.0/tests/test_shield_dashboard.py +159 -0
  41. phpwarden-2.3.0/tests/test_shield_runtime.py +225 -0
  42. {phpwarden-2.2.0 → phpwarden-2.3.0}/LICENSE +0 -0
  43. {phpwarden-2.2.0 → phpwarden-2.3.0}/setup.cfg +0 -0
  44. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/data/php-cve-feed.json +0 -0
  45. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/deps.py +0 -0
  46. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/detect.py +0 -0
  47. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/engine.py +0 -0
  48. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/env.py +0 -0
  49. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/feed.py +0 -0
  50. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/guard.py +0 -0
  51. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/integrity.py +0 -0
  52. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/probe.py +0 -0
  53. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/report.py +0 -0
  54. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/rules.py +0 -0
  55. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/sarif.py +0 -0
  56. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/verdict.py +0 -0
  57. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/version.py +0 -0
  58. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/vulndb.py +0 -0
  59. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden/wordpress.py +0 -0
  60. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden.egg-info/dependency_links.txt +0 -0
  61. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden.egg-info/entry_points.txt +0 -0
  62. {phpwarden-2.2.0 → phpwarden-2.3.0}/src/phpwarden.egg-info/top_level.txt +0 -0
  63. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_account_deletion.py +0 -0
  64. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_accounts.py +0 -0
  65. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_advisories.py +0 -0
  66. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_agency.py +0 -0
  67. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_assess.py +0 -0
  68. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_assignment.py +0 -0
  69. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_audit.py +0 -0
  70. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_billing.py +0 -0
  71. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_business_metrics.py +0 -0
  72. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_cli_edges.py +0 -0
  73. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_connect_flow.py +0 -0
  74. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_dashboard_states.py +0 -0
  75. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_deps_and_rules.py +0 -0
  76. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_detection_validation.py +0 -0
  77. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_developer.py +0 -0
  78. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_discussion.py +0 -0
  79. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_e2e_readiness.py +0 -0
  80. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_email.py +0 -0
  81. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_engine.py +0 -0
  82. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_engine_feed.py +0 -0
  83. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_feed_license.py +0 -0
  84. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_feed_subscription.py +0 -0
  85. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_fleet.py +0 -0
  86. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_free_feed.py +0 -0
  87. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_google_oauth.py +0 -0
  88. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_guard_live.py +0 -0
  89. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_integrity.py +0 -0
  90. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_kev_alert.py +0 -0
  91. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_kev_gaps.py +0 -0
  92. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_livefeed.py +0 -0
  93. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_mentions.py +0 -0
  94. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_probe.py +0 -0
  95. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_probe_cli.py +0 -0
  96. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_probe_dashboard.py +0 -0
  97. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_reachability.py +0 -0
  98. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_saas.py +0 -0
  99. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_saas_billing.py +0 -0
  100. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_sarif.py +0 -0
  101. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_server.py +0 -0
  102. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_settings.py +0 -0
  103. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_shield.py +0 -0
  104. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_shield_plan.py +0 -0
  105. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_slack.py +0 -0
  106. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_sso.py +0 -0
  107. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_store_connections.py +0 -0
  108. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_team.py +0 -0
  109. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_triage.py +0 -0
  110. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_waf_dryrun.py +0 -0
  111. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_webhook_idempotency.py +0 -0
  112. {phpwarden-2.2.0 → phpwarden-2.3.0}/tests/test_wordpress_and_report.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phpwarden
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: PHP vulnerability scanner for the PHP engine, Composer, WordPress, your own code and the live site. Ranked by real-world exploitation (CISA KEV, then EPSS), not CVSS. Offline, zero dependencies.
5
5
  Author: oroma-alfred
6
6
  License-Expression: Apache-2.0
@@ -59,7 +59,8 @@ $ phpwarden deps composer.lock
59
59
  | Composer dependency CVEs | `deps` | Snyk, `composer audit` |
60
60
  | WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
61
61
  | WordPress file integrity (wordpress.org checksums, injected files) | `integrity` | Wordfence CLI malware-scan (partly) |
62
- | Your own PHP code (75 rules) | `code` | — (not a PHPStan/Psalm replacement) |
62
+ | Your own PHP code: whole-program, Laravel/Symfony/WordPress-aware (89 rules) | `code` | Snyk Code, Semgrep, Psalm taint |
63
+ | Exploits of the vulnerabilities this site has, blocked inside PHP | `shield` | the WAF rule you would have to write |
63
64
  | Live site from outside: exposed files, debug endpoints | `probe` | — |
64
65
  | One ranked verdict across all of the above | `assess` | — |
65
66
  | Exploitation signatures in logs | `detect` | SIEM content |
@@ -120,7 +121,8 @@ phpwarden rules --format nginx --out php-block.conf # PHP-specific WAF rules (
120
121
  phpwarden rules --format nginx --dry-run # the same rules, log-only: nothing is blocked
121
122
  phpwarden rules --replay /var/log/nginx/access.log # what the rules would have blocked in an existing log
122
123
  phpwarden probe https://example.com # live check of a site you own: exposed files, debug endpoints, versions vs the feed
123
- phpwarden code . # your own PHP: 75 high-signal rules, 74 on by default (injection, unserialize, WordPress handlers)
124
+ phpwarden code . # your own PHP, whole program: 89 rules (injection, auth, mass assignment, Blade/Twig, WordPress)
125
+ phpwarden shield policy . && phpwarden shield install # block exploits of what is installed here, inside PHP (log-only first)
124
126
  phpwarden assess . --url https://example.com --log access.log # one ranked verdict; --out / --baseline shows what is fixed and verified
125
127
  phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
126
128
  phpwarden feed status # which feed is active, how many advisories
@@ -133,7 +135,7 @@ phpwarden feed update # pull a newer feed: free weekly snapshot,
133
135
  `probe`, `code`, `assess`, `--format sarif`, `rules --dry-run` / `--replay` and
134
136
  the no-key `feed update` are new in 2.2.0.
135
137
 
136
- Every command takes `--json`. Exit codes are shared across subcommands and
138
+ Every scanning command takes `--json` (`shield install` and `shield promote` do not). Exit codes are shared across subcommands and
137
139
  built for gating: `0` clean, `1` exposed / vulnerable / signature hits / tampered
138
140
  files / high or critical `code` and `probe` findings (`probe` also on any
139
141
  known-exploited finding) / anything to act on now or this week (`assess`),
@@ -142,7 +144,7 @@ error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
142
144
  error. Full table: `phpwarden --help`.
143
145
 
144
146
 
145
- ## New in 2.2.0
147
+ ## New in 2.3.0 and 2.2
146
148
 
147
149
  - **`phpwarden probe URL`**: a live check of a running site using GET and HEAD
148
150
  requests only. It looks for exposed files (`.env`, `.git`, backups), open debug
@@ -150,10 +152,21 @@ error. Full table: `phpwarden --help`.
150
152
  matches those against the feed. Active checks run only after you prove the
151
153
  site is yours (DNS TXT record, a file, or a meta tag; `--print-token` shows
152
154
  how). Without that it reads the homepage and stops.
153
- - **`phpwarden code PATH`**: 75 rules (74 on by default) for your own PHP code, following request
154
- data to `eval`, shell commands, SQL, `unserialize`, includes and redirects.
155
- It follows data through one level of same-file helper, not across files, and
156
- does not replace PHPStan or Psalm. `phpwarden code --list-rules` lists every rule.
155
+ - **`phpwarden code PATH`** (whole-program engine, default in 2.3.0): parses the
156
+ project, resolves namespaces, Composer autoloading, inheritance and types, and
157
+ follows request data across files to SQL, shell, `eval`, `unserialize`,
158
+ includes, redirects and HTTP clients, with a trace per finding (SARIF
159
+ `codeFlows`). Models Laravel, Symfony and WordPress: routes with no auth check,
160
+ mass assignment, raw Blade/Twig output, IDOR hints, CSRF exclusions, nonces.
161
+ Follows data stored in the session or WordPress options (second-order).
162
+ 89 rules. On the same pinned apps it found more than Semgrep and Psalm with far
163
+ 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
+ - **`phpwarden shield`**: one PHP file loaded before your app that blocks exploit
166
+ attempts against the exploited vulnerabilities this site actually has, scoped
167
+ to their entry points. Logs until you promote rules (7-day burn-in, explicit
168
+ flag), fails open, has a kill switch, about 0.07 ms per request. Not an edge
169
+ WAF: no DDoS, bot or TLS handling.
157
170
  - **`phpwarden assess`**: one ranked list from every view of a site: installed
158
171
  versions, your code, the live site from outside, and the access log, with the
159
172
  reason for each item ("exploited, reachable, visible from outside, already
@@ -235,7 +248,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
235
248
  ```yaml
236
249
  repos:
237
250
  - repo: https://github.com/oroma-alfred/phpwarden
238
- rev: v2.1.0 # pin to a tag with `pre-commit autoupdate`
251
+ rev: v2.3.0 # pin to a tag with `pre-commit autoupdate`
239
252
  hooks:
240
253
  - id: phpwarden-deps
241
254
  # args: ["--no-reachability"] # faster: skip the reachability pass
@@ -37,7 +37,8 @@ $ phpwarden deps composer.lock
37
37
  | Composer dependency CVEs | `deps` | Snyk, `composer audit` |
38
38
  | WordPress core, plugin and theme CVEs | `wordpress` | Patchstack, Wordfence |
39
39
  | WordPress file integrity (wordpress.org checksums, injected files) | `integrity` | Wordfence CLI malware-scan (partly) |
40
- | Your own PHP code (75 rules) | `code` | — (not a PHPStan/Psalm replacement) |
40
+ | Your own PHP code: whole-program, Laravel/Symfony/WordPress-aware (89 rules) | `code` | Snyk Code, Semgrep, Psalm taint |
41
+ | Exploits of the vulnerabilities this site has, blocked inside PHP | `shield` | the WAF rule you would have to write |
41
42
  | Live site from outside: exposed files, debug endpoints | `probe` | — |
42
43
  | One ranked verdict across all of the above | `assess` | — |
43
44
  | Exploitation signatures in logs | `detect` | SIEM content |
@@ -98,7 +99,8 @@ phpwarden rules --format nginx --out php-block.conf # PHP-specific WAF rules (
98
99
  phpwarden rules --format nginx --dry-run # the same rules, log-only: nothing is blocked
99
100
  phpwarden rules --replay /var/log/nginx/access.log # what the rules would have blocked in an existing log
100
101
  phpwarden probe https://example.com # live check of a site you own: exposed files, debug endpoints, versions vs the feed
101
- phpwarden code . # your own PHP: 75 high-signal rules, 74 on by default (injection, unserialize, WordPress handlers)
102
+ phpwarden code . # your own PHP, whole program: 89 rules (injection, auth, mass assignment, Blade/Twig, WordPress)
103
+ phpwarden shield policy . && phpwarden shield install # block exploits of what is installed here, inside PHP (log-only first)
102
104
  phpwarden assess . --url https://example.com --log access.log # one ranked verdict; --out / --baseline shows what is fixed and verified
103
105
  phpwarden report web01.json web02.json --out fleet.html # aggregate a fleet view
104
106
  phpwarden feed status # which feed is active, how many advisories
@@ -111,7 +113,7 @@ phpwarden feed update # pull a newer feed: free weekly snapshot,
111
113
  `probe`, `code`, `assess`, `--format sarif`, `rules --dry-run` / `--replay` and
112
114
  the no-key `feed update` are new in 2.2.0.
113
115
 
114
- Every command takes `--json`. Exit codes are shared across subcommands and
116
+ Every scanning command takes `--json` (`shield install` and `shield promote` do not). Exit codes are shared across subcommands and
115
117
  built for gating: `0` clean, `1` exposed / vulnerable / signature hits / tampered
116
118
  files / high or critical `code` and `probe` findings (`probe` also on any
117
119
  known-exploited finding) / anything to act on now or this week (`assess`),
@@ -120,7 +122,7 @@ error, `5` `--report-to` upload failed on an otherwise clean run, `64` usage
120
122
  error. Full table: `phpwarden --help`.
121
123
 
122
124
 
123
- ## New in 2.2.0
125
+ ## New in 2.3.0 and 2.2
124
126
 
125
127
  - **`phpwarden probe URL`**: a live check of a running site using GET and HEAD
126
128
  requests only. It looks for exposed files (`.env`, `.git`, backups), open debug
@@ -128,10 +130,21 @@ error. Full table: `phpwarden --help`.
128
130
  matches those against the feed. Active checks run only after you prove the
129
131
  site is yours (DNS TXT record, a file, or a meta tag; `--print-token` shows
130
132
  how). Without that it reads the homepage and stops.
131
- - **`phpwarden code PATH`**: 75 rules (74 on by default) for your own PHP code, following request
132
- data to `eval`, shell commands, SQL, `unserialize`, includes and redirects.
133
- It follows data through one level of same-file helper, not across files, and
134
- does not replace PHPStan or Psalm. `phpwarden code --list-rules` lists every rule.
133
+ - **`phpwarden code PATH`** (whole-program engine, default in 2.3.0): parses the
134
+ project, resolves namespaces, Composer autoloading, inheritance and types, and
135
+ follows request data across files to SQL, shell, `eval`, `unserialize`,
136
+ includes, redirects and HTTP clients, with a trace per finding (SARIF
137
+ `codeFlows`). Models Laravel, Symfony and WordPress: routes with no auth check,
138
+ mass assignment, raw Blade/Twig output, IDOR hints, CSRF exclusions, nonces.
139
+ Follows data stored in the session or WordPress options (second-order).
140
+ 89 rules. On the same pinned apps it found more than Semgrep and Psalm with far
141
+ fewer findings on clean framework code ([comparison](https://github.com/oroma-alfred/phpwarden/tree/main/bench/sast-compare-2026-11)).
142
+ Data that round-trips through the database is not followed.
143
+ - **`phpwarden shield`**: one PHP file loaded before your app that blocks exploit
144
+ attempts against the exploited vulnerabilities this site actually has, scoped
145
+ to their entry points. Logs until you promote rules (7-day burn-in, explicit
146
+ flag), fails open, has a kill switch, about 0.07 ms per request. Not an edge
147
+ WAF: no DDoS, bot or TLS handling.
135
148
  - **`phpwarden assess`**: one ranked list from every view of a site: installed
136
149
  versions, your code, the live site from outside, and the access log, with the
137
150
  reason for each item ("exploited, reachable, visible from outside, already
@@ -213,7 +226,7 @@ see [phpwarden/laravel](https://packagist.org/packages/phpwarden/laravel).
213
226
  ```yaml
214
227
  repos:
215
228
  - repo: https://github.com/oroma-alfred/phpwarden
216
- rev: v2.1.0 # pin to a tag with `pre-commit autoupdate`
229
+ rev: v2.3.0 # pin to a tag with `pre-commit autoupdate`
217
230
  hooks:
218
231
  - id: phpwarden-deps
219
232
  # args: ["--no-reachability"] # faster: skip the reachability pass
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "phpwarden"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  description = "PHP vulnerability scanner for the PHP engine, Composer, WordPress, your own code and the live site. Ranked by real-world exploitation (CISA KEV, then EPSS), not CVSS. Offline, zero dependencies."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -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"]
@@ -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.2.0"
9
+ __version__ = "2.3.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
@@ -562,6 +563,85 @@ def _assess(args):
562
563
  return vd.exit_code(res)
563
564
 
564
565
 
566
+ def _shield(args):
567
+ from pathlib import Path as _P
568
+ from phpwarden.shield import policy as sp, report as sr
569
+ if args.shield_cmd == "policy":
570
+ root = _P(args.path)
571
+ if not root.is_dir():
572
+ sys.stderr.write(f"error: not a directory: {args.path}\n")
573
+ return 4
574
+ 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)
576
+ out = args.out or "phpwarden-shield.policy.json"
577
+ j, php = sp.write_policy(pol, out)
578
+ if args.json:
579
+ print(json.dumps(pol, indent=2))
580
+ return 0
581
+ print(f"wrote {j} and {php} ({len(pol['rules'])} rule(s), mode: log)")
582
+ for r in pol["rules"]:
583
+ print(f" + {r['id']:<34} {r.get('cve') or '':<16} {r['component']}: {r['why']}")
584
+ if not pol["rules"]:
585
+ print(" No exploited vulnerability with a known request shape is installed here; the policy only has")
586
+ print(" the generic checks, if any applied. Nothing to protect against beyond those.")
587
+ print("\nNext: `phpwarden shield install` shows how to load the agent. It only logs until you promote rules.")
588
+ return 0
589
+ if args.shield_cmd == "install":
590
+ agent = _P(__file__).resolve().parent / "shield" / "agent" / "phpwarden-shield.php"
591
+ dest = _P(args.dest) if args.dest else None
592
+ if dest:
593
+ try:
594
+ dest.mkdir(parents=True, exist_ok=True)
595
+ (dest / "phpwarden-shield.php").write_bytes(agent.read_bytes())
596
+ except OSError as e:
597
+ sys.stderr.write(f"error: {e}\n")
598
+ return 4
599
+ where = (dest / "phpwarden-shield.php").resolve()
600
+ print(f"copied the agent to {where}")
601
+ else:
602
+ where = "/path/to/phpwarden-shield.php"
603
+ print(f"The agent ships with phpwarden at {agent}. Copy it next to your site, or rerun with --dest DIR.")
604
+ print(f"""
605
+ Load it before your application (pick one; nothing is changed for you):
606
+ php.ini or .user.ini: auto_prepend_file={where}
607
+ WordPress: add require '{where}'; as the first line of wp-config.php
608
+ Laravel / other apps: add require '{where}'; at the top of public/index.php
609
+
610
+ Put phpwarden-shield.policy.php (from `phpwarden shield policy`) next to the agent, or set
611
+ PHPWARDEN_SHIELD_POLICY to its path. It starts in log-only mode and fails open on any error.
612
+ Turn it off instantly: create a file named phpwarden-shield.off next to the agent, or set
613
+ PHPWARDEN_SHIELD=off.""")
614
+ return 0
615
+ if args.shield_cmd == "report":
616
+ try:
617
+ with open(args.log, errors="replace") as fh:
618
+ summ = sr.summarize(fh)
619
+ except OSError as e:
620
+ sys.stderr.write(f"error: {e}\n")
621
+ return 4
622
+ print(json.dumps(summ, indent=2, default=list) if args.json else sr.to_text(summ))
623
+ return 0
624
+ if args.shield_cmd == "promote":
625
+ try:
626
+ pol = json.loads(_P(args.policy).read_text())
627
+ with open(args.log, errors="replace") as fh:
628
+ summ = sr.summarize(fh)
629
+ except (OSError, ValueError) as e:
630
+ sys.stderr.write(f"error: {e}\n")
631
+ return 4
632
+ pol, rep = sp.promote(pol, summ, operator_ack=args.i_understand_this_blocks_requests,
633
+ rule_ids=set(args.rule) if args.rule else None)
634
+ for rid in rep["promoted"]:
635
+ print(f" BLOCK {rid}")
636
+ for r in rep["refused"]:
637
+ print(f" still logging {r['id']}: {r['reason']}")
638
+ if rep["promoted"]:
639
+ sp.write_policy(pol, args.policy)
640
+ print(f"\nupdated {args.policy} (and its .php twin). Promoted rules now return 403.")
641
+ return 0
642
+ return EX_USAGE
643
+
644
+
565
645
  def _integrity(args):
566
646
  from phpwarden import integrity as integ
567
647
  res = integ.check(args.path, locale=args.locale, plugins=not args.no_plugins)
@@ -674,6 +754,29 @@ def main(argv=None):
674
754
  _report_args(wpc)
675
755
  wpc.set_defaults(func=_wordpress)
676
756
 
757
+ sh = sub.add_parser("shield", help="protection inside PHP: block exploits of the vulnerabilities this site "
758
+ "actually has (log-only until you promote rules)")
759
+ shs = sh.add_subparsers(dest="shield_cmd", required=True, parser_class=_Parser)
760
+ shp = shs.add_parser("policy", help="build a policy for the exploited vulnerabilities installed at PATH")
761
+ shp.add_argument("path", nargs="?", default=".", help="project or WordPress root (default: .)")
762
+ shp.add_argument("--out", help="policy file (default phpwarden-shield.policy.json; a .php twin is written too)")
763
+ shp.add_argument("--php-version", help="the production PHP version, for engine CVE rules")
764
+ shp.add_argument("--php-sapi", choices=["fpm", "cgi", "cli"], help="the production SAPI")
765
+ shp.add_argument("--log", help="where the agent should write its log (default: next to the agent)")
766
+ shp.add_argument("--json", action="store_true", help="print the policy as JSON")
767
+ shi = shs.add_parser("install", help="show how to load the agent (and copy it with --dest)")
768
+ shi.add_argument("--dest", metavar="DIR", help="copy phpwarden-shield.php into this folder")
769
+ shr = shs.add_parser("report", help="summarise the agent's log")
770
+ shr.add_argument("log", help="phpwarden-shield.log")
771
+ shr.add_argument("--json", action="store_true", help="machine-readable JSON output")
772
+ shm = shs.add_parser("promote", help="switch rules from logging to blocking after a clean burn-in")
773
+ shm.add_argument("policy", help="the policy JSON to update")
774
+ shm.add_argument("--log", required=True, help="the agent log covering the burn-in")
775
+ shm.add_argument("--rule", action="append", metavar="ID", help="only this rule (repeatable)")
776
+ shm.add_argument("--i-understand-this-blocks-requests", action="store_true",
777
+ help="required: promoted rules return 403 to matching requests")
778
+ sh.set_defaults(func=_shield)
779
+
677
780
  asx = sub.add_parser("assess", help="one ranked verdict from every view: installed versions, your code, "
678
781
  "the live site from outside, and attacks in the access log")
679
782
  asx.add_argument("path", nargs="?", help="project or site root (composer.lock, WordPress, PHP code)")