DEV Community

Yogender
Yogender

Posted on

Fixing a Leaked Key Without Breaking the Company

How I used FalkorDB to trace attack paths, choose a safe fix, and check the result.

When a developer's access key leaks, the first question is not just, “What can this account access?” It is, “What can someone reach by following the permissions connected to this account?” A user may belong to a group, assume a role, inherit a policy, and reach a database. A password saved on a server can create another route through a service account.

The quick response is often to disable the user. That may stop the immediate access, but it can also interrupt legitimate work, and it may miss a path that continues through another identity. I wanted to explore a more careful response: trace the exposure, find a small fix that preserves required access, try it on a copy, and verify what changed.

That became BreachPath, my project for Graph Hacks Track 1, Agents That Act on Connected Data.

Why model access as a graph?

In BreachPath, an edge means that control of one asset can lead to another. The graph includes users, groups, roles, policies, servers, secrets, and databases. Crown-jewel databases are marked so the system can check whether an exposed identity can reach them.

This makes a chain of permissions visible as a path. In the main demo, scenario S2, Ravi can reach two crown jewels in different ways: one route escalates through deployment roles and policies; another starts with a saved password on a build server and continues through a billing service account.

There is a design detail behind that graph. FalkorDB's algo.maxFlow requires one relationship type, so I store all access edges as :ATTACK and put their specific meaning in a kind property, such as CAN_ASSUME, HAS_POLICY, or STORES_SECRET. The loader also rejects duplicate source-and-destination pairs because max-flow does not accept parallel edges in this setup.

Business needs are represented separately as BusinessNeed nodes with no graph edges. They describe access the company wants to preserve; they should not become extra connections an attacker can traverse.

From paths to a fix

Cypher variable-length matches list attack paths up to six hops. That bound keeps the path display manageable. It is not the only check: the graph algorithms still detect longer reachability. Scenario S7 deliberately has an eight-hop attack path. It is too long for the path listing, but the configured BFS depth of 50 and max-flow still find it.

To find a small set of permissions to remove, BreachPath uses the max-flow/min-cut relationship. Removable edges have capacity one. FalkorDB calculates the maximum flow from the compromised identity to all crown jewels:

CALL algo.maxFlow({sourceNodes: [s], targetNodes: targets,
                   relationshipTypes: ['ATTACK'], capacityProperty: 'cap'})
YIELD edges, edgeFlows, maxFlow
Enter fullscreen mode Exit fullscreen mode

The flow value tells me the cut capacity, but I also need the actual edges. I use the returned edge flows to build a residual graph in Python, then derive candidate cuts from the nodes reachable on either side. A sanity check confirms that each candidate's capacity adds up to the max-flow value. If it does not, the system stops and sends the incident for human attention.

Safety is part of the cut calculation. For each business need, algo.SPpaths finds a shortest path. In the sandbox, the edges on those paths receive a capacity greater than the number of graph edges. A minimum cut will prefer ordinary capacity-one edges; if the flow reaches the protection threshold, BreachPath reports that it cannot find a safe fix.

In S2, the smallest safe fix removes two edges: detach the risky admin policy from the deployment role, and delete and rotate the password stored on the build server. Ravi keeps the access he needs for his ordinary work.

Try the fix on a copy, then verify it

BreachPath does not apply a proposed fix to the baseline graph. It creates an incident sandbox with GRAPH.COPY, applies the candidate there, and checks the result. Candidate cuts can also be simulated on throwaway copies before the Planner chooses between them.

After execution, the Verifier checks that max flow is zero, that a BFS reaches no crown jewel, and that every business need still has a path. It also measures collateral: which other users lose access to which resources.

That distinction matters: a fix is not safe just because it blocks the attack. It also has to preserve the access the company depends on.

What the agents do—and what they do not do

BreachPath has three LLM agents: the Investigator, Planner, and Reporter. The Investigator and Planner choose from allow-listed tools through an MCP server running over stdio. The Reporter writes a plain-language summary, and a code fact guard checks the report against incident data.

The Executor and Verifier are ordinary code. The Executor applies only validated edge IDs in the sandbox; the Verifier judges the result using graph queries. The Planner can choose among candidate cuts, but its choice must pass simulation. If an LLM call is unavailable, the pipeline can fall back to code and mark the run as degraded.

The rule I used is simple: the graph and code establish what is reachable and what is safe; the LLM helps choose and explain.

Scenario S8 shows where that choice helps. Two one-edge cuts both stop the attack. One would make 12 stale analyst access pairs lose access; the other has zero collateral. BreachPath simulates the alternatives and selects the zero-collateral cut.

In scenario S5, the only attack path uses business-protected edges. There is no safe automatic cut, so BreachPath reports that a human needs to decide what to change instead of claiming it has a safe fix.

What the evaluation shows

I evaluated six methods across eight small, synthetic scenarios and scored the results against an independent NetworkX reference. Seven scenarios have a safe fix. BreachPath's fast mode found a minimum safe cut in all seven, broke zero business needs, and had zero measured collateral. It also returned the correct safe-fix or needs-human verdict in all eight scenarios.

Some baselines came close. An LLM given the graph as JSON found safe fixes in all seven solvable cases, but its chosen cut caused collateral in S8. Simpler approaches such as disabling the user or selecting edges by path frequency broke business needs in this evaluation.

These are planted scenarios, run once per method, on small graphs—not a benchmark of real cloud environments. They helped me check whether the design handled specific cases, including long paths, protected access, collateral, and the absence of a safe fix. The agent evaluation was run on an earlier commit, so I treat it as supporting evidence rather than a claim about every later change.

What I learned building on FalkorDB

The most useful lesson was that the graph model and the algorithm have to fit each other. I initially thought in terms of eight relationship types; the max-flow procedure's single-type requirement led me to normalize them under :ATTACK while preserving each type in a property. I also had to derive the cut from flow output, handle empty algorithm results explicitly, and keep sandbox copies separate from the baseline.

Those were not just implementation details. They shaped how I represented access, how I protected business needs, and how I could explain each proposed change. FalkorDB provides the graph queries, algorithms, and graph-copy operation that make this workflow possible; the rest of BreachPath turns those capabilities into a cautious incident process.

Try BreachPath

BreachPath runs locally with Docker Compose. The README includes setup instructions, the graph model, the queries, and screenshots.

Built for Graph Hacks by WeMakeDevs × FalkorDB.

Top comments (0)