mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-17 03:59:28 +00:00
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:
co-authored by
Claude Opus 5.5
parent
45d02ecf20
commit
7927d6e539
@@ -99,7 +99,7 @@ def test_alerts_attribute_functions_and_mark_the_fix_on_both_pins(tmp_path: Path
|
||||
|
||||
def test_quick_coverage_is_derived_from_the_ruleset_directory(tmp_path: Path) -> None:
|
||||
shipped = alerts.quick_languages()
|
||||
assert {"python", "javascript", "typescript", "java", "c", "groovy"} <= shipped and "php" not in shipped
|
||||
assert {"python", "javascript", "typescript", "java", "c", "groovy", "php"} <= shipped, "PHP has quick rules"
|
||||
assert "php" in alerts.engine_languages(), "the engine's semantic models cover PHP"
|
||||
rules = tmp_path / "ruleset" / "php"
|
||||
rules.mkdir(parents=True)
|
||||
@@ -113,7 +113,7 @@ def test_quick_coverage_is_derived_from_the_ruleset_directory(tmp_path: Path) ->
|
||||
def test_alerts_on_an_uncovered_language_record_coverage_none_not_a_clean_zero(tmp_path: Path) -> None:
|
||||
for side in ("v", "f"):
|
||||
(tmp_path / side).mkdir()
|
||||
(tmp_path / side / "index.php").write_text('<?php\n$id = $_GET["id"];\nmysqli_query($c, "SELECT " . $id);\n')
|
||||
(tmp_path / side / "app.rb").write_text('id = params["id"]\nDB.execute("SELECT " + id)\n')
|
||||
(tmp_path / side / "app.js").write_text("module.exports = 1;\n")
|
||||
out = tmp_path / "out"
|
||||
env = {
|
||||
@@ -125,9 +125,26 @@ def test_alerts_on_an_uncovered_language_record_coverage_none_not_a_clean_zero(t
|
||||
assert (out / "alerts.jsonl").read_text() == "" and summary["alerts"] == {"vulnerable": 0, "fixed": 0}
|
||||
assert summary["coverage"] == {
|
||||
"javascript": {"coverage": "quick", "files": {"fixed": 1, "vulnerable": 1}},
|
||||
"php": {"coverage": "none", "files": {"fixed": 1, "vulnerable": 1}},
|
||||
"ruby": {"coverage": "none", "files": {"fixed": 1, "vulnerable": 1}},
|
||||
}
|
||||
assert summary["uncovered"] == ["php"] and summary["pins"] == {"vulnerable": VULNERABLE, "fixed": FIXED}
|
||||
assert summary["uncovered"] == ["ruby"] and summary["pins"] == {"vulnerable": VULNERABLE, "fixed": FIXED}
|
||||
|
||||
|
||||
def test_php_is_quick_covered_and_alerts_on_a_php_sink(tmp_path: Path) -> None:
|
||||
for side in ("v", "f"):
|
||||
(tmp_path / side).mkdir()
|
||||
(tmp_path / side / "index.php").write_text('<?php\n$id = $_GET["id"];\nmysqli_query($c, "SELECT " . $id);\n')
|
||||
out = tmp_path / "out"
|
||||
env = {
|
||||
"OUSAST_OUTPUT_DIR": str(out), "OUSAST_WORKSPACE_DIR": str(tmp_path / "v"), "OUSAST_FIXED_DIR": str(tmp_path / "f"),
|
||||
"OUSAST_VULNERABLE_PIN": VULNERABLE, "OUSAST_FIXED_PIN": FIXED,
|
||||
} # fmt: skip
|
||||
assert alerts.main(env) == 0
|
||||
summary = json.loads((out / "summary.json").read_text())
|
||||
assert summary["coverage"] == {"php": {"coverage": "quick", "files": {"fixed": 1, "vulnerable": 1}}}
|
||||
assert summary["uncovered"] == [] and summary["alerts"] == {"vulnerable": 1, "fixed": 1}
|
||||
rows = [json.loads(line) for line in (out / "alerts.jsonl").read_text().splitlines()]
|
||||
assert {(r["rule_id"], r["line"]) for r in rows} == {("php-sql-call-composition", 3)}
|
||||
|
||||
|
||||
def test_fix_ranges_new_side_are_the_fixed_pins_lines(tmp_path: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user