mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +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),
|
"seconds": round(time.monotonic() - started, 1),
|
||||||
"questions": len(outcomes),
|
"questions": len(outcomes),
|
||||||
"completed": sum(1 for o in outcomes if o.status == "completed"),
|
"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}),
|
"degradations": sorted({str(d.get("reason")) for d in result.degradations}),
|
||||||
"findings": findings,
|
"findings": findings,
|
||||||
}
|
}
|
||||||
args.out.write_text(json.dumps(record, indent=2) + "\n")
|
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:
|
if not outcomes:
|
||||||
raise SystemExit("no question was asked: the dump measured nothing")
|
raise SystemExit("no question was asked: the dump measured nothing")
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -306,10 +306,12 @@
|
|||||||
val methods = methodsIn(fileName)
|
val methods = methodsIn(fileName)
|
||||||
val framework: List[io.shiftleft.codepropertygraph.generated.nodes.CfgNode] =
|
val framework: List[io.shiftleft.codepropertygraph.generated.nodes.CfgNode] =
|
||||||
methods.flatMap(_.ast.isCall.filter(c => sourcePatterns.exists(p => c.code.contains(p))).l)
|
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] =
|
val params: List[io.shiftleft.codepropertygraph.generated.nodes.CfgNode] =
|
||||||
if (parameterSources == "true")
|
if (parameterSources == "true") methods.flatMap(_.parameter.l.filter(_.name != "this")) else Nil
|
||||||
methods.flatMap(m => m.parameter.l.filter(p => p.name != "this" && fedByCaller(m, p)))
|
|
||||||
else Nil
|
|
||||||
framework ++ params
|
framework ++ params
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -331,7 +333,13 @@
|
|||||||
// queries that way: the fixed side of its pair went from 1 finding to 13 without this,
|
// 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.
|
// which is the pair no longer separating at all.
|
||||||
val flows = args(1).start.reachableByFlows(seeds.iterator).l
|
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
|
case _ => false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user