From 5c4991dcf66c008c36c2e2ded69febb174ccfd64 Mon Sep 17 00:00:00 2001 From: Gary Jones Date: Thu, 30 Jul 2026 16:09:14 +0100 Subject: [PATCH] feat: allow excluding filters from the restricted list PHP 8.1 deprecated `FILTER_SANITIZE_STRING` with no direct replacement, so the recommended pattern became reading the value with `FILTER_UNSAFE_RAW` (or no filter) and sanitising it afterwards. The PHPFilterFunctions sniff flags `FILTER_UNSAFE_RAW` as doing no filtering, which left developers who had knowingly adopted that pattern with no way to silence the warning short of a blanket ignore annotation. Add a configurable `exclude_filters` property so a ruleset can remove specific constants from the restricted list, for example to permit a deliberate `FILTER_UNSAFE_RAW`. Rather than exposing the default list directly, `$restricted_filters` stays private and the effective list is derived by subtracting the excludes, reusing the same `RulesetPropertyHelper::merge_custom_array()` plus `array_diff_key()` idiom already used for the `exclude` property elsewhere in VIPCS. This keeps ownership of the defaults with VIPCS while letting users express only their delta. The subtraction is recalculated per call so an inline `phpcs:set` change is honoured. The property defaults to an empty array, so behaviour is unchanged unless a ruleset opts in. Fixes #753. --- .../Security/PHPFilterFunctionsSniff.php | 31 ++++++++++++++- .../Security/PHPFilterFunctionsUnitTest.inc | 10 +++++ .../Security/PHPFilterFunctionsUnitTest.php | 38 ++++++++++--------- 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/WordPressVIPMinimum/Sniffs/Security/PHPFilterFunctionsSniff.php b/WordPressVIPMinimum/Sniffs/Security/PHPFilterFunctionsSniff.php index 32e9f7a9..77dd1f47 100644 --- a/WordPressVIPMinimum/Sniffs/Security/PHPFilterFunctionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Security/PHPFilterFunctionsSniff.php @@ -12,6 +12,7 @@ use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\Utils\PassedParameters; use WordPressCS\WordPress\AbstractFunctionParameterSniff; +use WordPressCS\WordPress\Helpers\RulesetPropertyHelper; /** * This sniff ensures that proper sanitization is occurring when PHP's filter_* functions are used. @@ -65,6 +66,28 @@ class PHPFilterFunctionsSniff extends AbstractFunctionParameterSniff { 'FILTER_UNSAFE_RAW' => true, ]; + /** + * Filter names to exclude from the list of restricted filters. + * + * This allows a developer who knowingly uses a non-sanitizing filter (for + * example, when they sanitize the value themselves afterwards) to prevent + * the sniff from flagging it, without having to redeclare the full list. + * + * Set this from a custom ruleset, for example to allow `FILTER_UNSAFE_RAW`: + * + * + * + * + * + * + * + * + * + * + * @var array + */ + public $exclude_filters = []; + /** * Process the parameters of a matched function. * @@ -118,7 +141,13 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p return; } - if ( isset( $this->restricted_filters[ $target_param['clean'] ] ) ) { + // Recalculated on each call so an inline change to the `exclude_filters` property is respected. + $restricted_filters = array_diff_key( + $this->restricted_filters, + RulesetPropertyHelper::merge_custom_array( $this->exclude_filters ) + ); + + if ( isset( $restricted_filters[ $target_param['clean'] ] ) ) { $first_non_empty = $this->phpcsFile->findNext( Tokens::$emptyTokens, $target_param['start'], ( $target_param['end'] + 1 ), true ); $message = 'Please use an appropriate filter to sanitize, as "%s" does no filtering, see: http://php.net/manual/en/filter.filters.sanitize.php.'; diff --git a/WordPressVIPMinimum/Tests/Security/PHPFilterFunctionsUnitTest.inc b/WordPressVIPMinimum/Tests/Security/PHPFilterFunctionsUnitTest.inc index 0159aac5..ee7c0927 100644 --- a/WordPressVIPMinimum/Tests/Security/PHPFilterFunctionsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Security/PHPFilterFunctionsUnitTest.inc @@ -98,3 +98,13 @@ filter_input_array( ], ] ); + +/* + * Bug #753: the list of restricted filters can be reduced via the `exclude_filters` property. + */ +$excludable = filter_input( INPUT_POST, 'a', FILTER_UNSAFE_RAW ); // Warning RestrictedFilter. + +// phpcs:set WordPressVIPMinimum.Security.PHPFilterFunctions exclude_filters[] FILTER_UNSAFE_RAW +$excluded = filter_input( INPUT_POST, 'b', FILTER_UNSAFE_RAW ); // OK - excluded via the property. +$still_restricted = filter_var( $value, FILTER_DEFAULT ); // Warning RestrictedFilter - other defaults still apply. +// phpcs:set WordPressVIPMinimum.Security.PHPFilterFunctions exclude_filters[] diff --git a/WordPressVIPMinimum/Tests/Security/PHPFilterFunctionsUnitTest.php b/WordPressVIPMinimum/Tests/Security/PHPFilterFunctionsUnitTest.php index 1faa0728..7a53bcd3 100644 --- a/WordPressVIPMinimum/Tests/Security/PHPFilterFunctionsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Security/PHPFilterFunctionsUnitTest.php @@ -32,24 +32,26 @@ public function getErrorList() { */ public function getWarningList() { return [ - 44 => 1, - 45 => 1, - 46 => 1, - 48 => 1, - 49 => 1, - 50 => 1, - 52 => 1, - 53 => 1, - 54 => 1, - 56 => 1, - 57 => 1, - 58 => 1, - 65 => 1, - 70 => 1, - 71 => 1, - 73 => 1, - 75 => 1, - 81 => 1, + 44 => 1, + 45 => 1, + 46 => 1, + 48 => 1, + 49 => 1, + 50 => 1, + 52 => 1, + 53 => 1, + 54 => 1, + 56 => 1, + 57 => 1, + 58 => 1, + 65 => 1, + 70 => 1, + 71 => 1, + 73 => 1, + 75 => 1, + 81 => 1, + 105 => 1, + 109 => 1, ]; } }