Skip to content

Commit ec2ff94

Browse files
authored
Merge pull request #2773 from WordPress/develop
Release WordPressCS 3.4.1
2 parents 469c18c + b558639 commit ec2ff94

11 files changed

Lines changed: 190 additions & 114 deletions

‎.github/SECURITY.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Security Policy
2+
3+
## Supported Versions
4+
5+
The latest minor version of the `3.x` release series is supported for security updates.
6+
7+
## Reporting a Vulnerability
8+
9+
The WordPressCS team takes security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
10+
11+
**Please do not report or discuss security vulnerabilities through public GitHub issues, discussions, or pull requests.**
12+
13+
Issues can be reported privately to the maintainers by opening a [Security vulnerability report].
14+
15+
> [!CAUTION]
16+
> Please take note that while the WordPress organisation has a HackerOne program, the WordPress Coding Standards software is not covered by this program.
17+
> Full details of the WordPress Security Policy and the list of covered projects and infrastructure can be found on [HackerOne][WordPress HackerOne].
18+
19+
### Preferences
20+
21+
* Please provide detailed reports with reproducible steps and a clearly defined impact.
22+
* Include the version number of the vulnerable package in your report.
23+
* Fixes are most welcome.
24+
25+
A private PR can be created from the security report to work on and discuss the patch.
26+
27+
[Security vulnerability report]: https://github.com/WordPress/WordPress-Coding-Standards/security/advisories/new
28+
[WordPress HackerOne]: https://hackerone.com/wordpress

