Skip to content

kaveo · Guides

Compliance and reporting

Map kaveo findings onto 10 frameworks, see pass and fail status per control, record waivers and export CSV, JSON, evidence worksheets, PDF or shareable reports.

On this page

kaveo rolls each scan's findings up onto the controls of 10 frameworks. Every failing control lists the findings behind it, with the evidence-ledger observation ids each finding cites.

Frameworks#

Frameworks render in this order. Use the key when you create a waiver.

FrameworkKeyVersion
CIS AWS Foundations BenchmarkCIS_AWS3.0.0 (default); 1.4.0 and 5.0.0 selectable
AWS Foundational Security Best PracticesFSBP1.0.0
PCI DSSPCI_DSS4.0
SOC 2 Trust Services CriteriaSOC22017
OWASP Top 10OWASP_TOP_102021
HIPAA Security RuleHIPAA45 CFR §164
GDPRGDPRReg. (EU) 2016/679
CCPA/CPRACCPACal. Civ. Code §1798
ISO/IEC 27001ISO_270012022
NIST SP 800-53NIST_800_53rev5

Read a scan's report with GET /v1/scans/{scan_id}/compliance, which needs view_findings. The first nine frameworks always render. NIST SP 800-53 appears only when a finding maps to one of its controls. To pin a CIS version, pass the cis_version query parameter to this endpoint or to the export endpoint. The console uses the default. An unknown value falls back to 3.0.0, and 5.0.0 uses the same control list as 3.0.0.

The single Azure, GCP, Kubernetes and GitHub detectors each map to that platform's CIS benchmark, under the keys CIS_AZURE, CIS_GCP, CIS_K8S and CIS_GITHUB. These have no control catalog. Each appears after the ten above only when its finding fires, lists only that one control, and shows the version unversioned.

Detectors declare their CIS, PCI DSS, SOC 2 and NIST mappings, which are stored with each scan. FSBP, OWASP Top 10, HIPAA, GDPR, CCPA and ISO/IEC 27001 mappings come mainly from the compliance knowledge base, which maps each finding's rule id to controls when the report is assembled. Older scans therefore pick up knowledge-base changes without a re-scan.

How status is decided#

For each framework, the report lists the controls a kaveo detector covers, plus any other control a finding maps to.

StatusMeaning
failAt least one deterministic finding in the scan maps to the control.
passA detector covers the control, and no finding in the scan maps to it.
waivedThe control would fail, but an active waiver covers it.

Controls no detector covers are not listed, and kaveo makes no claim about them. The not_assessed count is reserved for that case and never enters the score.

A pass means nothing fired. It does not confirm that the scan collected the resources the control is about. Before you rely on one, check three things:

  • Suppression. Findings suppressed with KAVEO_DETECT_SUPPRESS_RULES or KAVEO_DETECT_MIN_SEVERITY are excluded. A control whose only findings are suppressed shows pass. For an auditable accept-risk record, use a waiver instead.
  • Coverage. A pass reflects only what the scan collected. If you limited the scan to some regions, check its coverage with GET /v1/scans/{scan_id}.
  • Provider. Every framework renders for every scan, whatever the account's provider, and nearly all controls are backed by AWS detectors. Azure, GCP, Kubernetes and GitHub accounts have one detector each today, so on those accounts almost every control shows pass because the AWS detectors behind it did not run. Treat the report as meaningful for AWS accounts only.

Breach cost, incidents and penalties#

The compliance knowledge base is curated reference data, not AI. It never creates a finding. It adds context to the verdicts:

  • Estimated breach cost. Every detector rule has a cost range with a stated cost_basis, anchored to IBM's Cost of a Data Breach 2025 report and a reference incident's documented outcome. A failing control shows its costliest finding's range. A framework's headline exposure is its worst failing control's range, not a sum. These are order-of-magnitude estimates for prioritization, not actuarial figures.
  • A reference incident. Each rule is paired with a public, documented incident or published research of the same misconfiguration class.
  • Penalty context. Each framework has a short note on what non-compliance costs under it, such as GDPR's fine ceilings.
  • AI impact analysis, on request. From a failing control in the console, you can ask for a tenant-specific narrative of one finding. This runs the compliance_impact stage through POST /v1/findings/{finding_id}/compliance-impact (add /stream for SSE) and needs the investigate action. Every claim must cite stored observations, and the output never changes a status. See Evidence and grounded AI.

Waivers#

A waiver records a time-boxed decision to accept a risk, with who approved it and why. It targets one of two things:

  • A control, by framework and control_id.
  • A finding class on a resource, by finding_rule and resource_ref (the resource's ARN, as shown in the control's evidence). A control is waived only when waivers cover every finding behind it.

A waiver applies to every scan in your organization, across all accounts. Prefer the finding-and-resource form when you accept one exception, because a control waiver hides every failure of that control until it expires. Every waiver needs an approver, a justification and a future expires_at:

