mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
perf(taint): ask the caller check only of a parameter that starts a flow into the field
Asked of every parameter up front, 6ebbc89's caller check ran a repository- wide flow query per parameter of every method in a file and cut PMPro's completed injection questions from 272 to 67 in the same budget -- so its 48 -> 26 findings could not be read as precision. The flow into the field is asked once as before; only a parameter at the head of an unsanitized flow is checked against its callers. finding_dump now records completed regions so runs compare only where both answered. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6ebbc89d64
commit
e0c260ca5f
@@ -81,11 +81,17 @@ def main() -> int:
|
||||
"seconds": round(time.monotonic() - started, 1),
|
||||
"questions": len(outcomes),
|
||||
"completed": sum(1 for o in outcomes if o.status == "completed"),
|
||||
# Which questions answered, so two runs can be compared only where BOTH answered: a finding that
|
||||
# vanishes because its question went unasked is lost coverage, not gained precision.
|
||||
"completed_regions": sorted({f"{o.identity.path}:{o.identity.function or ''}" for o in outcomes if o.status == "completed"}),
|
||||
"degradations": sorted({str(d.get("reason")) for d in result.degradations}),
|
||||
"findings": findings,
|
||||
}
|
||||
args.out.write_text(json.dumps(record, indent=2) + "\n")
|
||||
print(json.dumps({k: v for k, v in record.items() if k != "findings"} | {"findings": len(findings)}), flush=True)
|
||||
print(
|
||||
json.dumps({k: v for k, v in record.items() if k not in ("findings", "completed_regions")} | {"findings": len(findings)}),
|
||||
flush=True,
|
||||
)
|
||||
if not outcomes:
|
||||
raise SystemExit("no question was asked: the dump measured nothing")
|
||||
return 0
|
||||
|
||||
@@ -306,10 +306,12 @@
|
||||
val methods = methodsIn(fileName)
|
||||
val framework: List[io.shiftleft.codepropertygraph.generated.nodes.CfgNode] =
|
||||
methods.flatMap(_.ast.isCall.filter(c => sourcePatterns.exists(p => c.code.contains(p))).l)
|
||||
// Every parameter is a CANDIDATE start; whether its callers feed it is asked only of a parameter that
|
||||
// actually begins an unsanitized flow into the field being read (`fieldIsTainted`). Asked of every
|
||||
// parameter up front, the caller check ran a repository-wide flow query per parameter of every
|
||||
// method in the file and cut PMPro's completed questions from 272 to 67 in the same budget.
|
||||
val params: List[io.shiftleft.codepropertygraph.generated.nodes.CfgNode] =
|
||||
if (parameterSources == "true")
|
||||
methods.flatMap(m => m.parameter.l.filter(p => p.name != "this" && fedByCaller(m, p)))
|
||||
else Nil
|
||||
if (parameterSources == "true") methods.flatMap(_.parameter.l.filter(_.name != "this")) else Nil
|
||||
framework ++ params
|
||||
}
|
||||
)
|
||||
@@ -331,7 +333,13 @@
|
||||
// queries that way: the fixed side of its pair went from 1 finding to 13 without this,
|
||||
// which is the pair no longer separating at all.
|
||||
val flows = args(1).start.reachableByFlows(seeds.iterator).l
|
||||
flows.exists(f => !f.elements.map(_.code).l.exists(code => mentionsToken(code, sanitizerNames)))
|
||||
flows.exists { f =>
|
||||
!f.elements.map(_.code).l.exists(code => mentionsToken(code, sanitizerNames)) && (f.elements.headOption match {
|
||||
case Some(param: io.shiftleft.codepropertygraph.generated.nodes.MethodParameterIn) =>
|
||||
fedByCaller(param.method, param)
|
||||
case _ => true
|
||||
})
|
||||
}
|
||||
case _ => false
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user