Same alert, different blast radius.
Triage by where it leads.
A crypto-miner on a checkout server and the same miner on a data-science sandbox are not the same alert. A Chalk Python resolver walks a graph of the estate for lateral movement from the alerted asset to its crown jewels, and native expressions turn that reach into a verdict, with the attack path as evidence.
Eight alerts, four worth waking up for
Pick an alert. Chalk runs the triage live: the resolver queries the graph for every route from the alerted asset to a crown jewel, and the verdict comes back with the shortest one drawn. The alerts come in pairs, so the same detection lands differently depending on what the asset can reach. Or raise your own alert against any asset.
Kestrel Pay, its estate and its alerts are fictional. The graph is a real Neo4j database running as a Chalk scaling group, and every verdict above is a real Chalk online query against a branch that holds these feature definitions. Each alert also shows a recorded run of the same query instantly, while the live one runs. Of the eight alerts, 4 are prioritized.
How it works
Severity tells you how loud a detection is. Reach tells you how much it matters. The graph supplies the reach, and Chalk turns it into features.
An alert arrives
A detection names an asset: a process on a VM, a leaked key, an exposed service. Chalk looks the alert up by id, as it would from your SIEM's table.
A Python resolver asks the graph
A plain Python resolver runs Cypher against a graph of the estate: shortest paths to every crown jewel, the number of routes, what else is in reach, and how the internet gets in.
Expressions score the reach
Native Chalk expressions turn the graph features into a risk score, term by term: closeness to a crown jewel, exposure, a leaked credential, privilege, severity.
The path is the evidence
The verdict comes back with the attack path that justifies it, so an analyst sees why a crypto-miner on one VM is urgent and the same miner on another is not.
Bring your own graph
Clone the example, deploy the graph and the features to a branch, and query an alert. The estate here is a fictional fintech; load your own asset inventory, IAM and network reachability into the graph, and the resolver and scoring do not change.
The resolver is ordinary Python with an HTTP client, so the same pattern works for any graph database, or any other service your features need.
chalk-ai/attack-pathsgit clone https://github.com/chalk-ai/attack-paths
cd attack-paths
# Neo4j with the fictional estate, read-only, reachable only inside your cluster.
./deploy_graph.py
chalk secret set ATTACK_GRAPH_URL=http://<graph service>:7474
# The alert features, the graph resolver and the scoring, on a branch.
chalk apply --branch attack-paths
chalk query --branch attack-paths --in security_alert.id=ALR-1041 \
--out security_alert.triage --out security_alert.risk_scoreWhat Chalk handles
The graph query, the scoring and the serving in one feature graph, live per alert.
Any database, in Python
Resolvers are Python functions, so a graph database, a vector store or an internal API is just another feature source next to your SQL tables.
Graph next to the data
The graph database runs as a Chalk scaling group in your cluster, reachable only from inside it. No public endpoint, no credentials on the wire.
Scoring you can read
The risk score is a sum of named terms in the feature definitions, versioned with the code and explained on every alert.
Live, per alert
Every triage on this page is a Chalk online query; the graph walk itself takes tens of milliseconds.
Features compose
Add alert history, identity risk or asset criticality as more features, and the same query returns them alongside the graph context.
Branches for policy changes
Try new weights or a new crown jewel on a branch, replay yesterday's alerts against it, and compare verdicts before you ship.
Put context behind every alert
Graphs, identity, asset inventories and alert history: talk to an engineer about turning your security context into features your triage can act on.