Get started

OpenSourceOM attack path queries

Core ships six named queries. You run them from the CLI, the API, or the console’s query dropdown. There is no ad-hoc graph language in this version. What attack path analysis means, as a practice, is in Attack path analysis.

./om paths list
./om paths run internet-to-datastore

HTTP: GET /v1/graph/queries lists them. GET /v1/graph/query?name=internet-to-datastore runs one. The JSON result is query, summary, and paths (each path is an array of nodes).

What a result looks like

CLI output is plain text. The HTTP body is JSON. Each path is the ordered list of nodes, starting at the internet node when the walk started there:

{
  "query": "internet-to-datastore",
  "summary": "Attack paths from the internet through workloads to datastores",
  "paths": [
    [
      { "id": "internet:global", "type": "Internet", "name": "Internet" },
      { "id": "aws:sg:sg-web", "type": "Network", "name": "sg-web" },
      { "id": "aws:ec2:i-web-1", "type": "Workload", "name": "web-1" },
      { "id": "aws:iam:role/AdminRole", "type": "Identity", "name": "AdminRole" },
      { "id": "aws:rds:prod-db", "type": "Datastore", "name": "prod-db" }
    ]
  ]
}

Walks follow every edge type, not only REACHABLE. A cycle is cut when the next node is already on the path. Depth counts edges. The demo path Internet → sg-web → web-1 → AdminRole → prod-db is four edges, so it fits inside the datastore walk.

internet-to-workload

Recursive walk from internet:global along any edge type. A path is kept when it ends on a Workload. Recursion stops before depth 6. At most 50 paths are returned. Shorter paths that end on a workload earlier in the walk are included, so one exposed instance can appear as its own path and again as a prefix of a longer one.

internet-to-datastore

Same walk, ending on a Datastore, and the path must include at least one Workload. Recursion stops before depth 8. At most 50 paths are returned. This is the query the demo prints after om scan demo. It does not look at sensitivity. A log bucket and a customer database on the same shape of path both appear.

internet-to-sensitive-datastore

The same walk as internet-to-datastore, with the same depth and path caps, kept only when the datastore’s sensitivity property is a non-empty string after trimming. A missing property and a blank value stay in internet-to-datastore and drop out of this one. The value itself is not ranked: customer and restricted both count. Collectors copy it from a tag or label named sensitivity or data-class. The demo marks prod-db and leaves the log and asset buckets unmarked.

public-datastore

Single-node results: datastores where public_access is true or public_access_block is disabled. The match is the node type and those properties, so a public S3 bucket and a public GCS bucket both appear. Limit 100. public-s3-buckets is an alias and is not listed.

admin-identities

Single-node results: identities with admin_access set to true. Limit 100. Collectors set that flag from role definitions and IAM bindings, described on each collector page.

admin-to-public-datastore

Pairs a public datastore (same property test as public-datastore) with an identity that has admin_access and a CAN_ACCESS edge whose target is that datastore. Limit 50. The returned node list is the datastore, then the identity. toxic-s3-public-with-admin-role is an alias and is not listed.

Attack-path findings

Named queries answer a question you ask. om rules run and POST /v1/rules/run also write the combination into the findings list. Control rules run first. The attack-path rule then writes one finding for each existing finding on an internet-reachable workload, paired with a datastore that workload can reach.

A workload is internet-reachable when a REACHABLE walk from internet:global ends on it, with the same depth limit as internet-to-datastore (recursion stops before depth 8). The source finding is a VIOLATES edge whose target is that workload. Findings with finding_type: attack_path are not sources, so the pass does not chain. Two findings on the same workload produce two attack-path rows for the same datastore.

The workload reaches a datastore in either of two hops. A CAN_ACCESS edge from the workload to the datastore is one. ASSUMES to an identity that itself has CAN_ACCESS is the other. The demo path uses the second: Internet → sg-web → web-1 → AdminRole → prod-db. When both hops reach the same datastore, the shorter path is stored, which is the direct CAN_ACCESS edge. The finding description names the source finding and the datastore, for example "Internet-exposed workload: web-1 is on a path to prod-db."

