Skip to content

feat(call-graph): attribute method calls on untyped receivers so DB sinks are reachable #87

Description

@rahlk

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):

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:

// app/data/allocations-dao.js
15    const allocationsCol = db.collection("allocations");
29    allocationsCol.update({});
86    allocationsCol.find(searchCriteria()).toArray((err, allocations) => {  });

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:

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 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.

Goals

  • Re-measure NodeGoat after feat(symbol-table): materialize this.x = fn and object-literal methods as callables #85 lands and record what remains unreachable
  • 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.
  • Jelly overlap. Jelly already reaches into mongodb from connection code. Once feat(symbol-table): materialize this.x = fn and 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.
  • Scope is the v1 (0.x) line.

Definition of done

  • 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew feature or capability

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions