Skip to content

InputNotSanitized / MissingUnslash: (int) ( $_GET['x'] ?? 0 ) not recognised as cast-sanitised #2723

Description

@dd32

Summary

ContextHelper::is_safe_casted() only inspects the token immediately preceding the superglobal. The common idiom

$id = (int) ( $_GET['id'] ?? 0 );

puts a ( between the cast and the superglobal, so the helper returns false and WordPress.Security.ValidatedSanitizedInput fires two errors — InputNotSanitized + MissingUnslash — even though the value is:

  • validated (the ?? default provides a value when the key is unset),
  • unslashed (slashes don't survive an (int) / (bool) / (float) cast), and
  • sanitised (the only output is a PHP scalar of the cast type).

Rewriting to (int) absint( wp_unslash( $_GET['id'] ?? 0 ) ) adds no safety.

Reproducer

<?php
$a = (int)   $_GET['foo'];              // OK today — direct cast adjacency.
$b = (int)   ( $_GET['foo']  ?? 0 );    // Reported as 2 errors. Should be OK.
$c = (bool)  ( $_POST['flag'] ?? false ); // Reported as 2 errors. Should be OK.
$d = (float) ( $_POST['rate'] ?? 0.0 );   // Reported as 2 errors. Should be OK.

Run with --sniffs=WordPress.Security.ValidatedSanitizedInput. Lines 3–5 each produce:

InputNotSanitized    — Detected usage of a non-sanitized input variable: $_GET[…]
MissingUnslash       — $_GET[…] not unslashed before sanitization. Use wp_unslash() or similar

Relation to #2210

Related but a different sanitiser mechanism. #2210 is about implicit numeric coercion — e.g. microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT'] — where the arithmetic operator itself coerces the operand to a number. This ticket is about explicit type casts separated from the superglobal by grouping parentheses. They need different detection paths; I hit this one when submitting a plugin to .org and originally commented on #2210, then realised it's a distinct case and split it out.

Proposed fix

Walk outward through grouping parentheses when searching for a preceding cast. Stop at the first non-paren, non-cast token. This handles the three canonical cast/?? shapes above plus nested grouping ((int) ( ( $_POST['n'] ?? 0 ) )), and leaves function-call sites (whose preceding-token-before-paren is a T_STRING, not a cast) unaffected.

Cases deliberately out of scope for the first patch — ternary between the cast and the superglobal ((int) ( isset($_POST['n']) ? $_POST['n'] : 0 )), arithmetic operators in between ((int) ( strlen($_POST['x']) + 1 )) — need a walk via the superglobal's nested_parenthesis stack and are a natural follow-up.

PR

I have a patch + tests up at #2722.


Issue drafted with AI assistance (Claude).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions