Repository navigation
v2.34.0 — the path the server ran
v2.34.0
The path the server ran.
A file the web server runs directly, such as WordPress's wp-login.php, xmlrpc.php and every /wp-admin/*.php, or Drupal's core/install.php, was matched as /. So the WordPress preset never fired on the pages it names, a /wp-login.php rate limit never counted, and a negated path condition matched every admin screen. Nothing reported it, and firewall-check said the rules matched. global.path_source: script_name fixes it for the sites that need it.
PHP 8.1–8.5 supported. The new keys are opt-in, and the default is unchanged. One dependency minimum goes up; see What changes on upgrade.
What changes on upgrade
- Nothing about
path, unless you setpath_source.pathinfostays the default in 2.x, because switching would change whatpathmeans on sites that work today. matomo/device-detectoris now required at^6.5.2, up from^6.4. 6.5.2 adds Nikto to its bot database, so abot:truerule now matches Nikto on every install, rather than depending on which version happened to be installed. PHP support is unchanged (^7.2|^8.0). A site on 6.5.1 would have got this on its nextcomposer updateanyway.- Block records store a direct file's URL correctly. A record for
/wp-login.phpused to storehttps://host/wp-login.php/, with a trailing slash. This applies whateverpath_sourceis. firewall-checkprints a note on stderr when--urlnames a.phpfile other thanindex.phpand there's no--script-name. stdout, and so--json, is unaffected.- The
bot:notice no longer lists Nikto among whatbot:misses.
Which setting you need
| Application | path_source |
|---|---|
| WordPress | script_name |
Drupal, for rules on core/install.php, core/rebuild.php or core/authorize.php |
script_name |
Laravel, Symfony, Drupal's routes: anything where every request goes through index.php |
the default, pathinfo |
global:
path_source: script_name
base_path: /blog # only if the site is installed in a subdirectoryscript_name gives the same answer as pathinfo on a front-controller app. It isn't "more correct" there, so leave it off.
Added
-
global.path_source(#414, #415).pathinfo(default) is Symfony'sgetPathInfo(), the path relative to the front controller.script_nameis the file the server ran:SCRIPT_NAME+PATH_INFO, falling back togetPathInfo()when that file is the front controller. It feeds everypathcondition, rate-limit pattern and preset, and thepathin logs, block records and{{request.path}}.Request Server runs pathinfoscript_name/wp-login.phpwp-login.php//wp-login.php/wp-admin/edit.phpwp-admin/edit.php//wp-admin/edit.php/wp-admin/wp-admin/index.php//wp-admin/index.php/learning/index.php/learning//learning/It uses the file that ran, deliberately, not the raw URL. The server decodes and normalises the URL before it chooses a file.
/./wp-login.php,/%77p-login.php,//wp-login.php,/x/../wp-login.phpand/wp-login.php;xall runwp-login.php, and a raw-URL match would have let each of them past apath:/wp-login.phprate limit. Underscript_name, they're all/wp-login.phpand count against the same budget. There's a test for each spelling.It's read from the request being evaluated, never from
$_SERVER, so it's right under Octane, RoadRunner and Swoole. -
global.base_path, for a subdirectory install. On a direct-file request nothing says where the application starts (getBasePath()for/wp-admin/edit.phpis/wp-admin), so it's named:<base_path>/index.phpis the front controller, andbase_pathcomes off the front of any other file's path, at a segment boundary only./blog/wp-login.phpthen matches the presets as/wp-login.php. -
firewall-check --script-name=FILEchecks a request the way the site receives it. Without it, the tool matches the path as typed, which is how it came to report WordPress rules as matching when they never did. The output adds amatched as pathline when the path differs from the URL, and--json'srequest.pathis the path the rules saw. -
firewall-doctorreports which path source is in use. An unknownpath_sourceor an unusablebase_pathis an error. At runtime, the firewall falls back topathinfowith a warning, the same way an unknownmodefalls back toblock.
Fixed
presets/wordpress.yml,search-bots.ymlanddrupal.ymlnever saw direct-file requests under the documented integration. Withpath_source: script_namethey do. Thesearch-bots.ymlexclusion was the worst of them: it's a negated path match, so matched against/it was always true, and the crawler allow covered exactly the back end it was written to keep out. The presets now say so in their headers; that's a comment change only, so no Preset-Version bump.- The WordPress integration docs now set
path_source: script_name. The Drupal, Symfony and Laravel sections say to keep the default. Path Source has the full explanation. - CI broke on the device-detector 6.5.2 release (#416).
composer.lockisn't committed, so CI resolved 6.5.2 as soon as it shipped, and three tests used Nikto as their example of an agentbot:misses. Raising the minimum makes that the same everywhere.
Not in this release
pathinfohas a related raw-URL weakness throughindex.php:/./user/logindoesn't match a rule on/user/login. It needs a different fix, normalising the default source./WP-LOGIN.PHPon a case-insensitive filesystem.- Downstream:
kanopi/firewall-laravelandkanopi/firewall-symfonyforward options tofirewall-checkexplicitly, so--script-nameneeds adding there before their users can pass it.
Full changelog: v2.33.2...v2.34.0