Live demo · Attack-path triage

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.

Alert queue · Kestrel Pay
Raise your own alert
ALR-1041 · EDR · Suspicious process
Crypto-miner process on checkout-web-01
Asset: checkout-web-01 (VM)
prioritizerisk 0.64
reaches :8080runs asreadsexposes :443routes to :443InternetINTERNETcheckout-public-lbLOAD BALANCERcheckout-web-01VMledger-svcCONTAINERcheckout-sessions (Redi…DATA STOREcheckout-web-instanceROLEledger-serviceROLEcheckout-redis-passwordSECRETledger-db-credentialsSECRETpayments-ledger (Postgr…CROWN JEWEL
━ attack path to nearest crown jewel┅ route in from the internet◆ crown jewel
Evidence: shortest attack path
checkout-web-01reaches (8080) →ledger-svcruns as →ledger-servicereads (rds-db:connect) →payments-ledger (Postgres)
How the internet reaches it
Internetexposes (443) →checkout-public-lbroutes to (443) →checkout-web-01
Graph features (Cypher, in a Python resolver)
Hops to nearest crown jewel3
Crown jewels reachable1
Distinct paths (≤ 6 hops)2
Blast radius (assets reachable)7
Secrets reachable2
Privileged roles reachable0
Internet exposure2 hops in
Risk score (Chalk expressions)
Close to a crown jewel+0.44
Reachable from the internet+0.15
Credential already leaked+0.00
Privileged role in reach+0.00
Detection severity+0.05
Several routes in+0.00
Risk score0.64
monitor ≥ 0.3 · prioritize ≥ 0.6
Recorded Chalk query · 2026-10-14 · query cmv8df2f100mr0iwv3qez2itx

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.

Getting started

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-paths
git 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_score

What 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.