✓ Human-authored analysis; AI used for formatting and proofreading.
Per-resource framework checks would have passed; the breach happened because of resource composition. Stave's job is the composition. The chains and cross-resource invariants that per-resource scanners can't see by construction. Run both.
The Capital One incident in 2019 exfiltrated 100 million customer records. The path was specific: an EC2 instance with IMDSv1 enabled, accessible via SSRF from the public-facing application, running with an IAM role that had s3:GetObject on a bucket holding the credit card data. Each individual setting was reviewable; each individual setting could have been "fixed" without anyone realizing the architecture composed into an exfiltration path.
If you had run turbot/steampipe-mod-aws-compliance against that account the morning of the breach, every CIS check would have passed. The IAM user had MFA, the bucket had encryption, the security group had documented inbound rules, the trail was logging. The composition failed. Per-resource scanners check the rows; the breach is in the join.
AWS Compliance Mod job is framework coverage of single-resource properties, and it does that job extremely well. Stave's job is the compositional layer above. Different tool, different question.
What the AWS Compliance Mod is good at
540+ controls across 16+ frameworks. CIS AWS Benchmark v1.4/v3.0/v4.0, PCI-DSS v3.2.1/v4.0, HIPAA Security Rule, NIST 800-53 Rev 5, SOC 2, FedRAMP Moderate, GxP, AWS Foundational Security Best Practices, more. Each control is a SQL check against Steampipe tables that wrap live AWS APIs. Mature ecosystem. Wide adoption.
If your question is "am I CIS-compliant right now?", install it. The framework-cited dashboards render exactly the evidence pack auditors expect. The per-resource property checks are fast, debuggable, deterministic. The contributor velocity is real.
For the atomic-controls slice, "is encryption enabled," "is MFA on," "is logging configured", the AWS Compliance Mod's framework breadth is its strength. Stave's catalog has those controls too (~86% of the Stave catalog is atomic), but Stave doesn't compete on framework coverage of the atomic axis. That territory is theirs.
Where Stave's territory starts
The 14% of Stave's catalog that's compound which is currently 4,755 controls and 770 chains, growing is the part that catches what per-resource scanners can't see by construction. Three classes:
Compound risk chains. A set of controls that, when they co-fail, create more risk than the sum of individual findings. A public-read S3 bucket plus access logging disabled is an exfiltration path with no audit trail. The chain engine fires when the composition's escalation threshold is met; the compound finding carries its own severity that may exceed any member.
Ghost references. Dangling cross-resource pointers such as a Cognito user pool referencing a deleted Lambda trigger, a CloudFront distribution whose S3 origin no longer exists, an ECS task naming a removed IAM role. The reference exists in one asset's configuration; the absence is in another asset's inventory. Per-resource scanners that don't model cross-resource invariants miss every one of these. Stave's catalog has ~99 ghost-reference controls today across APIGateway, CloudFront, Cognito, DynamoDB, ECS, EKS, ELB, EventBridge, Secrets Manager, SNS, SQS.
Snapshot-anchored reproducibility. Re-running the same obs.v0.1 snapshot produces byte-identical output. Two weeks later, a different engineer points the same snapshot at Stave and gets the same verdict. This is useful for audit timelines, regression bisects, "what did we know at time T" questions. Live-API tools can't offer this.
Side-by-side: the same intent, two tools
Both detecting "cross-account role assumption without an external ID."
AWS Compliance Mod — per-resource SQL check
The shape (representative, paraphrased from the live mod's IAM section):
-- One row per IAM role; check the role's own trust policy.
select
role_arn as resource,
case
when trust_requires_external_id then 'ok'
else 'alarm'
end as status,
case
when trust_requires_external_id then role_name || ' requires ExternalId.'
else role_name || ' allows cross-account assume without ExternalId.'
end as reason
from aws_iam_role
where assume_role_policy_document_has_cross_account_principal;
Per-role, status, reason, rendered in a CIS-cited benchmark dashboard. The check is per-role and SQL-shaped; the "is this role cross-account?" question is answered from the role's own trust policy.
Stave — compound control reasoning over an IAM-graph computation
CTL.IAM.IDENTITY.BLASTRADIUS.002 — abridged:
id: CTL.IAM.IDENTITY.BLASTRADIUS.002
name: Cross-Account Role Must Require External ID
severity: critical
scope: compound
corpus_reference: MITRE:T1550
applicable_asset_types: [aws_iam_role]
unsafe_predicate:
all:
- field: properties.identity.kind
op: eq
value: role
- field: properties.identity.role.blast_radius_scope
op: eq
value: cross_account
- field: properties.identity.role.cross_account_trust_without_external_id
op: eq
value: true
The predicate checks three properties on a single role. But two of those properties (blast_radius_scope and cross_account_trust_without_external_id) are pre-computed by Stave's observation extractor by reasoning across the role's trust policy, attached permissions, and the reachable resources in other accounts. The extractor walks the IAM graph; the predicate evaluates the result.
The control fires when the composition produces the unsafe state. AWS Compliance Mod's per-row SQL shape can't express the composition. The "is the trust missing an external ID?" question is per-role and SQL-shaped; the "does the role have cross-account reach AND does the trust permit assumption from any principal in the trusted account?" question is graph-shaped, computed once during observation extraction, and predicated on at query time.
When to use which
| Your question | Use |
|---|---|
| Am I CIS-compliant right now? | AWS Compliance Mod |
| Am I PCI/HIPAA/SOC 2/FedRAMP compliant? | AWS Compliance Mod |
| Do my IAM users have MFA? (per-resource check) | AWS Compliance Mod |
| Would the Capital One pattern get through my CI? | Stave |
| Where are my dangling cross-resource references? | Stave |
| Can I prove this verdict was deterministic against this snapshot? | Stave |
| Can I export detection logic to Z3 / Soufflé for formal analysis? | Stave |
| I want both auditor reporting and compound-risk detection | Both, side by side |
Running both together
Both tools render in Powerpipe and consume Steampipe / DuckDB backends. Install side by side:
# Framework benchmarks against live AWS.
powerpipe mod install github.com/turbot/steampipe-mod-aws-compliance
# Compound risk + ghost references over snapshots.
powerpipe mod install github.com/zepho/powerpipe-mod-stave
# Stave itself produces the snapshot the powerpipe-mod-stave consumes.
go install github.com/sufield/stave/cmd/stave@latest
A typical workflow:
-
Daily / on-snapshot:
stave apply --format json > out.stave.jsonproduces the deterministic evaluation. CI fails on new compound-risk chains or ghost references viastave ci gate. -
Pre-audit:
powerpipe benchmark run cis_v3 --mod aws_compliancerenders the framework-cited evidence pack auditors expect. -
Architecture review:
powerpipe serveropens both dashboards side by side. The AWS Compliance Mod's severity rollup answers the framework question; Stave'sstave_posturedashboard answers the composition question.
The two surfaces are intentionally orthogonal. The Stave Powerpipe mod deliberately does not render severity-count cards (that surface duplicates AWS Compliance Mod territory, and Stave's first dashboard widget is the chain graph instead). The AWS Compliance Mod does not render compound-risk chains (by design its job is per-resource framework coverage). Neither tries to be the other.
TL;DR
Run both. Auditor reports come from the AWS Compliance Mod. Composition risk comes from Stave. The full canonical comparison with every section above expanded is at aws-compliance-mod. The Capital One framing is the canonical wedge. The rest is just what falls out of using the right tool for each question.
Top comments (0)