ParseStrKeyNormalizationMismatch
warning native on by default quick-fixGroup: Probable bugs
parse_str normalizes dots and spaces in parameter names to underscores. Read the normalized key from the parsed result.
Example
<?php
parse_str("user.name=Ada",$q);echo $q["user.name"];<?php
parse_str("user.name=Ada",$q);echo $q['user_name'];Reported:
- line 2: Read the normalized query parameter key.
Detection
- Resolved parse_str literal query and explicit local output array contains a key with dot/space normalized to underscore. A subsequent unguarded result access uses original key, which is absent after parsing. Ignore bracket syntax and duplicate normalized keys in initial proof.
- Require an actual value read of the absent dimension. Exclude assignment targets, unset, by-reference binding or arguments, and calls whose argument contract does not prove a read. Any mutation of the result array in the same containing statement discards the access proof. Query keys with leading whitespace after URL decoding are unsupported and excluded.
- All argument uses are excluded because they can involve reference passing. Also exclude increment/decrement, unset and write contexts. Any same-statement mutation or call receiving the result array discards the actual-read proof.
- Resolve builtin functions, classes, constants and methods; user-defined lookalikes are excluded. Follow only bounded local proof in one lexical scope.
- The access and the parse_str statement must belong to the same variable scope. An access inside a nested closure, arrow function or property hook is not followed, even when an arrow function would capture the parsed array: the name may equally be a parameter or local of the nested scope.
- By-reference binding covers the value side of a reference assignment (
$slot = &$q["a.b"]), a by-reference array item value ([&$q["a.b"]]) and the source of a by-referenceforeach. Each of these creates the dimension rather than reading it, so none is reported. The same holds when a deeper dimension of the access is what gets bound ($slot = &$q["a.b"]["c"]). An access that only supplies the key of another by-reference access ($slot = &$cache[$q["a.b"]]) is an ordinary read and is reported. - Emit one finding per violating operation, not per supporting evidence statement. When the proof is inconclusive, nothing is reported.
Exceptions (no report)
- Do not report unresolved or shadowed builtin names, unknown values, or incomplete receiver/type provenance. Unknown calls, aliases escaping, and intervening writes invalidate local proof. Similar code outside the stated detection contract is not a finding.
- Both standard suppression forms suppress this native ID.
Fix
F1. Replace the directly written literal array dimension with a single-quoted normalized query key, escaping single quotes and backslashes. The literal query must prove that normalized key exists uniquely: no duplicate normalized keys, bracket notation, or NUL bytes. Preserve all surrounding access bytes; unavailable for variable/computed dimensions or ambiguous input.
PHP versions
Available throughout supported PHP 5.3–8.5 where the referenced builtin/extension is available. Respect syntax/version-specific receiver contracts; unavailable APIs are not this rule’s concern.
See the PHP query parsing contract for the relevant builtin behavior.
Configure
In custos.json:
{
"rules": {
"ParseStrKeyNormalizationMismatch": {
"enabled": false
}
}
}Suppress
Before the statement or declaration (or the first statement of the file):
// @custos-ignore ParseStrKeyNormalizationMismatch