From c2b023ac92e724a9b316d62fa170357234f7d23c Mon Sep 17 00:00:00 2001
From: O M
- GET /v1/findings returns an array. Each item has finding (the node)
+ GET /v1/findings returns findings and, when another page remains,
+ next_cursor. Each item has finding (the node)
plus affected_resource_id, affected_resource_name, and
- affected_resource_type from the VIOLATES edge. Rows are ordered by
- normalized_score descending. Default limit 200.
+ affected_resource_type from the VIOLATES edge. An attack-path
+ finding also includes path, the ordered node ids from the internet to the
+ datastore. Rows are ordered by normalized_score descending. Default limit 200.
POST /v1/rules/run returns matches and findings_created.
@@ -163,7 +165,8 @@ curl -s -H "Authorization: Bearer $OM_API_SECRET" \\
Failures are JSON {`{"error":"..."}`} with 400, 401, or 500. A missing or wrong
secret on any /v1 route except health is 401 unauthorized. Invalid JSON on ingest is 400
- invalid json body. There is no request id and no pagination cursor. Raise
+ invalid json body. There is no request id. A list that has another page returns
+ next_cursor; pass it back as cursor. Raise
limit when a list is truncated. There is no server-side maximum above the
number you pass, except the fixed caps inside path queries, blast radius, and exports.
+ 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.
+
An empty path list means the edges are not there, not that the account is safe. The walk diff --git a/src/pages/docs/cli.astro b/src/pages/docs/cli.astro index 9d6db8c..20f379c 100644 --- a/src/pages/docs/cli.astro +++ b/src/pages/docs/cli.astro @@ -69,7 +69,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';
om rules run [rule-id]om paths listGET /v1/graph/stats.GET /v1/findings. Each card shows severity, title, and the affected resource. Choosing a card highlights that node when it is in the current snapshot.GET /v1/findings. Each card shows severity, title, and the affected resource. An attack-path card also prints the path node ids. Choosing a card highlights the affected resource when it is in the current snapshot.internet-to-datastore when that query exists, then draws those paths. Choose Full graph snapshot to call GET /v1/graph/snapshot instead (500 nodes and 2000 edges).PATH and any reason property is missing. The nodes themselves come from the query, not from the snapshot cap.GET /v1/identity/blast-radius. The panel shows the summary and the reachable nodes. See Blast radius. Other node types do not open that panel.VIOLATES edge. The finding id is
{'finding:{cve-id-lower}:{workload-id}'}, so a second run overwrites the same
row. The edge property relationship is affects. A later run does
- not delete a CVE finding whose package has since left the workload.
+ not delete a CVE finding whose package has since left the workload. Enrichment does not
+ write attack-path findings. Run om rules run afterward so a new CVE on a
+ workload that can reach a datastore is paired with that datastore. See
+ Attack paths.
finding_type is cve.
affected_resource_* comes from the VIOLATES edge. A finding with
- no such edge still exports. Those three fields are omitted.
+ no such edge still exports. Those three fields are omitted. An attack-path finding also
+ includes path, the ordered node ids, both on the record and inside
+ finding.properties.
Open http://localhost:8080. The console opens on
internet-to-datastore, which should draw that path. Stats should be non-zero,
- and the findings list should include the public datastore and the internet-exposed workload.
+ and the findings list should include attack-path rows for web-1 to prod-db, the public datastore,
+ and the internet-exposed workload. web-1 matches the internet-exposed workload rule, the
+ public-IP rule, and the IMDSv1 rule, so each of those becomes its own attack-path finding.
+ The card lists the ids internet:global, aws:sg:sg-web,
+ aws:ec2:i-web-1, aws:iam:role/AdminRole, and
+ aws:rds:prod-db.
If the page is empty, the CLI and the API are pointed at different databases. See
Docker Compose.
normalized_score, highest first, and the console
shows that order.
+
+ The attack-path rule does not use this table. It copies
+ normalized_score from the finding already on the workload and maps that score
+ through the bands above. A source with no score is stored as 75 (high). See
+ Attack paths.
+
- The rules engine reads the graph and writes Finding nodes. Three checks are
- implemented in Go. The rest come from YAML packs embedded in
- the binary at compile time. om rules list prints the combined catalog.
+ The rules engine reads the graph and writes Finding nodes. Three control checks
+ are implemented in Go. An attack-path pass runs after them. The other checks come from
+ YAML packs embedded in the binary at compile time.
+ om rules list prints the combined catalog. attack-path is last.
{`./om rules list
./om rules run
@@ -64,11 +65,17 @@ import DocsLayout from '@/layouts/DocsLayout.astro';
60
Datastore with a CAN_ACCESS edge from an identity whose admin_access is true. The match forces admin_can_access in graph context.
attack-path
- Every match is then scored with the boosts on Prioritization.
+ Control rules and pack rules are scored with the boosts on Prioritization.
+ The attack-path rule copies the source finding’s score and does not add those boosts again.
A base of 70 plus a path-to-datastore boost of 25 becomes 95, severity critical. The
admin-datastore rule forces admin_can_access before scoring, so that match also
receives the +5 admin boost. The other two built-in rules do not set that flag.
@@ -77,8 +84,9 @@ import DocsLayout from '@/layouts/DocsLayout.astro';
internet-to-datastore and still score as not internet-reachable.
- Each match upserts {'finding:{rule-id}:{resource-id}'} and a
- VIOLATES edge from the finding to the resource. Re-running updates that row.
+ Each control-rule match upserts {'finding:{rule-id}:{resource-id}'} and a
+ VIOLATES edge from the finding to the resource. The attack-path id is
+ {'finding:attack-path:{source-finding-id}:{datastore-id}'}. Re-running updates that row.
findings_created counts those upserts, including updates. Pack rules and
cspm-public-datastore consider at most 500 nodes of the target type, ordered by
name. cspm-internet-workload has no row cap. It uses the full
diff --git a/src/pages/docs/schema.astro b/src/pages/docs/schema.astro
index bbf14fe..d24b80b 100644
--- a/src/pages/docs/schema.astro
+++ b/src/pages/docs/schema.astro
@@ -55,7 +55,7 @@ import DocsLayout from '@/layouts/DocsLayout.astro';
Findingfinding_type is cspm or a CVE id is set.finding_type is cspm, cve, exposure, or attack_path.Control{'finding:{rule-id}:{resource-node-id}'}{'finding:{rule-id}:{resource-node-id}'}. An attack-path finding is {'finding:attack-path:{source-finding-id}:{datastore-id}'}.