mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
feat(pre-push-safety-net): census the declared families and fix a sink matched in a comment
Seven vulnerability families are declared and two had been proven. The census asks each of them, on whole checkouts of NodeGoat and VAmPI, whether it establishes anything at all, and reports a family that answered and found nothing separately from one that was never asked. Four establish findings: injection, access_control, config_secrets and untrusted_destination. Output encoding, path, prototype and deserialization establish nothing on either subject. All 299 NodeGoat and 35 VAmPI questions completed, so the silences are answers. Three of those silences are diagnosed gaps, not clean code. NodeGoat's documented SSRF calls needle.get, which the sink vocabulary does not hold. Its XSS is server-rendered, and output encoding declares only DOM sinks, so no question it can ask could have found it. Its two access-control defects raise no obligation at all: measured with the real dominance query, every modelled data operation lives in app/data/*-dao.js and none in a route handler file, while the family scopes operations to the region's own file. Tasks 14.2 to 14.5 carry each gap; none is fixed here. The census also found a false positive and it is fixed. NodeGoat's only prototype report was the word `set` inside `// set these up in case we have an error case`: the matched node is the <operator>.assignment holding the entire 1,000-character signup handler, and sinkMatches searched a node's whole text. It now confines the text clause to the callee and excludes operator nodes. Fourth instance of this bug class after fgets/gets, resolve/resolveUrl and user/users. Measured on NodeGoat's non-vendor calls, injection sinks fall 28 to 3, prototype 13 to 2 and untrusted destination 26 to 7, with no true positive lost; req.query had been an injection sink because `query` is word-bounded inside it. Witnesses now name the sink call rather than the assignment that contained it. 1,411 tests pass, ten skipped; the six Joern-gated query tests pass in the image, including a new one that fails on the shape this fixed. 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
770bae0e9e
commit
4e2619e248
@@ -499,8 +499,24 @@
|
||||
// which is the safe API being reported as the dangerous one. Third instance of this bug class here, after
|
||||
// the sanitizer that matched `resolve` inside `resolveUrl` and the discharger that matched `user` inside
|
||||
// `users`; the sink matcher had simply never been given the same treatment.
|
||||
def sinkMatches(c: io.shiftleft.codepropertygraph.generated.nodes.Call, n: String): Boolean =
|
||||
c.name == n || c.code.startsWith(n + "(") || mentionsToken(c.code, List(n)) || mentionsToken(c.methodFullName, List(n))
|
||||
// Fourth instance of the same bug class, and this one word-bounding did not catch. NodeGoat's signup handler
|
||||
// is one `<operator>.assignment` node whose code is the entire 1,000-character arrow function, and inside it
|
||||
// a comment reads `// set these up in case we have an error case`. `set` is a prototype-pollution sink, the
|
||||
// word is bounded, and the match was reported as a prototype finding on a handler that calls no `set` at
|
||||
// all. Searching a node's whole text asks "is this word anywhere near here", which is not the question.
|
||||
//
|
||||
// So the text clause is confined to the CALLEE -- everything before the first argument -- which is the only
|
||||
// part of a call that names what is being called, and operators are excluded from it entirely: an
|
||||
// assignment is not a call to a library function, whatever its right-hand side happens to spell.
|
||||
def calleeText(code: String): String = {
|
||||
val open = code.indexOf('(')
|
||||
if (open < 0) code else code.substring(0, open)
|
||||
}
|
||||
|
||||
def sinkMatches(c: io.shiftleft.codepropertygraph.generated.nodes.Call, n: String): Boolean = {
|
||||
val operator = c.name.startsWith("<operator")
|
||||
c.name == n || (!operator && (mentionsToken(calleeText(c.code), List(n)) || mentionsToken(c.methodFullName, List(n))))
|
||||
}
|
||||
|
||||
// Does THIS path element cleanse the value flowing through it?
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user