You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
roadmap — discovered while evaluating whether OWASP NodeGoat is sufficiently modelled for security triage. Sibling of #85.
Problem
Method calls on a value that has no resolvable type produce no call-graph edge and no external symbol, so the database layer of a typical Node app is invisible.
Measured on NodeGoat (--eager -a 2, dependencies installed):
route -> mongodb path: NO PATH
callers of mongodb nodes: (none — every mongodb node is a graph root)
The 22 external symbols include needle.get, node-esapi.encoder and several mongodb:* entries, but the mongodb ones come from connection setup in server/config code. The actual query sinks produce nothing:
db is an untyped constructor-function parameter. The tsc resolver cannot type it, so resolveCalleeSignature fails; the phantom fallback (src/semantic_analysis/phantoms.ts) only indexes require() of external specifiers at top level, so there is nothing to attribute the call to either.
Consequence: the question "does user input from POST /allocations reach a Mongo query?" — the NoSQL-injection case, and squarely the COCOA/PoE use case — cannot be answered from the graph.
Scope boundary
Makes unresolved method calls on untyped receivers attributable, so a security consumer can reach the sink. Does not:
attempt real points-to or type inference for untyped values;
invent edges to a specific library when the receiver's origin is genuinely unknown;
change the schema — call sites already carry method_name and receiver_expr;
There is a strong hypothesis that #85 fixes much of this by itself. The jelly leg currently reports 3725 dropped edges, and jellyProvider drops any edge with an unresolved endpoint. Today the DAO methods are not callables at all, so every edge whose source is a DAO method is dropped regardless of whether jelly traced it. Re-measure after #85 before designing anything here; the residual may be small.
For a call whose receiver traces to a parameter, record enough on the call site to attribute the sink (receiver expression and the parameter it binds to)
Where the receiver's origin is a first-party parameter, emit an edge or a phantom rather than silently dropping the call
Fixture: a constructor function taking an untyped db parameter, calling db.collection("x").find(...)
Caveats and known risks
Flooding. Attributing every unresolved method call on every unknown receiver would add large numbers of low-value edges. Any attribution must be bounded — e.g. only where the receiver is traceable to a declared parameter of an enclosing first-party callable.
Wrong attribution is worse than none for security triage. A false edge into a sink manufactures a vulnerability. Prefer recording call-site evidence over synthesizing an edge when the target is uncertain.
On unmodified NodeGoat, a path exists from an app/routes/* node to a Mongo query sink, and it is reported in the PR with the exact hop sequence.
The DAO query calls at allocations-dao.js:29 and :86 are attributable to a sink from the analysis output alone.
No edge is emitted whose target is a guess: every new edge is justified by a traceable receiver origin, and the fixture asserts the negative case (genuinely unknown receiver ⇒ no edge).
Plan
roadmap — discovered while evaluating whether OWASP NodeGoat is sufficiently modelled for security triage. Sibling of #85.
Problem
Method calls on a value that has no resolvable type produce no call-graph edge and no external symbol, so the database layer of a typical Node app is invisible.
Measured on NodeGoat (
--eager -a 2, dependencies installed):The 22 external symbols include
needle.get,node-esapi.encoderand severalmongodb:*entries, but themongodbones come from connection setup in server/config code. The actual query sinks produce nothing:dbis an untyped constructor-function parameter. The tsc resolver cannot type it, soresolveCalleeSignaturefails; the phantom fallback (src/semantic_analysis/phantoms.ts) only indexesrequire()of external specifiers at top level, so there is nothing to attribute the call to either.Consequence: the question "does user input from
POST /allocationsreach a Mongo query?" — the NoSQL-injection case, and squarely the COCOA/PoE use case — cannot be answered from the graph.Scope boundary
Makes unresolved method calls on untyped receivers attributable, so a security consumer can reach the sink. Does not:
method_nameandreceiver_expr;this.x = fnand object-literal methods as callables #85 and must land first.Ordering note — test this after #85
There is a strong hypothesis that #85 fixes much of this by itself. The jelly leg currently reports 3725 dropped edges, and
jellyProviderdrops any edge with an unresolved endpoint. Today the DAO methods are not callables at all, so every edge whose source is a DAO method is dropped regardless of whether jelly traced it. Re-measure after #85 before designing anything here; the residual may be small.Goals
this.x = fnand object-literal methods as callables #85 lands and record what remains unreachabledbparameter, callingdb.collection("x").find(...)Caveats and known risks
mongodbfrom connection code. Once feat(symbol-table): materializethis.x = fnand object-literal methods as callables #85 lands, some of these edges may arrive from jelly for free; duplicating them via tsc must coalesce on the(source, target)key.Definition of done
app/routes/*node to a Mongo query sink, and it is reported in the PR with the exact hop sequence.allocations-dao.js:29and:86are attributable to a sink from the analysis output alone.