json
{
  "finding_rule": "identity.access_key_rotation",
  "resource_ref": "arn:aws:iam::123456789012:user/legacy-integration",
  "approver": "security-lead",
  "justification": "Legacy integration key; replacement scheduled for Q1",
  "expires_at": "2027-03-31T00:00:00Z"
}

approver is free text. The audit log separately records the user who made the call. kaveo does not check that a framework key, control id, rule id or ARN exists, so copy them from the report.

MethodPathPurpose
POST/v1/compliance/waiversCreate. An expires_at that is not in the future returns 409.
GET/v1/compliance/waiversList active waivers.
DELETE/v1/compliance/waivers/{waiver_id}Revoke early. The row is kept with revoked_at set.

All three need approve_remediation, which only admins have, and creates and revokes are written to the audit log. Waivers are managed through the API only. The console shows which controls are waived but has no form to create or revoke a waiver. An expired waiver needs no clean-up: the control returns to fail the next time the report is assembled. The console marks a waived control whose waiver expires within 30 days.

The MCP compliance_status tool does not apply waivers, so it reports a waived control as failing.

Exports#

Export one scan's compliance report with GET /v1/scans/{scan_id}/compliance/export?format=csv|json|evidence|pdf. The console's Compliance page offers the same four formats.

FormatContents
csv (default)One row per control, with its status and finding ids. Failing controls add the cost range and reference incident.
jsonThe full report, including per-control evidence.
evidenceA CSV auditor worksheet with one row per control, finding and observation.
pdfA printable evidence pack covering every framework and control.

For a SIEM, GET /v1/findings/export?format=ocsf|asff|csv|json exports findings. The default is ocsf, and asff is the AWS Security Finding Format. You can filter with scan_id, severity and source. Suppressed findings are left out unless you pass include_suppressed=true.

For asff, account_id and region fill each finding's AwsAccountId and product ARN. Without them the export uses placeholders (000000000000 and us-east-1), so set both before you import into Security Hub.

Each export returns at most the 10,000 newest matches, and the CLI warns when it hits that cap:

bash
kaveo findings export --format ocsf --scan <scan-id> --out findings.json

To track scores over time:

  • GET /v1/scans/{scan_id}/compliance/trend returns the account's per-framework scores across its succeeded scans.
  • GET /v1/compliance/rollup returns the scores from each account's latest succeeded scan. In the console, open Org rollup from the Compliance page.

Both read scores recorded when each scan finished. A waiver or knowledge-base change made later shows in the live report but does not rewrite those recorded points.

A score is round(100 * (passing + waived) / (passing + failing + waived)), so waived controls count as satisfied. A framework with no assessed controls scores 100.

Risk Report and sharing#

The Risk Report (console /report, or GET /v1/scans/{scan_id}/report) shows a scan's severity totals and top risks, which findings sit on an internet-exposed resource or a toxic attack path, and the severity trend. By default (ai=true), the grounded prioritize and explain stages enrich the top findings, 5 unless you set top (up to 50). In the console, By risk / effort sorts high-risk, low-effort fixes first.

KAVEO_REPORT_RELEVANCE_MODE sets what the report brings forward:

ModeThe report shows
real_risk (default)Everything except 10 built-in hygiene rules, such as dormant users and key rotation. A hygiene finding still shows if its resource is internet-exposed or on a toxic path.
strictOnly findings on exploitable resources, plus the exposure. and secrets. rule classes.
allEvery non-suppressed finding.

KAVEO_REPORT_HYGIENE_RULES replaces the hygiene set with a comma-separated list of rule ids. The gate changes only the report: hidden findings appear as counts, and /v1/findings and compliance still include them.

Share links. POST /v1/scans/{scan_id}/shares creates an anonymous, read-only link to the Risk Report. The token is returned once and stored only as a hash. Links expire after KAVEO_SHARE_TTL_DAYS days (default 30). List them with GET /v1/scans/{scan_id}/shares, and revoke one with DELETE /v1/shares/{share_id}. These calls need share_report, which analysts and admins have. The shared view never runs an AI stage and never shows account details.

Email. POST /v1/reports/{scan_id}/email sends the Risk Report as an email with a PDF attached, and needs view_findings. The body takes email, scan_id and an optional note; the scan_id in the path wins. The call returns 202 even if delivery fails, and a failure is logged.

The default KAVEO_MAILER=console only writes the message to the api log. To send real mail, set KAVEO_MAILER to smtp (with KAVEO_SMTP_HOST) or ses, and set KAVEO_MAIL_FROM. If you hold share_report and KAVEO_PUBLIC_WEB_URL is set, each email also mints a new share link with the same expiry rules. The stock compose file passes none of these settings, including KAVEO_PUBLIC_WEB_URL, so add them with a compose override. See Configuration.