18.4. Resource Graph#
The resource graph is the typed node/edge view of a Cloudflare
account, built once per assessment and shared by the attack-path
engine, the SIEM shippers, the MCP get_attack_paths tool, the
notification dispatcher, and the dashboard Posture map page. It
is the only place findings are joined to entities; every downstream
consumer reads from the same shape.
Source: src/core/graph/resourceGraph.js.
18.4.1. Building the graph#
const { buildResourceGraph } = require('flareinspect/src/core/graph/resourceGraph');
const graph = buildResourceGraph(assessment);
// graph = { nodes: [...], edges: [...], stats: {...} }
The builder walks assessment.zones, assessment.configuration,
assessment.findings, and the per-account sub-resources (Workers,
KV, D1, Queues, Zaraz, access apps, tunnels, …) and emits a
flat array of typed nodes and a flat array of typed edges. Findings
are attached to their owning node (so the attack-path rules, the
posture map, and the SIEM enrichments all see the same attachment).
18.4.2. Node types#
There are 14 node types. Each node has a stable id (the
{type}:{zone|account}:{resource} triple), a type string, a
label (for the UI), and a props bag of resource-specific
data.
Type |
Meaning |
Example id |
|---|---|---|
|
Synthetic root node — the entry point for every external request |
|
|
The Cloudflare account |
|
|
A zone in the account |
|
|
A DNS record (A / AAAA / CNAME / TXT / …) |
|
|
The IP / hostname a record resolves to |
|
|
A Cloudflare Worker script |
|
|
A Cloudflare Tunnel |
|
|
A Cloudflare Access application |
|
|
An R2 bucket |
|
|
A Workers KV namespace |
|
|
A D1 database |
|
|
A Workers Queue |
|
|
A generic downstream service (tunnel target, LB origin, …) |
|
|
A finding attached to a parent node (informational; rare) |
|
18.4.3. Edge types#
There are 8 edge types. Each edge has from, to, and a
type string. Edges are directional but the UI draws them
undirected.
Type |
Meaning |
Example |
|---|---|---|
|
Hierarchical ownership (record → zone → account) |
|
|
DNS A/AAAA/CNAME record → origin |
|
|
Proxied DNS record → its service |
|
|
Tunnel / worker / access-app → service |
|
|
Access app → service (or Access policy → app) |
|
|
Worker script → service / route |
|
|
Worker → KV / D1 / R2 |
|
|
Internet → entry node |
|
The stats bag on the graph records counts (nodes by type, edges
by type, findings by severity).
18.4.4. Attack-path rules#
src/core/graph/attackPaths.js runs five rule-based detectors
against the graph. Each rule reads nodes + edges + the
findingsByNode index and emits 0+ paths. Rules are
deterministic and ordered — same input → same path IDs (so the
UI can deep-link a path).
Kind |
Severity |
Trigger |
|---|---|---|
|
high |
Un-proxied A/AAAA/CNAME record that resolves to an origin |
|
high |
TLS configuration that does not enforce HTTPS / min TLS 1.2 |
|
high |
Access app whose policy effectively allows everyone (no MFA / allow-everyone) |
|
medium |
Public tunnel routing to a private service with no Access app in front |
|
critical |
Workers binding whose value is a secret-shaped plain-text string (e.g. |
Each emitted path has the shape:
{
"id": "ap:exposed-origin:dns:z1:r1",
"kind": "exposed-origin",
"title": "Exposed origin (un-proxied A/AAAA/CNAME)",
"severity": "high",
"hopCount": 2,
"entryNodeId": "internet",
"targetNodeId": "origin:203.0.113.1",
"nodes": ["internet", "dns:z1:r1", "origin:203.0.113.1"],
"edges": [{ "from": "internet", "to": "dns:z1:r1", "type": "resolves_to" },
{ "from": "dns:z1:r1", "to": "origin:203.0.113.1", "type": "resolves_to" }],
"explanation": "DNS record a.x.test (A) is not proxied and resolves to 203.0.113.1 — origin is directly reachable from the Internet.",
"relatedCheckIds": ["CFL-INSIGHT-005"],
"remediableCheckIds": []
}
18.4.5. Adding a new rule#
Append a new
detect*function inattackPaths.js.Add it to the
RULESarray with a uniquekindstring.Add a fixture + assertion in
tests/attackPaths.test.js.
The pipeline is fault-tolerant: a single rule that throws is
captured as a rule_error:<kind> path so the rest of the rules
still run.
18.4.6. Consumers#
Consumer |
What it reads |
|---|---|
Posture map ( |
Full graph + paths; draws nodes, edges, attack paths |
|
Returns the graph + paths JSON for the dashboard |
SIEM shippers ( |
Per-finding |
MCP |
Returns the graph + paths to the agent |
Notification dispatcher |
|
18.4.7. Next steps#
Posture Map — how the dashboard page uses this graph
SIEM — how the shippers enrich findings with the graph
MCP Server — the MCP
get_attack_pathstool