DEV Community

RAVI Ravi
RAVI Ravi

Posted on

Look Before You Leap: Building an Incident Agent That Proves Its Fix in a FalkorDB Sandbox Graph

Most agent demos stop at an answer. Ours had to take an action in production—the kind of action that pages people at 3 a.m. if it is wrong.

Building AegisGraph for Graph Hacks (WeMakeDevs x FalkorDB, Track 01: Agents That Act on Connected Data) taught us that an agent's ability to act safely depends almost entirely on the shape of its context. Flat vector chunks were not enough. A knowledge graph was.


The question an incident actually asks

A WAF alert says forged bearer tokens are being accepted by auth-token-service, and points at jwt-crypto-core. The on-call engineer needs answers to three things at once:

  1. What customer data can an attacker reach from here?
  2. What can we cut to stop it without taking checkout down?
  3. Who should fix the code?

Each answer lives in a different system. Package imports live in the repositories. Service-to-service traffic lives in the mesh. Data access lives in cloud IAM.

The real answer is a multi-hop path across all three:

CVE → Package → Function → Service → Service Mesh → IAM Role → Database

In our demo topology that is five to eight hops, and there are six such paths leading into 3 critical PII databases.

A vector store finds text that sounds like JWT bugs. It cannot tell you that legacy-billing-bridge sits on all six paths.


One graph for code, runtime and identity

We modelled the three layers as one FalkorDB property graph:

  • Code: (:Vulnerability)-[:AFFECTS]->(:Package)<-[:IMPORTS]-(:Function)<-[:MAINTAINED]-(:Developer), plus (:Function)-[:CALLS]->(:Function)
  • Runtime: (:Service)-[:DEPLOYS_CODE]->(:Function) and (:Service)-[:ROUTES_TO]->(:Service)
  • Identity and data: (:Service)-[:ASSUMES_ROLE]->(:IAMRole)-[:CAN_ACCESS]->(:Database)

The whole blast radius then becomes one OpenCypher query:


cypher
MATCH (v:Vulnerability {id: $cve_id})-[:AFFECTS]->(:Package)<-[:IMPORTS]-(f:Function)<-[:DEPLOYS_CODE]-(origin:Service)
WITH DISTINCT origin
MATCH attack = (origin)-[:ROUTES_TO*0..3]->(pivot:Service)-[:ASSUMES_ROLE]->(role:IAMRole)
               -[:CAN_ACCESS]->(db:Database {contains_pii: true})
WHERE ALL(r IN relationships(attack) WHERE coalesce(r.status, 'ACTIVE') = 'ACTIVE')
RETURN pivot.name, role.name, db.name, [n IN nodes(attack) | n.name] AS path
Enter fullscreen mode Exit fullscreen mode

Top comments (0)