Part of #170 (source↔sink reachability API).
Goal
Given a sink (a callable or external/library symbol), return all sources that can reach it through the call graph. This is the reverse direction — the originally requested capability — and it answers "who can get here?" for a dangerous operation.
The key point: this needs no new graph data. It is the forward reachability query with the edge direction flipped, so it is purely a query concern over the existing call graph.
Proposed API
def get_reaching_sources(
sink: str,
sources: Iterable[str] | None = None, # candidate source signatures; None ⇒ any ancestor
*,
max_depth: int | None = None,
) -> Dict[str, int]: # reaching source signature -> shortest hop distance
...
Semantics
All s such that s -CALLS*1..max_depth-> sink, intersected with sources when provided.
Backing implementations
- Neo4j:
MATCH (s:Symbol)-[:CALLS*1..]->(t:Symbol {signature:$sink})
WHERE $sources IS NULL OR s.signature IN $sources
RETURN DISTINCT s.signature AS source
Same query as forward reachability, just anchored on the sink and traversing incoming edges.
- In-memory (NetworkX):
nx.ancestors(G, sink), intersected with sources.
Example use
For a known dangerous sink (e.g. node:child_process.exec), enumerate the entrypoints/route handlers that can reach it — i.e. which externally-triggerable sources put that sink in play.
Acceptance criteria
Part of #170 (source↔sink reachability API).
Goal
Given a sink (a callable or external/library symbol), return all sources that can reach it through the call graph. This is the reverse direction — the originally requested capability — and it answers "who can get here?" for a dangerous operation.
The key point: this needs no new graph data. It is the forward reachability query with the edge direction flipped, so it is purely a query concern over the existing call graph.
Proposed API
Semantics
All
ssuch thats -CALLS*1..max_depth-> sink, intersected withsourceswhen provided.Backing implementations
nx.ancestors(G, sink), intersected withsources.Example use
For a known dangerous sink (e.g.
node:child_process.exec), enumerate the entrypoints/route handlers that can reach it — i.e. which externally-triggerable sources put that sink in play.Acceptance criteria
sources=None, explicitsourcesset, andmax_depthhonored.Externalphantom symbol, not just an internal callable.