Title: [Security][Medium] /api/graph/trace-flow-reverse enumerates all simple paths with attacker-controlled maxDepth — exponential CPU/memory DoS
Severity: Medium (CWE-400, CWE-407)
Locations: packages/core/src/flow/tracer.ts:144-165 (walkBack); packages/server/src/routes/flow.ts:32-58
Summary
walkBack DFS-enumerates every acyclic path up to maxDepth, pushing a full copy of pathNodes/pathEdges at every leaf — simple-path enumeration without a visited-set bound. The forward tracer uses a global visited set; the reverse tracer does not. maxDepth and the whole config come from the request body with no zod validation.
Evidence
packages/core/src/flow/tracer.ts:144-165:
function walkBack(nodeId, depth, path, pathEdges): void {
if (depth > cfg.maxDepth) {
allPaths.push({ pathNodes: [...path], pathEdges: [...pathEdges] }); return
}
const ins = (incoming.get(nodeId) ?? []).filter(
(e) => !noise.has(e.source) && nodeMap.has(e.source) && !path.includes(e.source))
if (ins.length === 0) { allPaths.push({ pathNodes: [...path], pathEdges: [...pathEdges] }); return }
for (const edge of ins) { ... walkBack(edge.source, depth + 1, path, pathEdges) ... }
}
routes/flow.ts:47-58: traceFlowReverse(nodeId, nodes, edges, config) with config = req.body.config.
Impact
One unauthenticated POST with {"config":{"maxDepth": 50}} against a diamond-rich call graph freezes the single-threaded server and can exhaust memory via stored path copies. Related: noiseFilter.minFanIn accepts negatives (inverting the filter to match everything), and /graph/convergence consumes unbounded string[][] bodies.
Recommended fix
Server-side cap on maxDepth and total enumerated paths/edges; bounded reverse-reachability instead of full path enumeration; zod-validate FlowTracerConfig (non-negative minFanIn, bounded excludePatterns) and convergence/impact/callgraph inputs (depth parseInt clamps).
Title: [Security][Medium] /api/graph/trace-flow-reverse enumerates all simple paths with attacker-controlled maxDepth — exponential CPU/memory DoS
Severity: Medium (CWE-400, CWE-407)
Locations: packages/core/src/flow/tracer.ts:144-165 (walkBack); packages/server/src/routes/flow.ts:32-58
Summary
walkBackDFS-enumerates every acyclic path up tomaxDepth, pushing a full copy of pathNodes/pathEdges at every leaf — simple-path enumeration without a visited-set bound. The forward tracer uses a global visited set; the reverse tracer does not.maxDepthand the wholeconfigcome from the request body with no zod validation.Evidence
packages/core/src/flow/tracer.ts:144-165:
routes/flow.ts:47-58:
traceFlowReverse(nodeId, nodes, edges, config)withconfig = req.body.config.Impact
One unauthenticated POST with
{"config":{"maxDepth": 50}}against a diamond-rich call graph freezes the single-threaded server and can exhaust memory via stored path copies. Related:noiseFilter.minFanInaccepts negatives (inverting the filter to match everything), and/graph/convergenceconsumes unboundedstring[][]bodies.Recommended fix
Server-side cap on maxDepth and total enumerated paths/edges; bounded reverse-reachability instead of full path enumeration; zod-validate FlowTracerConfig (non-negative minFanIn, bounded excludePatterns) and convergence/impact/callgraph inputs (depth parseInt clamps).