mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
fix(scan): a language boundary is a declared limit, not a graph-integrity failure
Membership of GRAPH_INTEGRITY_GAPS should turn on whether a gap can make a reported answer WRONG, not on whether it cost coverage. An unparsed file may hold the sanitizer that would have cleared a flow this scan reports, so it belongs there. A flow this tool cannot follow from one language into another can hide a finding and never invent one, because a PHP sink is sanitised in PHP. cross_partition_semantics_unresolved therefore moves to DECLARED_EXCLUSION_GAPS. It touches exactly two places, both of which already honour the distinction: the outcome demotion in scan.py, and _blocking_degradations in the push policy, whose docstring already says a declared exclusion is not one of them. The reason stays published in the scope unresolved boundaries either way, so the limit is still reported; the class decides only whether every answer in the repository is demoted. This corrects the task note written earlier today, which said not to reclassify on the grounds that it would claim cross-language flows are out of scope rather than unresolved. That was a misreading of the class. PMPro still completes none of its questions, and the measurement now names why: the JavaScript partition expected 36 files and holds 32, so partition_file_census_incomplete fires and is still an integrity gap. The four absent files are js/blocks/blocks.build.js, js/jsapi.js, js/select2.min.js and webpack.config.js -- a build output, a source file, a minified third-party library and a build config. A bundled select2 costs 4,425 PHP regions their admissibility. Same over-broad blast radius, third instance of one principle in this thread, and the fix direction is recorded. The census harness now records each degradation whole rather than only its reason, because a partition census that reports files missing carries WHICH files and without them the gap is a label rather than a lead. 1,417 tests pass, eighteen skipped. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5
parent
73e9c1fed6
commit
c93669c34f
@@ -570,6 +570,17 @@ def _scan_repository_impl(
|
||||
|
||||
# Reading the graph failed or produced something incomplete. An answer drawn from it cannot be
|
||||
# trusted, so a completed outcome is demoted.
|
||||
#
|
||||
# The test for membership is whether the gap could make a reported answer WRONG, not whether it cost
|
||||
# coverage. An unparsed file may hold the sanitizer that would have cleared a flow this scan reports,
|
||||
# so `files_unparsed` belongs here and demoting is the conservative reading. A missing file the
|
||||
# partition census expected is the same shape. That is why `cross_partition_semantics_unresolved` is
|
||||
# NOT here: a flow this tool cannot follow from one language into another can hide a finding, never
|
||||
# invent one, because a PHP sink is sanitised in PHP. Demoting on it protected nothing and cost
|
||||
# everything -- measured 2026-09-21, where a WordPress plugin with 36 regions of admin JavaScript
|
||||
# completed none of 520 questions while establishing 43 findings, and admission requires a completed
|
||||
# outcome. Every multi-language repository was unadmittable by construction, which is every plugin
|
||||
# this product was built for.
|
||||
GRAPH_INTEGRITY_GAPS = frozenset(
|
||||
{
|
||||
"files_unparsed",
|
||||
@@ -577,7 +588,6 @@ GRAPH_INTEGRITY_GAPS = frozenset(
|
||||
"partition_file_census_incomplete",
|
||||
"partition_file_census_unavailable",
|
||||
"cpg_sharded",
|
||||
"cross_partition_semantics_unresolved",
|
||||
"symlink_context_unresolved",
|
||||
"frontend_unsupported",
|
||||
"source_unreadable",
|
||||
@@ -588,7 +598,14 @@ GRAPH_INTEGRITY_GAPS = frozenset(
|
||||
# Declared, intentional exclusions. The exclusion stays physical and stays reported; it is not
|
||||
# evidence that any particular first-party answer is wrong. Treating it as one demoted every
|
||||
# answer on every real repository, because every real repository excludes its dependencies.
|
||||
DECLARED_EXCLUSION_GAPS = frozenset({"vendor_semantics_unresolved"})
|
||||
# Declared, intentional exclusions. The exclusion stays physical and stays reported -- both classes are
|
||||
# added to the scope's unresolved boundaries, so a declared gap is still published as unresolved; the class
|
||||
# decides only whether it demotes every answer in the repository.
|
||||
#
|
||||
# `cross_partition_semantics_unresolved` is one of these: analysing each language separately, and not
|
||||
# modelling a flow that leaves one for another, is a deliberate scope choice of exactly the kind vendor
|
||||
# exclusion is. It was in the integrity set until 2026-09-21 and the reason for moving it is above.
|
||||
DECLARED_EXCLUSION_GAPS = frozenset({"vendor_semantics_unresolved", "cross_partition_semantics_unresolved"})
|
||||
|
||||
|
||||
def _identity(unit: str, region: ScanRegion, family: str) -> QuestionIdentity:
|
||||
|
||||
Reference in New Issue
Block a user