You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Spec: #1 (upstream: Graphify-Labs#1682). Found by the red-team session against 2e68ec6. Repro scripts: /tmp/rt6/ (harness.py, wave2.py, wave3.py).
Problem
_php_qualified_corroborates (extract.py:3058) promotes an inline-new member call from INFERRED 0.8 to EXTRACTED 1.0 when the written FQN corroborates the resolved node. The only evidence it consults is the tail of the file path, casefolded:
Its docstring justifies this with "PHP nodes carry no namespace" — but the namespace declaration is present in the source file and is simply not read. PSR-4 is a convention, not an invariant, so path and namespace can diverge. Two exploits follow.
Exploit A — declared namespace differs from the PSR-4 path (worse)
The class the source names — App\Services\Client — does not exist anywhere in the corpus. The edge points at App\Vendor\Client and is stamped at maximum confidence. This is a wrong target at 1.0, not merely confidence inflation.
Realistic triggers: PSR-0 leftovers, custom/classmap autoloaders, files moved without their namespace updated, and generated code.
Repro: /tmp/rt6/wave2.py::B5_ns_path_mismatch.
Exploit B — root-namespace FQN matches as a path suffix
// app/Services/Client.phpnamespaceApp\Services;
class Client { publicfunctionpost(): void {} }
// caller
(new \Services\Client())->post(); // root namespace \Services, NOT App\Services
Actual: EXTRACTED 1.0. The comparison only requires the written segments to equal the tail of the path, so a proper suffix of the real FQN corroborates. \Services\Client and App\Services\Client are different classes; the former does not exist. A missing use plus a leading \ is a common real bug, and it gets rewarded with maximum confidence.
Repro: /tmp/rt6/wave2.py::B6_root_ns_tail.
Controls (behaviour that is correct today)
From /tmp/rt6/wave3.py:
\App\Services\Client against namespace App\Services; at app/Services/Client.php → EXTRACTED 1.0 (F3_control_ns_matches) — the intended promotion
(new Client()) bare name → INFERRED 0.8 (F3_control_bare_name) — no namespace written, no evidence
\Acme\Sdk\Client (path does not match) → INFERRED 0.8 (F3_wrong_ns_no_path_match) — correct downgrade
So the mechanism is purely path-based, exactly as documented; the exploits come from path/namespace divergence and from suffix rather than whole-name matching.
What to build
Read the namespace declaration during PHP extraction and use it as the corroborating fact instead of (or in addition to) the path:
Carry the declaring namespace on the PHP class node (or on the per-file result, keyed to the class) at extraction time — namespace_definition is already parsed for imports.
In _php_qualified_corroborates, compare the written FQN against declared_namespace + '\' + class_name, requiring a whole-name match rather than a path-tail suffix match, so \Services\Client no longer corroborates App\Services\Client.
If threading the namespace is deferred, an interim mitigation is to require the written segment count to match the full path-derived prefix (closing exploit B) and to refuse promotion when the file declares a namespace that disagrees with its path (closing exploit A).
Acceptance criteria
Declared namespace differing from the PSR-4 path does not promote to EXTRACTED (exploit A)
A root-namespace or otherwise-truncated FQN does not corroborate a longer real FQN (exploit B)
Matching FQN still promotes to EXTRACTED 1.0; bare name still INFERRED; mismatched-but-plausible qualifier still downgrades rather than refusing
Parent
Spec: #1 (upstream: Graphify-Labs#1682). Found by the red-team session against
2e68ec6. Repro scripts:/tmp/rt6/(harness.py,wave2.py,wave3.py).Problem
_php_qualified_corroborates(extract.py:3058) promotes an inline-newmember call fromINFERRED0.8 toEXTRACTED1.0 when the written FQN corroborates the resolved node. The only evidence it consults is the tail of the file path, casefolded:Its docstring justifies this with "PHP nodes carry no namespace" — but the
namespacedeclaration is present in the source file and is simply not read. PSR-4 is a convention, not an invariant, so path and namespace can diverge. Two exploits follow.Exploit A — declared namespace differs from the PSR-4 path (worse)
Actual:
The class the source names —
App\Services\Client— does not exist anywhere in the corpus. The edge points atApp\Vendor\Clientand is stamped at maximum confidence. This is a wrong target at 1.0, not merely confidence inflation.Realistic triggers: PSR-0 leftovers, custom/classmap autoloaders, files moved without their namespace updated, and generated code.
Repro:
/tmp/rt6/wave2.py::B5_ns_path_mismatch.Exploit B — root-namespace FQN matches as a path suffix
Actual:
EXTRACTED 1.0. The comparison only requires the written segments to equal the tail of the path, so a proper suffix of the real FQN corroborates.\Services\ClientandApp\Services\Clientare different classes; the former does not exist. A missinguseplus a leading\is a common real bug, and it gets rewarded with maximum confidence.Repro:
/tmp/rt6/wave2.py::B6_root_ns_tail.Controls (behaviour that is correct today)
From
/tmp/rt6/wave3.py:\App\Services\Clientagainstnamespace App\Services;atapp/Services/Client.php→EXTRACTED 1.0(F3_control_ns_matches) — the intended promotion(new Client())bare name →INFERRED 0.8(F3_control_bare_name) — no namespace written, no evidence\Acme\Sdk\Client(path does not match) →INFERRED 0.8(F3_wrong_ns_no_path_match) — correct downgradeSo the mechanism is purely path-based, exactly as documented; the exploits come from path/namespace divergence and from suffix rather than whole-name matching.
What to build
Read the
namespacedeclaration during PHP extraction and use it as the corroborating fact instead of (or in addition to) the path:namespace_definitionis already parsed for imports._php_qualified_corroborates, compare the written FQN againstdeclared_namespace + '\' + class_name, requiring a whole-name match rather than a path-tail suffix match, so\Services\Clientno longer corroboratesApp\Services\Client.INFERRED) and keep non-matching qualifiers as a downgrade rather than a refusal, per PHP: (new Service())->method() resolves as EXTRACTED with FQN corroboration #3's shipped policy.If threading the namespace is deferred, an interim mitigation is to require the written segment count to match the full path-derived prefix (closing exploit B) and to refuse promotion when the file declares a namespace that disagrees with its path (closing exploit A).
Acceptance criteria