Finding Schema
Every engine on the platform — CSPM checks, CIEM, vulnerability, data security, CDR, and the rest of the 29 engines — emits findings normalized to one shared data model. This reference documents that model: the base finding item, the detail response with its compliance and remediation blocks, the list envelopes, the status lifecycle, and the severity scale. If you consume findings through the REST API or a webhook integration, this is the contract.

Where findings come from
Engines write findings into tenant-scoped engine databases during each scan run. The BFF gateway serves them through two shapes: compact list rows for tables (GET /api/v1/<engine>/findings) and a full detail document for the finding page (GET /api/v1/views/finding/{engine}/{id}).
Field naming is cloud-neutral by design, so one integration works across all 7 providers:
| Canonical field | Instead of |
|---|---|
resource_uid | ARN, instance ID, compartment OCID |
account_id | Subscription ID, project ID |
region | Availability zone |
provider | One of aws, azure, gcp, oci, alicloud, ibm, k8s |
provider_metadata | Opaque dict for CSP-specific fields that don't normalize |
BaseFindingItem
BaseFindingItem is the required base for every per-finding list item, regardless of engine.
| Field | Type | Notes |
|---|---|---|
finding_id | string | Stable identifier, unique per engine |
resource_uid | string | Cloud-neutral resource identifier |
resource_type | string | e.g. aws_s3_bucket, azure_storage_account |
provider | enum | aws, azure, gcp, oci, alicloud, ibm, k8s |
region | string | Region the resource lives in |
severity | enum | critical, high, medium, low, info |
status | string | Lifecycle status — see below |
account_id | string, optional | Cloud account, subscription, or project |
scan_run_id | string, optional | The scan run that produced or last confirmed the finding |
first_seen_at | datetime, optional | First scan run that raised the finding |
last_seen_at | datetime, optional | Most recent scan run that still observed it |
provider_metadata | object | CSP-specific extras that don't normalize across clouds |
Credential fields never reach the API.credential_refandcredential_typeare declared on the model but excluded from serialization, and a validator rejects any payload containing credential- or secret-material keys anywhere in its nested structure. This is enforced in the schema layer as defense in depth — not just in the UI.
Example finding
{
"finding_id": "chk-4f9a2c31",
"resource_uid": "arn:aws:s3:::billing-exports",
"resource_type": "aws_s3_bucket",
"provider": "aws",
"region": "us-east-1",
"severity": "critical",
"status": "OPEN",
"account_id": "123456789012",
"scan_run_id": "550e8400-e29b-41d4-a716-446655440000",
"first_seen_at": "2026-06-28T04:12:09Z",
"last_seen_at": "2026-07-19T04:10:44Z",
"provider_metadata": {
"bucket_policy_is_public": true,
"block_public_access_enabled": false
}
}Severity levels
Five severity levels apply across all engines. Severity is assigned by the rule (for configuration findings) or computed (for vulnerabilities, where EPSS and CISA KEV feed prioritization).
| Severity | Meaning | Recommended remediation window |
|---|---|---|
| Critical | Exploitable exposure with severe impact — public data, admin-level privilege escalation, actively exploited CVEs | 7 days |
| High | Significant weakness likely to be exploited or with broad blast radius | 30 days |
| Medium | Weakness requiring specific preconditions, or partial control failure | 90 days |
| Low | Hardening gap with limited direct impact | Next maintenance cycle |
| Info | Observation, inventory fact, or best-practice note — no action required | — |
Status lifecycle
Finding status is mutable through the console and the API. New findings open as OPEN; every transition is audit-logged.
| Status | Meaning | Set by |
|---|---|---|
OPEN | Detected and unresolved — the default for new or regressed findings | Automatic |
IN_PROGRESS | Assigned and being remediated | Manual |
RESOLVED | Fixed — a subsequent scan verifying the fix also resolves it automatically | Manual or automatic on rescan |
SUPPRESSED | Accepted risk — hidden from default views, kept for audit | Manual, note recommended |
FALSE_POSITIVE | Judged not a real issue — feeds rule tuning | Manual, note recommended |
Status is updated with a StatusUpdateRequest:
PATCH /api/v1/views/finding/{engine}/{id}/status
{
"status": "SUPPRESSED",
"note": "Public access is intentional - static website bucket, exception EX-142."
}| Field | Type | Notes |
|---|---|---|
status | enum | OPEN, IN_PROGRESS, RESOLVED, SUPPRESSED, FALSE_POSITIVE |
note | string, optional | Free-text justification, stored in the audit log |
If a resource regresses after beingRESOLVED— the misconfiguration comes back — the next scan reopens the finding asOPENand preserves the originalfirst_seen_at, so your mean-time-to-remediate metrics stay honest.
FindingDetailResponse
GET /api/v1/views/finding/{engine}/{id} returns the full detail document that powers the finding page's tabs. The engine path segment is one of: check, threat, iam, network-security, datasec, encryption, container-security, dbsec, ai-security, cdr, secops, attack-path, api-security.
| Field | Type | Notes |
|---|---|---|
finding | FindingHeader | The full header — also aliased as header |
relatedFindings | RelatedFindingsBlock | Cross-engine findings on the same resource |
compliance | ComplianceBlock | Framework control mappings for the rule |
remediation | RemediationBlock | Ordered fix steps and references |
resourceContext | object, optional | Null here — the UI calls /api/v1/asset-context/{resource_uid} separately |
engineExtensions | object | Engine-specific extra tab payloads |
degradedEngines | string list | Engines that timed out during the related-findings fan-out |
restrictedEngines | string list | Engines your role may not read (viewer role restrictions) |
FindingHeader
The header carries identity, rule metadata, taxonomy, and framework mappings. Detail responses use camelCase field names. Key fields:
| Field | Type | Notes |
|---|---|---|
findingId, engine, ruleId | string | Identity — which rule fired, in which engine |
title, description, rationale | string | Human-readable summary and why it matters |
severity, status, riskScore | string, string, int | Triage fields; riskScore is 0–100 |
resourceUid, resourceType, resourceName | string | The affected resource |
provider, accountId, region | string | Where it lives |
firstSeenAt, lastSeenAt | datetime | Lifecycle timestamps |
module, module_label, subcategory, subcategory_label | string | Taxonomy codes and display labels for filter grouping |
ruleType | string | config, cdr, or threat |
mitreTactics, mitreTechniques | string list | MITRE ATT&CK mappings, when threat-relevant |
cisBenchmark, cisSection, nistControls, soc2Criteria | string / list | Direct framework references on the rule |
ticketId, ticketUrl | string | Linked Jira or ServiceNow ticket, if one exists |
standardColumns | StandardColumns | The mandatory 14 columns echoed from every finding table |
findingData | object | Engine-specific evidence payload |
ComplianceBlock
Maps the finding's rule to compliance controls across the 70+ supported frameworks.
| Field | Type | Notes |
|---|---|---|
available | boolean | False when the engine has no rule-to-control mapping |
controlMappings | list | Items of { framework, controlId, controlName, status } |
RemediationBlock
| Field | Type | Notes |
|---|---|---|
available | boolean | False when no guidance exists for the rule |
steps | RemediationStep list | Ordered steps — { order, action, detail } |
referenceItems | list | { url, title } links to vendor documentation |
estimatedEffort | string, optional | e.g. "15 minutes" |
slaPriority | string, optional | Suggested remediation window from the severity table above |
guidance, markdown | string, optional | Long-form guidance |
runbook_url | string, optional | Link to an internal runbook, if configured |
RelatedFindingsBlock
Cross-engine findings on the same resource_uid, fetched with a bounded per-engine fan-out.
| Field | Type | Notes |
|---|---|---|
available | boolean | Whether the fan-out ran |
items | list | { engine, findingId, severity, ruleId, title, status } |
perEngineAvailability | object | Which engines answered within the time budget |
restrictedEngines | string list | Engines omitted because your role lacks <engine>:read |
List envelopes
Three container shapes wrap findings in list and aggregate responses.
PaginatedList — every scrollable table:
| Field | Type | Notes |
|---|---|---|
items | list | Page of finding items |
total | int | Total matching rows |
page, page_size | int | Defaults 1 and 50; page_size max 200 |
has_more | boolean | True when further pages exist |
SeverityCounts — KPI breakdowns:
| Field | Type | Notes |
|---|---|---|
critical, high, medium, low, info | int | Counts per severity |
by_provider | object | Per-provider SeverityCounts — populated only on multi-cloud aggregation pages (dashboard, compliance, risk) |
ScanRunRef — lightweight scan-run metadata attached to results:
| Field | Type | Notes |
|---|---|---|
scan_run_id | string | The run identifier |
provider, account_id | string, optional | Which account the run covered |
completed_at | datetime, optional | Completion time |
total_findings | int, optional | Findings produced by the run |
Next steps
- API Reference — authentication, pagination, and the endpoint map these schemas travel through
- CSPM — how the Check engine evaluates 10,000+ rules to produce configuration findings
- Integration Catalog — push these finding payloads to Jira, Slack, Splunk, or any webhook
- Data Retention — how long findings are stored and when they are deleted