Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Security Policy

## Supported Versions

The latest minor version of the `3.x` release series is supported for security updates.

## Reporting a Vulnerability

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.

**Please do not report or discuss security vulnerabilities through public GitHub issues, discussions, or pull requests.**

Issues can be reported privately to the maintainers by opening a [Security vulnerability report].

> [!CAUTION]
> Please take note that while the WordPress organisation has a HackerOne program, the WordPress Coding Standards software is not covered by this program.
> Full details of the WordPress Security Policy and the list of covered projects and infrastructure can be found on [HackerOne][WordPress HackerOne].

### Preferences

* Please provide detailed reports with reproducible steps and a clearly defined impact.
* Include the version number of the vulnerable package in your report.
* Fixes are most welcome.

A private PR can be created from the security report to work on and discuss the patch.

[Security vulnerability report]: https://github.com/WordPress/WordPress-Coding-Standards/security/advisories/new
[WordPress HackerOne]: https://hackerone.com/wordpress
9 changes: 7 additions & 2 deletions .github/release-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH
- [ ] 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
- [ ] Check if the minimum WP version property needs updating in `MinimumWPVersionTrait::$default_minimum_wp_version` and if so, action it - PR #xxx
- [ ] Check if the `minimum_wp_version` and `testVersion` properties in `phpcs.xml.dist.sample` need updating and if so, action it - PR #xxx
- [ ] Check if the PHPCompatibility `testVersion` mentioned in the README needs updating and if so, action it - PR #xxx
: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`.
- [ ] Check if any of the list based sniffs need updating and if so, action it.
:pencil2: Make sure the "last updated" annotation in the docblocks for these lists has also been updated!
List based sniffs:
Expand Down Expand Up @@ -48,14 +50,16 @@ PR for tracking changes for the x.x.x release. Target release date: **DOW MONTH
- [ ] Merge this PR.
- [ ] Make sure all CI builds are green.
- [ ] Tag and create a release against `main` (careful, GH defaults to `develop`!) & copy & paste the changelog to it.
_From within the GH interface: Code tab -> Releases -> "Draft a new release" button at the top of the page._
:pencil2: Check if anything from the link collection at the bottom of the changelog needs to be copied in!
- Remove square brackets from all ticket links or make them proper full links (as GH markdown parser doesn't parse these correctly).
- Change all contributor links to full inline links (as GH markdown parser on the Releases page doesn't parse these correctly).
- [ ] Make sure all CI builds are green.
- [ ] Close the milestone.
- [ ] Open a new milestone for the next release.
- [ ] If any open PRs/issues which were milestoned for this release did not make it into the release, update their milestone.
- [ ] Fast-forward `develop` to be equal to `main`.
- [ ] Fast-forward `develop` to be equal to `main` (`git checkout develop && git pull upstream/develop && git merge main`).
: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!

### After release

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

[phpcs-releases]: https://github.com/PHPCSStandards/PHP_CodeSniffer/releases
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/basic-qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

Expand Down Expand Up @@ -117,7 +117,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

Expand Down Expand Up @@ -157,7 +157,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

Expand Down Expand Up @@ -240,7 +240,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

Expand Down Expand Up @@ -270,7 +270,7 @@ jobs:

steps:
- name: "Checkout"
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
uses: "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1" # v7.0.1
with:
persist-credentials: false

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/quicktest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ This project adheres to [Semantic Versioning](https://semver.org/) and [Keep a C

_No documentation available about unreleased changes as of yet._

## [3.4.1] - 2026-07-27

**This is a security release and all users are advised to update their WordPressCS install as soon as possible.**

### Changed
- The minimum required `PHPCSUtils` version to 1.2.3 (was 1.2.2). [#2770]
- The minimum required `PHPCSExtra` version to 1.5.1 (was 1.5.0). [#2770]
- Various housekeeping, including documentation improvements.

### Fixed
- **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]
This affects users of the `WordPress` and `WordPress-Extra` rulesets. The `WordPress-Core` ruleset and the `WordPress-Docs` ruleset are not affected.
For more details, see the [security advisory][sec-1].
Thanks to [@FORIMOC] for responsibly disclosing the vulnerability.

[sec-1]: https://github.com/WordPress/WordPress-Coding-Standards/security/advisories/GHSA-3pwp-g2mj-5p3v

[#2770]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2770
[#2771]: https://github.com/WordPress/WordPress-Coding-Standards/pull/2771


## [3.4.0] - 2026-07-16

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

[Unreleased]: https://github.com/WordPress/WordPress-Coding-Standards/compare/main...HEAD
[3.4.1]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.4.0...3.4.1
[3.4.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.3.0...3.4.0
[3.3.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.2.0...3.3.0
[3.2.0]: https://github.com/WordPress/WordPress-Coding-Standards/compare/3.1.0...3.2.0
Expand Down Expand Up @@ -1842,6 +1864,7 @@ Initial tagged release.
[@dd32]: https://github.com/dd32
[@desrosj]: https://github.com/desrosj
[@dingo-d]: https://github.com/dingo-d
[@FORIMOC]: https://github.com/FORIMOC
[@fredden]: https://github.com/fredden
[@GaryJones]: https://github.com/GaryJones
[@gogdzl]: https://github.com/gogdzl
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ Install either as a separate ruleset and run it separately against your code or
</rule>
```

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.
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.

