Skip to content

gax-php: RelativeResourceTemplate::match() false-positives (wrong bindings) when path is too short for a template with ** + wildcard variables #9425

Description

@Ziffrit

Summary

RelativeResourceTemplate::match() returns a false-positive match with wrong, internally-contradictory bindings when a template contains a ** path wildcard followed by wildcard variable segments, and the input path has fewer segments than required. Such paths should be rejected (the library's own test suite asserts this), but instead a match is returned and PHP emits "Undefined array key -N" warnings.

Steps to reproduce

<?php
require 'vendor/autoload.php';
use Google\ApiCore\PathTemplate;

// Template: a, **, {y=*}, {x=*}, f  (5 segments)
$tpl = new PathTemplate('a/**/{y=*}/{x=*}/f');

// Path 'a/e/f' has only 3 slash-pieces. Minimum valid path needs 5
// (the '**' must consume at least one segment). This must be REJECTED.
var_dump($tpl->match('a/e/f'));

Actual output

array(3) {
  ["$0"] => string(1) "e"
  ["y"]  => string(1) "a"
  ["x"]  => string(1) "e"
}

The path a/e/f does not have enough segments to satisfy the template (minimum is 5), yet it "matches". Worse, the bindings are impossible: path-piece a is consumed both as the literal a and as variable y, and piece e is bound to both $0 (the **) and x.

PHP also logs these warnings during the match:

Warning: Undefined array key -1 in .../RelativeResourceTemplate.php on line ~196

Expected behavior

match() should throw ValidationException and matches() should return false, exactly as the library's own test suite already requires for the same class of input:

// tests/Unit/ResourceTemplate/RelativeResourceTemplateTest.php
['foo/**/bar', 'foo/bar', 'Missing middle wildcard'],   // must NOT match

Root cause

In src/ResourceTemplate/RelativeResourceTemplate.php (~line 178):

$doubleWildcardPieceCount =
    count($slashPathPieces) - $flattenedKeySegmentTuplesCount + 1;

When the path has fewer slash-pieces than the template's flattened segments, $doubleWildcardPieceCount becomes negative. This is then passed as the length to array_slice():

$doubleWildcardPathPieces = array_slice(
    $slashPathPieces,
    $pathPiecesIndex,
    $doubleWildcardPieceCount
);

A negative length truncates the slice from the end, so ** consumes a wrong/empty piece set, $pathPiecesIndex is advanced by a negative amount, and subsequent variable segments re-read already-consumed literal prefix pieces. The result is a validated but semantically impossible match.

The trigger requires:

  • a ** segment not in final position, followed by
  • at least two variable/wildcard segments, and
  • an input path exactly 2 pieces shorter than the minimum required length.

Impact

Low. ** mid-template is not used by real generated Google API clients today, so practical reachability is minimal. However this is a correctness bug in a public parsing API (PathTemplate::match/matches) — callers doing authorization or routing decisions based on parsed bindings could be affected, and the emitted "Undefined array key" warnings indicate the code path is genuinely broken, not merely permissive.

Environment

  • google/gax v1.43.1 – v1.47.0 (also HEAD of main, release 0.338.0)
  • PHP 8.4.23
  • All releases are affected (code unchanged).

Suggested fix

Clamp the piece count / add a bounds check before slicing, and bail out (no match) when the path is shorter than the minimum required for the template:

if ($doubleWildcardPieceCount < 1) {
    return $this->matchException($path);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions