mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
fix(scan,taint): TypeScript's limit is owned by TypeScript; a source pattern is a token
Two defects found by the cause-B rescan of the independent population (exploratory, a retune after protocol-v1): - `typescript_property_support_unvalidated` still reached the whole repository. YesWiki is a PHP application that ships a few `.ts` files, and all 1,128 of its PHP questions were unresolved. The limit is now owned by the TypeScript partition. A TypeScript project (Mockoon, Signal K) is unchanged. - `isSourceCall`, and the method-level "reads a source" heuristics, matched source patterns as substrings. Flask's `request.get` therefore claimed Django's `request.get_host()`. wger's fix passes that host to `url_has_allowed_host_and_scheme`, and both fixed functions were reported as open redirects from it. Patterns are now matched as bounded tokens, like sinks and sanitizers. wger's vulnerable-pin detections rest on `request.GET`, which still matches. 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
65dcb46b5e
commit
3358631448
@@ -174,7 +174,10 @@
|
||||
if (accessOperators.contains(c.name)) c.code
|
||||
else if (c.name.startsWith("<operator")) ""
|
||||
else c.code.takeWhile(_ != '(')
|
||||
named.nonEmpty && sourcePatterns.exists(p => named.contains(p))
|
||||
// Bounded, like every other token here: a substring test let Flask's `request.get` claim Django's
|
||||
// `request.get_host()` -- the host handed to the redirect VALIDATOR in wger's fix -- and both fixed
|
||||
// functions were reported as open redirects.
|
||||
named.nonEmpty && sourcePatterns.exists(p => mentionsToken(named, List(p)))
|
||||
}
|
||||
|
||||
def frameworkSources = cpg.call.filter(isSourceCall)
|
||||
@@ -1003,7 +1006,7 @@
|
||||
def sourceMethods(fileName: String): Set[String] =
|
||||
sourceMethodsMemo.getOrElseUpdate(
|
||||
fileName,
|
||||
methodsIn(fileName).filter(m => m.ast.isCall.exists(c => sourcePatterns.exists(p => c.code.contains(p)))).map(_.name).toSet
|
||||
methodsIn(fileName).filter(m => m.ast.isCall.exists(c => mentionsToken(c.code, sourcePatterns))).map(_.name).toSet
|
||||
)
|
||||
|
||||
def fedFields(fileName: String): Map[String, String] =
|
||||
@@ -1023,7 +1026,7 @@
|
||||
case id: io.shiftleft.codepropertygraph.generated.nodes.Identifier => params.contains(id.name)
|
||||
case _ => false
|
||||
}
|
||||
val fromSource = sourcePatterns.exists(p => rhs.code.contains(p)) || (readers.nonEmpty && rhs.ast.isCall.name.l.exists(readers.contains))
|
||||
val fromSource = mentionsToken(rhs.code, sourcePatterns) || (readers.nonEmpty && rhs.ast.isCall.name.l.exists(readers.contains))
|
||||
if (fromSource) Some(target.code.trim -> "source")
|
||||
else if (fromParameter) Some(target.code.trim -> "parameter")
|
||||
else None
|
||||
@@ -1041,7 +1044,7 @@
|
||||
callbackFedMemo.getOrElseUpdate(
|
||||
callback,
|
||||
strongest(cpg.method.nameExact(callback).l.map { m =>
|
||||
if (m.ast.isCall.exists(c => sourcePatterns.exists(p => c.code.contains(p)))) "source"
|
||||
if (m.ast.isCall.exists(c => mentionsToken(c.code, sourcePatterns))) "source"
|
||||
else {
|
||||
val fed = fedFields(m.filename)
|
||||
if (fed.isEmpty) "" else strongest(m.ast.isCall.nameExact(FIELD_ACCESS).code.l.map(code => prefixKind(code.trim, fed)))
|
||||
@@ -1073,7 +1076,7 @@
|
||||
// no-dataflow query take minutes. `cpg.method.filter(...)` over every method per request was the
|
||||
// other half of that, replaced by a direct lookup of the reachable names.
|
||||
def methodHasSource(m: io.shiftleft.codepropertygraph.generated.nodes.Method): Boolean =
|
||||
methodSourceMemo.getOrElseUpdate(m.fullName, m.ast.isCall.exists(c => sourcePatterns.exists(p => c.code.contains(p))))
|
||||
methodSourceMemo.getOrElseUpdate(m.fullName, m.ast.isCall.exists(c => mentionsToken(c.code, sourcePatterns)))
|
||||
val sinkList = sinkCalls.l
|
||||
val sourceLocal = labeledMethods.exists(methodHasSource)
|
||||
val sourceNear = depth > 0 && cpg.method.fullNameExact(reachableMethods.toSeq: _*).exists(methodHasSource)
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
else
|
||||
sourceMemo.getOrElseUpdate(
|
||||
c.method.fullName,
|
||||
c.method.ast.isCall.exists(x => sourcePatterns.exists(p => x.code.contains(p)))
|
||||
c.method.ast.isCall.exists(x => sourcePatterns.exists(p => boundedPattern(p).matcher(x.code).find()))
|
||||
)
|
||||
|
||||
def isModelled(c: io.shiftleft.codepropertygraph.generated.nodes.Call): Boolean = {
|
||||
|
||||
@@ -279,6 +279,9 @@ def build_partitions(
|
||||
if language == "typescript":
|
||||
record_limits += ("typescript_property_support_unvalidated",)
|
||||
boundaries.add("typescript_property_support_unvalidated")
|
||||
# About TypeScript answers. YesWiki is a PHP application that ships a few `.ts` files, and this
|
||||
# boundary made all 1,128 of its PHP questions unresolved.
|
||||
owned_languages.setdefault("typescript_property_support_unvalidated", set()).add("typescript")
|
||||
if language in {"c", "cpp"}:
|
||||
record_limits += ("c_bounds_arithmetic_unmodeled",)
|
||||
records.append(
|
||||
|
||||
Reference in New Issue
Block a user