diff --git a/projects/packages/waf/changelog/fix-protect-174-cli-cron-403 b/projects/packages/waf/changelog/fix-protect-174-cli-cron-403 new file mode 100644 index 000000000000..5118259a2c0c --- /dev/null +++ b/projects/packages/waf/changelog/fix-protect-174-cli-cron-403 @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +WAF: Skip rule evaluation when there is no HTTP request (e.g. server-side cron executed via a PHP CLI wrapper), preventing false-positive 403 blocks from rule 911100. diff --git a/projects/packages/waf/src/class-waf-runner.php b/projects/packages/waf/src/class-waf-runner.php index 929f118cd7cd..2d8d3eda16f8 100644 --- a/projects/packages/waf/src/class-waf-runner.php +++ b/projects/packages/waf/src/class-waf-runner.php @@ -238,8 +238,12 @@ public static function run() { // like PHP's prepend_file setting (yay!). define( 'JETPACK_WAF_RUN', defined( 'ABSPATH' ) ? 'plugin' : 'preload' ); - // if the WAF is being run before a command line script, don't try to execute rules (there's no request). - if ( PHP_SAPI === 'cli' ) { + // If the WAF is being run before a command line script, or in any other non-HTTP + // context (e.g. server-side cron executed via a PHP wrapper that does not report + // PHP_SAPI as 'cli'), there is no HTTP request to evaluate. Skip rule execution so + // HTTP-specific rules (e.g. rule 911100, which checks the request method) don't + // produce a false-positive 403 block. + if ( PHP_SAPI === 'cli' || ! isset( $_SERVER['REQUEST_METHOD'] ) ) { return; } diff --git a/projects/packages/waf/tests/php/unit/WafRunnerTest.php b/projects/packages/waf/tests/php/unit/WafRunnerTest.php index 660e4b3f93e4..7423a420039b 100644 --- a/projects/packages/waf/tests/php/unit/WafRunnerTest.php +++ b/projects/packages/waf/tests/php/unit/WafRunnerTest.php @@ -7,6 +7,7 @@ use Automattic\Jetpack\Waf\Waf_Constants; use Automattic\Jetpack\Waf\Waf_Runner; +use PHPUnit\Framework\Attributes\PreserveGlobalState; use PHPUnit\Framework\Attributes\RunInSeparateProcess; /** @@ -51,4 +52,50 @@ public function testRunSetsConstants() { $this->assertSame( '/pseudo/dir/jetpack-waf', JETPACK_WAF_DIR ); $this->assertSame( '/pseudo/dir/../wp-config.php', JETPACK_WAF_WPCONFIG ); } + + /** + * Test that run does not evaluate rules when there is no HTTP request method. + * + * Reproduces PROTECT-174: when wp-cron.php is executed directly (e.g. via a PHP + * wrapper for a server-side cron) there is no HTTP context, so REQUEST_METHOD is + * absent. The WAF must skip rule evaluation in that case so that HTTP-specific + * rules (e.g. rule 911100, which checks the request method) don't fire a + * false-positive 403. + * + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + #[RunInSeparateProcess] + #[PreserveGlobalState( false )] + public function testRunSkipsRulesWhenRequestMethodIsAbsent() { + define( 'ABSPATH', '/pseudo' ); + define( 'WP_CONTENT_DIR', '/pseudo/dir' ); + + // Simulate a non-HTTP execution context: no REQUEST_METHOD present. + unset( $_SERVER['REQUEST_METHOD'] ); + + // Point the WAF at a rules file that would set a flag if it were ever included. + $rules_dir = sys_get_temp_dir() . '/jetpack-waf-test-' . uniqid(); + $rules_file = $rules_dir . '/rules.php'; + mkdir( $rules_dir ); + file_put_contents( $rules_file, 'assertTrue( defined( 'JETPACK_WAF_RUN' ), 'Waf_Runner::run() should have started executing.' ); + // ...but returned early without ever including the rules file. + $this->assertFalse( defined( 'JETPACK_WAF_RULES_EXECUTED' ), 'WAF rules must not be evaluated when there is no HTTP request method.' ); + } finally { + // Clean up, even if an assertion fails. + unlink( $rules_file ); + rmdir( $rules_dir ); + } + } }