Skip to content

Security Graph Model

Use this page when the question is not "how do I open the graph?" but "what is this graph actually storing, how should I read it, and what stays true when the snapshot gets large?"

agent-bom persists the graph as a snapshot-oriented control-plane view:

  • nodes are canonical entities such as agents, MCP servers, tools, packages, credentials, containers, cloud resources, vulnerabilities, and misconfigurations
  • edges are typed relationships such as uses, depends_on, exposes_cred, affects, invoked, and lateral_path
  • attack paths are precomputed fix-first paths derived from the persisted graph, with an explicit reachability verdict and evidence basis
  • interaction risks are runtime-oriented overlays that highlight where agent behavior or shared control surfaces can expand blast radius

This is not a best-effort browser-only canvas. It is a persisted graph snapshot loaded from the control plane.

Reachability truth

Every path distinguishes executable evidence from investigation context:

  • confirmed — a persisted graph path or function-level symbol proof exists
  • likely — package/dependency or observed graph evidence supports the path
  • unknown — structural topology connects the entities, but executable reach has not been proven; these candidates stay below evidence-backed paths
  • unlikely — graph or symbol evidence disproves reachability; the finding is retained as evidence but is not emitted as an exploit chain

Credential and tool exposure increase impact, not reachability. MITRE ATT&CK and ATLAS enrich evidence-backed hops; their technique mappings never create a hop or promote a structural candidate into an executable path.

What a snapshot means

Each graph snapshot is identified by:

  • scan_id
  • tenant_id
  • created_at

The snapshot captures:

  • the nodes present at that scan/control-plane save point
  • the edges between those nodes
  • the derived attack paths and interaction risks for that saved graph
  • aggregate counts used for graph headers, graph search, and UI summaries

The important operator rule is:

  • pagination changes the visible canvas
  • pagination does not change what the snapshot is

So when the graph page says showing 1-500 of 3,200 nodes, it is telling you how much of the persisted graph is on the current page, not that the rest of the graph disappeared.

IDs, timestamps, and evidence

The graph uses stable identifiers wherever possible:

  • agents use canonical agent IDs
  • MCP servers use canonical server stable_id
  • tools, resources, and packages use their own stable IDs
  • graph nodes expose a node_id that the API and UI can round-trip

That means operators can talk about:

  • one node across filters
  • one snapshot across pages
  • one server across repo scan, fleet sync, gateway discovery, and runtime

Time fields have specific meaning:

  • created_at on the snapshot = when the graph snapshot was persisted
  • first_seen on a node = earliest observed timestamp for that entity in the current correlated model
  • last_seen on a node = latest observed timestamp for that entity in the current correlated model

Those are different concepts. A snapshot is a saved graph view; first_seen and last_seen are entity lifecycle signals inside that view.

What the graph is for

The graph is not meant to be a generic everything-map. It exists for three operator jobs:

  1. blast radius Follow package or configuration risk into agents, credentials, tools, and reachable runtime surfaces.
  2. inventory correlation Show how repo, fleet, gateway, and runtime evidence point at the same MCP server or agent surface.
  3. fix-first triage Let operators collapse many exposed paths with one change instead of chasing every finding separately.

For the product and review rubric behind these jobs, see docs/graph/SECURITY_GRAPH_UX_RUBRIC.md.

Reading the graph in the UI

The UI exposes three layers of interpretation:

  1. snapshot metadata
  2. scan ID
  3. captured time
  4. total nodes and edges
  5. current page window
  6. topology filters
  7. focused vs expanded view
  8. relationship scope
  9. runtime/static scope
  10. agent filters
  11. severity filters
  12. node detail
  13. node ID
  14. first seen / last seen
  15. incoming / outgoing edges
  16. sources and impact counts

That split is intentional:

  • the header tells you what snapshot you are looking at
  • the filters tell you how you are slicing it
  • the detail panel tells you why one node matters

Inspecting one instance or path

Open Investigation → Summary, drill into an account or environment, then choose Inspect on a package. Matching package names can represent different instances: the row includes available image/workload context, and Node ID reveals the canonical identifier used by the API. Missing context stays absent.

In Attack Paths → List, expand any hop to load up to 12 direct neighbors. Outgoing and incoming groups retain the relationship labels; an access edge is not automatically a dependency. Use Traverse from this hop for a focused investigation, or Retry neighbor lookup after a failed request. A partial response does not establish that a node has no other neighbors.

The queue distinguishes paths shown, unique paths loaded from both the occurrence queue and priority cards, and the snapshot total. Evidence priority identifies a priority-card score; Queue score identifies an occurrence-queue score. Neither score establishes compromise.

New demo projections direct dependencies from a workload to its image and from an image to its packages. This change does not rewrite existing saved snapshots; rebuild the demo snapshot to see corrected projection semantics.

Scale and readability

To keep the graph readable at larger sizes, agent-bom uses:

  • persisted snapshots rather than only transient browser layouts
  • paginated node windows
  • precomputed attack paths for shortlist triage
  • focused vs expanded topology modes
  • relationship-scope filters
  • node detail enrichment on demand
  • an independent persisted-path fast lane, so full-estate fix guidance cannot delay the first ranked path
  • semantic role chains in the queue (agent → server → package → finding) and bounded one-hop traversal for direct dependencies and dependents

The operator workflow should be:

  1. start with the focused graph or attack-path shortlist
  2. narrow by agent, severity, or relationship scope
  3. expand a hop for bounded direct neighbors, or traverse from it into lineage
  4. open node detail for IDs, timestamps, and impact
  5. page or expand only when the current slice is too narrow

Relationship categories

At a high level the graph separates:

  • inventory relationships
  • hosts
  • uses
  • depends_on
  • provides_tool
  • exposes_cred
  • attack relationships
  • affects
  • vulnerable_to
  • exploitable_via
  • remediates
  • lateral_path
  • runtime relationships
  • invoked
  • accessed
  • delegated_to
  • governance relationships
  • manages
  • owns
  • part_of
  • member_of

This is why the graph page has relationship-scope filters. The same snapshot can be read as inventory, attack path, runtime context, or governance context without pretending those are all the same edge type.

What the graph is not

The graph is not:

  • a replacement for the raw scan JSON
  • a guarantee that every runtime event is persisted forever
  • a substitute for the proxy or gateway itself
  • a live network map of traffic that never entered the control plane

It is the persisted operator model that unifies inventory, findings, runtime evidence, and remediation.