feat(ruleset): PHP quick-mode rules from the engine's vocabulary, measured on the development corpus

Quick mode had no PHP rules, so a PHP quick scan read the files and reported a plausible zero.
ruleset/php/rules.toml adds 13 line/statement rules built on semantic/php.toml's sinks, sources and
sanitizers (sql/wpdb, command, code_eval, unserialize, echo, file_inclusion, filesystem,
outbound_request, redirect). 9 are enabled and 4 are shadow (sql-call-composition, code-eval,
unserialize, xss-echo-input). A rule is shadow when it fired on the corpus but never at a known site.
precision_estimate is the measured lower bound.

Measured in-sample on the development corpus only (pmpro introduce/repair/benign, population-v1 PHP
cases; population-v2 was not read). Known site hit on the vulnerable pin in 4 of 5 cases (LearnPress
missed). The hit disappears on the fixed pin for pmpro and YesWiki. Numbers, per-pin files/bytes read
and the engine comparison are in benchmarks/measurements/2026-09-30-php-quick-rules/.

- fixtures php-vulnerable / php-benign plus manifests: ousast benchmark covers PHP
  (9/14 reported, 13/14 including shadow; the benign twin records 2 known false alerts)
- PHP `#` comments are comments; `#[...]` attributes are not
- the alerts task now reports PHP as quick coverage; alerts-engine keeps running the engine for PHP
  next to the quick rules (ENGINE_ALONGSIDE_QUICK)
- other languages' benchmark findings and metrics are unchanged

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-30 15:05:47 +02:00
co-authored by Claude Opus 5.5
parent 45d02ecf20
commit 7927d6e539
16 changed files with 1757 additions and 17 deletions
+4 -2
View File
@@ -64,6 +64,8 @@ def test_repository_benchmark_manifests_load_and_point_to_fixtures() -> None:
"javascript-node-web-smoke.toml",
"javascript-vulnerable.toml",
"mutation-python.toml",
"php-benign.toml",
"php-vulnerable.toml",
"python-vulnerable.toml",
"python-web-smoke.toml",
"split-sink-c.toml",
@@ -73,8 +75,8 @@ def test_repository_benchmark_manifests_load_and_point_to_fixtures() -> None:
}
for manifest_path in manifest_paths:
manifest = load_benchmark_manifest(manifest_path)
assert manifest.expected
assert manifest.language in {"c_cpp", "java_web", "javascript_node_web", "python_web"}
assert manifest.expected or manifest.name == "php-benign", "only the repaired twin expects nothing"
assert manifest.language in {"c_cpp", "java_web", "javascript_node_web", "php_web", "python_web"}
assert (manifest_path.parent / manifest.source.path).resolve().exists()