‎.github/release-checklist.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH
1616
- [ ] PHPCSExtra: check if there have been [releases][phpcsextra-releases] since the last WordPressCS release and check through the changelog to see if there is anything WordPressCS could take advantage of - PR #xxx
1717
- [ ] Check if the minimum WP version property needs updating in `MinimumWPVersionTrait::$default_minimum_wp_version` and if so, action it - PR #xxx
1818
- [ ] Check if the `minimum_wp_version` and `testVersion` properties in `phpcs.xml.dist.sample` need updating and if so, action it - PR #xxx
19+
- [ ] Check if the PHPCompatibility `testVersion` mentioned in the README needs updating and if so, action it - PR #xxx
20+
:pencil2: Rule of thumb: stay in line with the "support three versions of WP below the current version" guideline, as also applied for the `minimum_wp_version`.
1921
- [ ] Check if any of the list based sniffs need updating and if so, action it.
2022
:pencil2: Make sure the "last updated" annotation in the docblocks for these lists has also been updated!
2123
List based sniffs:
@@ -48,14 +50,16 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH
4850
- [ ] Merge this PR.
4951
- [ ] Make sure all CI builds are green.
5052
- [ ] Tag and create a release against `main` (careful, GH defaults to `develop`!) & copy & paste the changelog to it.
53+
_From within the GH interface: Code tab -> Releases -> "Draft a new release" button at the top of the page._
5154
:pencil2: Check if anything from the link collection at the bottom of the changelog needs to be copied in!
5255
- Remove square brackets from all ticket links or make them proper full links (as GH markdown parser doesn't parse these correctly).
5356
- Change all contributor links to full inline links (as GH markdown parser on the Releases page doesn't parse these correctly).
5457
- [ ] Make sure all CI builds are green.
5558
- [ ] Close the milestone.
5659
- [ ] Open a new milestone for the next release.
5760
- [ ] If any open PRs/issues which were milestoned for this release did not make it into the release, update their milestone.
58-
- [ ] Fast-forward `develop` to be equal to `main`.
61+
- [ ] Fast-forward `develop` to be equal to `main` (`git checkout develop && git pull upstream/develop && git merge main`).
62+
:pencil: Branch protection may need to get a temporary exception to allow for pushing the `develop` branch. Please remember to remove the exception once the push has gone through!
5963

6064
### After release
6165

@@ -69,7 +73,8 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH
6973
:pencil2: No need to post in the #core-coding-standard channel as that gets an automated release notification anyway.
7074
- [ ] Optionally post in #plugin-review if a sniff was added in a release which was requested by the plugin review team.
7175
- [ ] Optionally post in #core-docs if significant updates were made to the documentation ruleset.
72-
- [ ] Create a Marketing team ["amplify request"][amplify-request].
76+
- [ ] Create a Marketing team ["amplify request"][amplify-request].
77+
Example post: https://github.com/WordPress/marketing/issues/746
7378
- [ ] Submit for the ["Monthly Dev Roundup"][dev-roundup].
7479

7580
[phpcs-releases]: https://github.com/PHPCSStandards/PHP_CodeSniffer/releases

‎.github/workflows/basic-qa.yml‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626

2727
steps:
2828
- name: Checkout repository
29-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
29+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3030
with:
3131
persist-credentials: false
3232

@@ -117,7 +117,7 @@ jobs:
117117

118118
steps:
119119
- name: Checkout code
120-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
120+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
121121
with:
122122
persist-credentials: false
123123

@@ -157,7 +157,7 @@ jobs:
157157

158158
steps:
159159
- name: Checkout repository
160-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
160+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
161161
with:
162162
persist-credentials: false
163163

@@ -240,7 +240,7 @@ jobs:
240240

241241
steps:
242242
- name: Checkout code
243-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
243+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
244244
with:
245245
persist-credentials: false
246246

@@ -270,7 +270,7 @@ jobs:
270270

271271
steps:
272272
- name: "Checkout"
273-
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
273+
uses: "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1" # v7.0.1
274274
with:
275275
persist-credentials: false
276276

‎.github/workflows/quicktest.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030

3131
steps:
3232
- name: Checkout repository
33-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3434
with:
3535
persist-credentials: false
3636

‎.github/workflows/unit-tests.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474

7575
steps:
7676
- name: Checkout repository
77-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
77+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
7878
with:
7979
persist-credentials: false
8080

‎CHANGELOG.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,27 @@ This project adheres to [Semantic Versioning](https://semver.org/) and [Keep a C
88

99
_No documentation available about unreleased changes as of yet._
1010

11+
## [3.4.1] - 2026-07-27
12+
13+
**This is a security release and all users are advised to update their WordPressCS install as soon as possible.**
14+
15+
### Changed
16+
- The minimum required `PHPCSUtils` version to 1.2.3 (was 1.2.2). [#2770]
17+
- The minimum required `PHPCSExtra` version to 1.5.1 (was 1.5.0). [#2770]
18+
- Various housekeeping, including documentation improvements.
19+
20+
### Fixed
21+
- **SECURITY FIX**: Running the `WordPress.WP.EnqueuedResourceParameters` sniff over untrusted PHP code, for example, in a CI pipeline that lints pull requests, or on a developer machine reviewing third-party code, could lead to arbitrary command execution on the scanning host. [#2771]
22+
This affects users of the `WordPress` and `WordPress-Extra` rulesets. The `WordPress-Core` ruleset and the `WordPress-Docs` ruleset are not affected.
23+
For more details, see the [security advisory][sec-1].
24+
Thanks to [@FORIMOC] for responsibly disclosing the vulnerability.
25+
26+
[sec-1]: https://github.com/WordPress/WordPress-Coding-Standards/security/advisories/GHSA-3pwp-g2mj-5p3v
27+
28+
[#2770]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2770
29+
[#2771]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2771
30+
31+
1132
## [3.4.0] - 2026-07-16
1233

1334
We're happy to welcome [@rodrigoprimo] as co-maintainer of WordPressCS as of this release.
@@ -1797,6 +1818,7 @@ Initial tagged release.
17971818
[PHPCompatibility]: https://github.com/PHPCompatibility/PHPCompatibility
17981819

17991820
[Unreleased]: https://github.com/WordPress/WordPress-Coding-Standards/compare/main...HEAD
1821+
[3.4.1]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.4.0...3.4.1
18001822
[3.4.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.3.0...3.4.0
18011823
[3.3.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.2.0...3.3.0
18021824
[3.2.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.1.0...3.2.0
@@ -1842,6 +1864,7 @@ Initial tagged release.
18421864
[@dd32]: https://github.com/dd32
18431865
[@desrosj]: https://github.com/desrosj
18441866
[@dingo-d]: https://github.com/dingo-d
1867+
[@FORIMOC]: https://github.com/FORIMOC
18451868
[@fredden]: https://github.com/fredden
18461869
[@GaryJones]: https://github.com/GaryJones
18471870
[@gogdzl]: https://github.com/gogdzl

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ Install either as a separate ruleset and run it separately against your code or
163163
</rule>
164164
```
165165

166-
Whichever way you run it, do make sure you set the `testVersion` to run the sniffs against. The `testVersion` determines for which PHP versions you will receive compatibility information. The recommended setting for this at this moment is `7.2-` to support the same PHP versions as WordPress Core supports.
166+
Whichever way you run it, do make sure you set the `testVersion` to run the sniffs against. The `testVersion` determines for which PHP versions you will receive compatibility information. The recommended setting for this at this moment is `7.2-` to support the last three WordPress releases.
167167

168168
For more information about setting the `testVersion`, see:
169169
* [PHPCompatibility: Sniffing your code for compatibility with specific PHP version(s)](https://github.com/PHPCompatibility/PHPCompatibility#sniffing-your-code-for-compatibility-with-specific-php-versions)

‎WordPress/Sniffs/WP/EnqueuedResourceParametersSniff.php‎

Lines changed: 58 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
namespace WordPressCS\WordPress\Sniffs\WP;
1111

1212
use PHP_CodeSniffer\Util\Tokens;
13+
use PHPCSUtils\Tokens\Collections;
14+
use PHPCSUtils\Utils\Arrays;
1315
use PHPCSUtils\Utils\Numbers;
1416
use PHPCSUtils\Utils\PassedParameters;
17+
use PHPCSUtils\Utils\TextStrings;
1518
use WordPressCS\WordPress\AbstractFunctionParameterSniff;
1619

1720
/**
@@ -65,46 +68,17 @@ final class EnqueuedResourceParametersSniff extends AbstractFunctionParameterSni
6568
\T_NS_SEPARATOR => \T_NS_SEPARATOR, // Needed to handle fully qualified \false (PHPCS 3.x).
6669
);
6770

68-
/**
69-
* Token codes which are "safe" to accept to determine whether a version would evaluate to `false`.
70-
*
71-
* This array is enriched with several of the PHPCS token arrays in the register() method.
72-
*
73-
* @var array<int|string, int|string>
74-
*/
75-
private $safe_tokens = array(
76-
\T_NULL => \T_NULL,
77-
\T_FALSE => \T_FALSE,
78-
\T_TRUE => \T_TRUE,
79-
\T_LNUMBER => \T_LNUMBER,
80-
\T_DNUMBER => \T_DNUMBER,
81-
\T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
82-
\T_START_NOWDOC => \T_START_NOWDOC,
83-
\T_NOWDOC => \T_NOWDOC,
84-
\T_END_NOWDOC => \T_END_NOWDOC,
85-
\T_OPEN_PARENTHESIS => \T_OPEN_PARENTHESIS,
86-
\T_CLOSE_PARENTHESIS => \T_CLOSE_PARENTHESIS,
87-
\T_STRING_CONCAT => \T_STRING_CONCAT,
88-
);
89-
9071
/**
9172
* Returns an array of tokens this test wants to listen for.
9273
*
9374
* Overloads and calls the parent method to allow for adding additional tokens to the
94-
* $false_tokens and $safe_tokens properties.
75+
* $false_tokens property.
9576
*
9677
* @return array
9778
*/
9879
public function register() {
9980
$this->false_tokens += Tokens::$emptyTokens;
10081

101-
$this->safe_tokens += Tokens::$emptyTokens;
102-
$this->safe_tokens += Tokens::$assignmentTokens;
103-
$this->safe_tokens += Tokens::$comparisonTokens;
104-
$this->safe_tokens += Tokens::$operators;
105-
$this->safe_tokens += Tokens::$booleanOperators;
106-
$this->safe_tokens += Tokens::$castTokens;
107-
10882
return parent::register();
10983
}
11084

@@ -194,6 +168,12 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p
194168
/**
195169
* Determine if a range has a falsy value.
196170
*
171+
* Only a limited set of values is recognized as falsy:
172+
* - Boolean false.
173+
* - An integer or float equal to zero.
174+
* - A text string with the content `'0'` or `''` (single or double-quoted, heredoc, or nowdoc).
175+
* - An empty array.
176+
*
197177
* @param int $start The position to start looking from.
198178
* @param int $end The position to stop looking (inclusive).
199179
*
@@ -202,67 +182,76 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p
202182
* couldn't be reliably determined.
203183
*/
204184
protected function is_falsy( $start, $end ) {
205-
206185
// Find anything excluding the false tokens.
207186
$has_non_false = $this->phpcsFile->findNext( $this->false_tokens, $start, ( $end + 1 ), true );
208187
// If no non-false tokens are found, we are good.
209188
if ( false === $has_non_false ) {
210189
return true;
211190
}
212191

213-
$code_string = '';
214-
for ( $i = $start; $i <= $end; $i++ ) {
215-
if ( isset( $this->safe_tokens[ $this->tokens[ $i ]['code'] ] ) === false ) {
216-
// Function call/variable or other token which makes it neigh impossible
217-
// to determine whether the actual value would evaluate to false.
192+
$target_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $start, ( $end + 1 ), true );
193+
194+
// An array only evaluates to false when it is empty.
195+
if ( isset( Collections::arrayOpenTokensBC()[ $this->tokens[ $target_ptr ]['code'] ] ) ) {
196+
$open_close = Arrays::getOpenClose( $this->phpcsFile, $target_ptr );
197+
if ( false === $open_close ) {
198+
// Short list assignment, not an array.
218199
return false;
219200
}
220201

221-
if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) {
222-
continue;
223-
}
202+
// Bail if there is any non-empty token in the $ver parameter after the array, as that's a more complex
203+
// expression which can't be reliably evaluated.
204+
$next_after_array = $this->phpcsFile->findNext(
205+
Tokens::$emptyTokens,
206+
( $open_close['closer'] + 1 ),
207+
( $end + 1 ),
208+
true
209+
);
224210

225-
// Make sure that PHP 7.4 numeric literals and PHP 8.1 explicit octals don't cause problems.
226-
if ( \T_LNUMBER === $this->tokens[ $i ]['code'] || \T_DNUMBER === $this->tokens[ $i ]['code'] ) {
227-
$number_info = Numbers::getCompleteNumber( $this->phpcsFile, $i );
228-
$code_string .= $number_info['decimal'];
229-
$i = $number_info['last_token'];
230-
continue;
211+
if ( false !== $next_after_array ) {
212+
return false;
231213
}
232214

233-
// Make sure that when deprecated casts are used in the code under scan and the sniff is run on PHP 8.5,
234-
// the eval() won't cause a deprecation notice, borking the scan of the file.
235-
if ( \PHP_VERSION_ID >= 80500 ) {
236-
if ( \T_INT_CAST === $this->tokens[ $i ]['code'] ) {
237-
$code_string .= '(int)';
238-
continue;
239-
}
215+
$first_non_empty_in_array = $this->phpcsFile->findNext(
216+
Tokens::$emptyTokens,
217+
( $open_close['opener'] + 1 ),
218+
$open_close['closer'],
219+
true
220+
);
240221

241-
if ( \T_DOUBLE_CAST === $this->tokens[ $i ]['code'] ) {
242-
$code_string .= '(float)';
243-
continue;
244-
}
222+
return ( false === $first_non_empty_in_array );
223+
}
245224

246-
if ( \T_BOOL_CAST === $this->tokens[ $i ]['code'] ) {
247-
$code_string .= '(bool)';
248-
continue;
249-
}
225+
// Check if it is a '0' or '' string.
226+
if ( isset( Collections::textStringStartTokens()[ $this->tokens[ $target_ptr ]['code'] ] ) ) {
227+
if ( \T_DOUBLE_QUOTED_STRING === $this->tokens[ $target_ptr ]['code'] ) {
228+
// No need to examine as it will never match/can't be determined.
229+
return false;
230+
}
250231

251-
if ( \T_BINARY_CAST === $this->tokens[ $i ]['code'] ) {
252-
$code_string .= '(string)';
253-
continue;
254-
}
232+
$valid_tokens = array( \T_CONSTANT_ENCAPSED_STRING ) + Tokens::$heredocTokens + Tokens::$emptyTokens;
233+
if ( false !== $this->phpcsFile->findNext( $valid_tokens, $start, ( $end + 1 ), true ) ) {
234+
// Bail if the $ver parameter is more than a single text string.
235+
return false;
255236
}
256237

257-
$code_string .= $this->tokens[ $i ]['content'];
238+
$content = TextStrings::getCompleteTextString( $this->phpcsFile, $target_ptr );
239+
240+
return '0' === $content || '' === $content;
258241
}
259242

260-
if ( '' === $code_string ) {
243+
// The int/float check below only handles a single literal token, so bail if there is more than one non-empty token.
244+
if ( false !== $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $target_ptr + 1 ), ( $end + 1 ), true ) ) {
261245
return false;
262246
}
263247

264-
// Evaluate the argument to figure out the outcome is false or not.
265-
// phpcs:ignore Squiz.PHP.Eval -- No harm here.
266-
return ( false === eval( "return (bool) $code_string;" ) );
248+
// Check if it is an int or float equal to zero.
249+
if ( \T_LNUMBER === $this->tokens[ $target_ptr ]['code'] || \T_DNUMBER === $this->tokens[ $target_ptr ]['code'] ) {
250+
$number_info = Numbers::getCompleteNumber( $this->phpcsFile, $target_ptr );
251+
252+
return 0.0 === (float) $number_info['decimal'];
253+
}
254+
255+
return false;
267256
}
268257
}

0 commit comments

Comments
 (0)