path is that ordered list of node ids. The finding id is finding:attack-path:{source-finding-id}:{datastore-id}. finding_type is attack_path. normalized_score and severity are copied from the source finding’s score, using the same bands as other rules. A source with no score is stored as 75, severity high. The graph-context bonuses are not added again. The VIOLATES edge points at the workload, so the findings list names that workload as the affected resource. datastore_id and source_finding_id are properties on the finding.

A reachable workload with no datastore hop does not get this finding. A hop whose workload has no other finding does not either. Run om rules run attack-path on a graph that has the edges and no findings, and the pass writes nothing. om rules run with no id is different: cspm-internet-workload writes a finding on every internet-reachable workload first, and the attack-path pass then pairs that finding with each datastore the workload can reach. sensitivity does not filter these rows. internet-to-sensitive-datastore remains the query that keeps only marked datastores.

Re-running the rule deletes an attack-path finding whose hop or source finding is gone. Other rules’ findings stay. om enrich cve does not write attack-path rows. Run rules again after enrichment so a new CVE is paired with the datastore. GET /v1/findings returns path on the row. The console prints those ids on the card. SIEM export includes the same list.

Audit events on a path

om scan aws reads CloudTrail management events, om scan azure reads administrative Activity Log events, and om scan gcp reads Admin Activity audit logs, from the last 24 hours. A match is stored as audit_events on the identity and the resource the event names. A match requires both nodes to already be in the scan, and the resource has to sit on an exposed path: an internet-reachable workload, something that workload assumes or can access, a public datastore, or an identity that can access one. At most five events are kept on a node, newest first. A failed lookup leaves the property off and the rest of the scan still ingests.

GET /v1/graph/query copies an event into audits when that resource node is on the returned path. index is the path’s position in paths. The same event stored on two nodes of one path is listed once. om paths run prints the time, event name, principal, and resource under that path. The console does the same. GetObject is an S3 data event and is not in this slice. Entra ID sign-in logs and GCP Data Access logs are not collected. See the AWS collector, the Azure collector, and the GCP collector.

Reading an empty result

An empty path list means the edges are not there, not that the account is safe. The walk cannot start unless some edge leaves internet:global. That edge is missing when an AWS instance has no public IPv4 or its security groups are closed, when an Azure NIC has no public IP, when a GCE instance has no NAT IP, or when a pod is selected only by ClusterIP Services. Check om graph stats and the collector page before treating zero paths as a clean bill.

public-datastore and admin-identities do not need that edge. They match properties. A public Azure storage account shows up in public-datastore even when no VM is reachable from the internet.

Questions

Which attack path queries does OpenSourceOM run?

om paths list prints six names. internet-to-datastore walks from the internet node and keeps paths that include a workload and end on a datastore. internet-to-sensitive-datastore is that walk limited to datastores whose sensitivity property is set. The other four are internet-to-workload, public-datastore, admin-identities, and admin-to-public-datastore. public-s3-buckets and toxic-s3-public-with-admin-role still run as aliases.

How do I run an attack path query?

After a scan, run om paths run internet-to-datastore. The same named queries are available from GET /v1/graph/query and from the console dropdown.

When does a rules run write an attack-path finding?

After the control rules, om rules run writes one finding per existing finding on an internet-reachable workload that can reach a datastore. The finding stores the path as ordered node ids. GET /v1/findings returns that path on the row.

Where do cloud audit events show up on a path?

om scan aws stores recent CloudTrail management events, om scan azure stores recent Activity Log events, and om scan gcp stores recent Admin Activity audit logs, on the identity and resource they name. GET /v1/graph/query returns those events in audits when the resource node is on a path. om paths run prints the same lines under the path.

Related reading