For more information about setting the `testVersion`, see:
* [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)
Expand Down
127 changes: 58 additions & 69 deletions WordPress/Sniffs/WP/EnqueuedResourceParametersSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
namespace WordPressCS\WordPress\Sniffs\WP;

use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Tokens\Collections;
use PHPCSUtils\Utils\Arrays;
use PHPCSUtils\Utils\Numbers;
use PHPCSUtils\Utils\PassedParameters;
use PHPCSUtils\Utils\TextStrings;
use WordPressCS\WordPress\AbstractFunctionParameterSniff;

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

/**
* Token codes which are "safe" to accept to determine whether a version would evaluate to `false`.
*
* This array is enriched with several of the PHPCS token arrays in the register() method.
*
* @var array<int|string, int|string>
*/
private $safe_tokens = array(
\T_NULL => \T_NULL,
\T_FALSE => \T_FALSE,
\T_TRUE => \T_TRUE,
\T_LNUMBER => \T_LNUMBER,
\T_DNUMBER => \T_DNUMBER,
\T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING,
\T_START_NOWDOC => \T_START_NOWDOC,
\T_NOWDOC => \T_NOWDOC,
\T_END_NOWDOC => \T_END_NOWDOC,
\T_OPEN_PARENTHESIS => \T_OPEN_PARENTHESIS,
\T_CLOSE_PARENTHESIS => \T_CLOSE_PARENTHESIS,
\T_STRING_CONCAT => \T_STRING_CONCAT,
);

/**
* Returns an array of tokens this test wants to listen for.
*
* Overloads and calls the parent method to allow for adding additional tokens to the
* $false_tokens and $safe_tokens properties.
* $false_tokens property.
*
* @return array
*/
public function register() {
$this->false_tokens += Tokens::$emptyTokens;

$this->safe_tokens += Tokens::$emptyTokens;
$this->safe_tokens += Tokens::$assignmentTokens;
$this->safe_tokens += Tokens::$comparisonTokens;
$this->safe_tokens += Tokens::$operators;
$this->safe_tokens += Tokens::$booleanOperators;
$this->safe_tokens += Tokens::$castTokens;

return parent::register();
}

Expand Down Expand Up @@ -194,6 +168,12 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p
/**
* Determine if a range has a falsy value.
*
* Only a limited set of values is recognized as falsy:
* - Boolean false.
* - An integer or float equal to zero.
* - A text string with the content `'0'` or `''` (single or double-quoted, heredoc, or nowdoc).
* - An empty array.
*
* @param int $start The position to start looking from.
* @param int $end The position to stop looking (inclusive).
*
Expand All @@ -202,67 +182,76 @@ public function process_parameters( $stackPtr, $group_name, $matched_content, $p
* couldn't be reliably determined.
*/
protected function is_falsy( $start, $end ) {

// Find anything excluding the false tokens.
$has_non_false = $this->phpcsFile->findNext( $this->false_tokens, $start, ( $end + 1 ), true );
// If no non-false tokens are found, we are good.
if ( false === $has_non_false ) {
return true;
}

$code_string = '';
for ( $i = $start; $i <= $end; $i++ ) {
if ( isset( $this->safe_tokens[ $this->tokens[ $i ]['code'] ] ) === false ) {
// Function call/variable or other token which makes it neigh impossible
// to determine whether the actual value would evaluate to false.
$target_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $start, ( $end + 1 ), true );

// An array only evaluates to false when it is empty.
if ( isset( Collections::arrayOpenTokensBC()[ $this->tokens[ $target_ptr ]['code'] ] ) ) {
$open_close = Arrays::getOpenClose( $this->phpcsFile, $target_ptr );
if ( false === $open_close ) {
// Short list assignment, not an array.
return false;
}

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

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

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

if ( \T_DOUBLE_CAST === $this->tokens[ $i ]['code'] ) {
$code_string .= '(float)';
continue;
}
return ( false === $first_non_empty_in_array );
}

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

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

$code_string .= $this->tokens[ $i ]['content'];
$content = TextStrings::getCompleteTextString( $this->phpcsFile, $target_ptr );

return '0' === $content || '' === $content;
}

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

// Evaluate the argument to figure out the outcome is false or not.
// phpcs:ignore Squiz.PHP.Eval -- No harm here.
return ( false === eval( "return (bool) $code_string;" ) );
// Check if it is an int or float equal to zero.
if ( \T_LNUMBER === $this->tokens[ $target_ptr ]['code'] || \T_DNUMBER === $this->tokens[ $target_ptr ]['code'] ) {
$number_info = Numbers::getCompleteNumber( $this->phpcsFile, $target_ptr );

return 0.0 === (float) $number_info['decimal'];
}

return false;
}
}
Loading
Loading