-
Notifications
You must be signed in to change notification settings - Fork 1
OCSF Mapping
Auto-generated mirror. This page mirrors
docs/ocsf-mapping.md, the canonical source of truth. Do not edit this file directly; editdocs/ocsf-mapping.mdand re-runscripts/wiki/sync_mirrors.py.
Status: NORMATIVE as of v0.10.0 (2026-05-22). Documents the bidirectional mapping between Evidentia's native
SecurityFindingmodel and the OCSF Compliance Finding class.Canonical location:
docs/ocsf-mapping.mdCross-references: v0.10.0-plan.md, integration-survey.md, api-stability.md
The Open Cybersecurity Schema Framework is the emerging vendor-neutral schema for security findings. Mapping Evidentia findings to and from OCSF makes downstream integrations cheap rather than bespoke: any OCSF-aware consumer (SIEMs, AWS Security Lake, Datadog, RegScale ingestion) can read Evidentia output through one adapter, and — once the v0.10.1 ingestion collector lands — any OCSF-emitting tool can be read into Evidentia the same way.
Evidentia keeps its own Pydantic finding model. OCSF is an
interchange format at the boundary, not the internal representation.
The native model retains Evidentia's enrichment over OCSF — OLIR-typed
control mappings and CollectionContext provenance — which the OCSF
compliance object cannot natively express.
| Item | Value |
|---|---|
| OCSF class | Compliance Finding |
class_uid |
2003 |
category_uid |
2 (Findings) |
| OCSF schema (current) | 1.8.0 (released 2026-03-18) |
py-ocsf-models version pinned |
>=0.9.0,<0.10.0 |
| OCSF schema modelled by that library | 1.5.0 |
The Compliance Finding class is stable across OCSF 1.1+, so the
1.5.0-vs-1.8.0 gap is a version-label detail, not a functional one.
The generic security_finding class was deprecated in OCSF 1.1;
Compliance Finding is the semantically correct class for
control-pass/fail findings.
Note on Prowler / AWS Security Hub. Prowler emits the OCSF
Detection Finding class, not Compliance Finding. The v0.10.0
finding_from_ocsf direction targets Compliance Finding; the deferred
v0.10.1 OCSF-ingestion collector adds a Detection Finding path.
The OCSF representation comes from
py-ocsf-models
(Apache-2.0, Pydantic, maintained by the Prowler team). It is an
optional dependency, installed via the ocsf extra:
pip install 'evidentia-core[ocsf]'evidentia_core.ocsf.finding_mapping is the only module that
imports it. The core evidentia_core.models.finding model never does,
so:
- the default install stays slim (
py-ocsf-modelspulls incryptographyand other transitive dependencies); - the core
SecurityFindingmodel is insulated from OCSF schema churn — only the mapping layer moves on an OCSF version bump.
Calling finding_to_ocsf / finding_from_ocsf without the extra
installed raises OCSFMappingError with an actionable install hint.
finding_to_ocsf(finding) produces a JSON-ready dict conforming to
the OCSF Compliance Finding class.
SecurityFinding field |
OCSF Compliance Finding location |
|---|---|
id |
finding_info.uid |
title |
finding_info.title |
description |
finding_info.desc, compliance.desc, message
|
severity |
severity_id (+ severity string) |
status (FindingStatus) |
status_id |
compliance_status (ComplianceStatus) |
compliance.status_id |
remediation |
remediation.desc (omitted when None) |
source_system |
finding_info.data_sources[] |
resource_type |
resources[].type |
resource_id |
resources[].uid |
resource_region |
resources[].region |
control_mappings[].framework |
compliance.standards[] (deduplicated, sorted) |
control_mappings[].control_id |
compliance.requirements[] |
first_observed |
finding_info.first_seen_time_dt, time, time_dt
|
last_observed |
finding_info.last_seen_time_dt |
| (whole finding) |
unmapped["evidentia"] — see §6 |
OCSF envelope fields (class_uid, category_uid, class_name,
category_name, activity_id, type_uid, metadata.product) are
computed by the mapping layer, not stored on SecurityFinding.
metadata.product is fixed to Evidentia (name, vendor_name
"Polycentric Labs", version).
Fields with no native OCSF home — source_finding_id,
resource_account, resolved_at, collection_context, raw_data,
and the OLIR relationship + justification on each control mapping —
are not lost: they ride in the unmapped["evidentia"] block (§6).
Severity → OCSF severity_id:
Severity |
severity_id |
|---|---|
CRITICAL |
5 |
HIGH |
4 |
MEDIUM |
3 |
LOW |
2 |
INFORMATIONAL |
1 |
Inbound OCSF severity_id values with no Evidentia equivalent
(0 Unknown, 6 Fatal, 99 Other) fall back to MEDIUM.
ComplianceStatus → OCSF compliance.status_id:
ComplianceStatus |
status_id |
OCSF name |
|---|---|---|
PASS |
1 | Pass |
WARNING |
2 | Warning |
FAIL |
3 | Fail |
UNKNOWN |
0 | Unknown |
NOT_APPLICABLE |
99 | Other |
OCSF has no "not applicable" compliance value, so NOT_APPLICABLE
maps to Other (99). The exact Evidentia value still round-trips
losslessly via the unmapped block.
FindingStatus → OCSF status_id:
FindingStatus |
status_id |
OCSF name |
|---|---|---|
ACTIVE |
1 | New |
RESOLVED |
4 | Resolved |
SUPPRESSED |
3 | Suppressed |
finding_from_ocsf(ocsf_dict, *, trust_unmapped=True) is the inverse.
It first validates the input as an OCSF Compliance Finding
(OCSFMappingError on failure), then takes one of two paths:
-
Evidentia-produced input (
trust_unmapped=True, default) — thedictcarries anunmapped["evidentia"]block; the originalSecurityFindingis reconstructed exactly from it (see §6). -
Third-party input — no
unmapped["evidentia"]block ORtrust_unmapped=False. The finding is rebuilt best-effort from native OCSF fields:finding_info→ id/title/description,severity_id→ severity,compliance.status_id→ compliance_status,compliance.standards+compliance.requirements→ control mappings (with anOLIRRelationship.RELATED_TOdefault and a justification noting the OCSF provenance),remediation.desc→ remediation.
A valid-but-malicious OCSF producer could craft an
unmapped["evidentia"] block to control the reconstructed
SecurityFinding — id / title / source_system / control_mappings all
attacker-chosen. Pydantic still re-validates the model so corrupted
blocks fail safely, but the residual identity / attribution-forgery
risk is real (CWE-345 Insufficient Verification of Data
Authenticity, proxy).
The v0.10.1 OCSF-ingestion collector passes trust_unmapped=False
when reading third-party input whose origin it does not verify; the
block is then ignored entirely and the finding is rebuilt from
native OCSF fields only. Operators integrating
finding_from_ocsf into their own ingestion pipelines should adopt
the same pattern unless they verify the OCSF doc's origin
cryptographically (Sigstore bundle, signed envelope, trusted SaaS
source allowlist).
This parameter closes pre-release-review finding F-V100-L1 identified during the v0.10.0 ship.
The mapping guarantees:
finding_from_ocsf(finding_to_ocsf(f)) == fholds exactly for any Evidentia-produced finding.
OCSF's compliance object cannot express Evidentia's OLIR-typed
control mappings (relationship + justification) or its
CollectionContext provenance. Rather than drop them, finding_to_ocsf
stashes the complete finding — finding.model_dump(mode="json") —
under the OCSF-standard unmapped field, namespaced as
unmapped["evidentia"]. finding_from_ocsf restores from that block
when present.
This is a deliberate design choice: the native OCSF fields make the
finding readable by any OCSF consumer, while the unmapped block
makes the round trip lossless for Evidentia-to-Evidentia flows. A
third-party OCSF consumer simply ignores unmapped["evidentia"].
from evidentia_core.ocsf import (
finding_to_ocsf, # SecurityFinding -> OCSF Compliance Finding dict
finding_from_ocsf, # OCSF Compliance Finding dict -> SecurityFinding
finding_from_ocsf_detection, # v0.10.1: OCSF Detection Finding dict -> SecurityFinding
OCSFMappingError, # raised when the `ocsf` extra is absent or input is invalid
)All three mapping functions are pure — no I/O, no global state.
finding_to_ocsf returns a JSON-ready dict (serialize however);
finding_from_ocsf and finding_from_ocsf_detection accept dicts
parsed from JSON.
v0.10.1 also ships an ingestion collector that wraps these for file + URL input:
from evidentia_collectors.ocsf import (
collect_ocsf_file, # path-or-Path -> list[SecurityFinding]
collect_ocsf_url, # https:// URL -> list[SecurityFinding]
OCSFIngestError, # malformed JSON / unsupported class_uid / URL policy
)Or use the CLI: evidentia collect ocsf --input <file-or-url> [--output findings.json]. URL mode is HTTPS-only with no redirects,
a 10s connect/read timeout, and a 50 MB body cap — see the
collector module's docstring for the rationale.
OCSF Detection Finding (class_uid 2004) is what Prowler and
AWS Security Hub emit — not Compliance Finding. The
finding_from_ocsf_detection(ocsf_dict, *, trust_unmapped=False)
function handles it.
The key delta vs. Compliance Finding: Detection Finding has no
native compliance object, so compliance_status and
control_mappings cannot be read from the OCSF doc directly.
The conversion:
SecurityFinding field |
Source on Detection Finding |
|---|---|
id |
finding_info.uid |
title |
finding_info.title |
description |
finding_info.desc ∥ message ∥ finding_info.title
|
severity |
severity_id (same mapping as Compliance Finding) |
status |
status_id (same FindingStatus mapping) |
compliance_status |
synthesized from severity_id — see table below |
remediation |
remediation.desc |
source_system |
metadata.product.name (fallback "ocsf-detection-import") |
resource_type / _id / _region
|
resources[0] |
first_observed / last_observed
|
finding_info.first_seen_time_dt / last_seen_time_dt
|
control_mappings |
empty — downstream collectors enrich based on detector-ruleset knowledge |
Synthesized compliance_status from severity_id (the v0.10.1
heuristic in _DETECTION_SEVERITY_TO_COMPLIANCE):
severity_id |
OCSF name | → compliance_status
|
|---|---|---|
| 5 | Critical | FAIL |
| 4 | High | FAIL |
| 3 | Medium | FAIL |
| 2 | Low | WARNING |
| 1 | Informational | UNKNOWN |
| 0 | Unknown | UNKNOWN |
| 6 | Fatal | FAIL |
| 99 | Other | UNKNOWN |
Rationale: a detection finding represents an observed problem, so
non-informational severities map to a non-passing compliance state.
INFORMATIONAL is treated as UNKNOWN because the source is publishing
context, not a check result.
Default trust_unmapped=False for Detection Finding — the typical
input source (Prowler, AWS Security Hub) is third-party and not
Evidentia-produced. Operators who produce their own Detection Finding
round-trip artifacts can flip to True for lossless reconstruction.
v0.10.5 Phase 7 adds the SIEM-target emit counterpart to v0.10.4's
--format ocsf (Compliance Finding 2003). The Detection Finding emit
renders each ControlGap as an OCSF Detection Finding (class_uid
2004) so gap output flows natively into SIEMs whose production-traffic
schema is 2004 — Splunk, Elastic, Microsoft Sentinel, Datadog.
Why two OCSF emits? Compliance Finding 2003 is the semantically
correct class for control pass/fail evidence and remains Evidentia's
default OCSF emit for GRC-target tooling (Security Lake, RegScale,
OCSF-aware GRC platforms). Detection Finding 2004 is the OCSF class
SIEMs have actually wired their ingest pipelines to — Prowler and AWS
Security Hub emit 2004 natively, so SIEMs landed there as well.
Operators who need SIEM ingest pick --format ocsf-detection;
operators who need OCSF-aware GRC tooling stay on --format ocsf.
Both emits carry the same gap data with the same severity mappings —
the only structural difference is the OCSF class.
ControlGap → Detection Finding field map (gap_report_to_ocsf_detection_array):
ControlGap field |
Detection Finding location |
|---|---|
id |
finding_info.uid |
framework + control_id
|
finding_info.types[] (as "<framework>/<control_id>") |
control_title |
embedded in finding_info.title
|
gap_description |
finding_info.desc, message
|
gap_severity |
severity_id (same mapping as Compliance Finding) |
status (GapStatus) |
status_id (OPEN/IN_PROGRESS → New (1); REMEDIATED → Resolved (4); ACCEPTED → Suppressed (3); NOT_APPLICABLE → Other (99)) |
remediation_guidance |
remediation.desc (omitted when empty) |
created_at |
finding_info.first_seen_time_dt, last_seen_time_dt, time, time_dt
|
| (whole gap) |
unmapped["evidentia"]["gap"] — round-trip fidelity |
Why finding_info.types[] for the framework + control id? Detection
Finding has no native compliance object, so framework + control_id
has no native home on the class. The SARIF-style stable identifier
<framework>/<control_id> (which Evidentia's SARIF emit already uses
as rule_id) is the closest semantic match for OCSF's types[] list,
which describes the kind of finding. SIEM filters that key off finding
type therefore see a stable per-control identifier even when the
unmapped block is stripped on ingest.
No compliance object → unmapped block is canonical for status.
Detection Finding's lack of compliance.status_id means the gap's
implementation_status (missing / partial / planned /
implemented / not_applicable) round-trips only via the
unmapped["evidentia"]["gap"] block. SIEM operators who care about the
compliance binding read it from the unmapped block; SIEM operators who
just want findings see the gap description in the standard message
and finding_info.desc fields.
API:
from evidentia_core.gap_analyzer.ocsf_detection import (
gap_report_to_ocsf_detection_array,
)
detection_array = gap_report_to_ocsf_detection_array(report)
# returns list[dict[str, Any]] — each dict is a valid OCSF Detection FindingOr use the CLI: evidentia gap analyze --inventory inv.yaml --frameworks nist-csf-2.0 --output gaps.ocsf-detection.json --format ocsf-detection.
Both gap_report_to_ocsf_array (Compliance Finding) and
gap_report_to_ocsf_detection_array (Detection Finding) require the
[ocsf] extra (pip install 'evidentia-core[ocsf]'); both raise
OCSFMappingError otherwise.
- The third-party OCSF-ingestion collector that wires
finding_from_ocsfto a file/API reader (Prowler, AWS Security Hub). Requires a Detection Finding path — that is the class Prowler emits. - Migrating the remaining ~11 collectors to populate
compliance_statusandremediationexplicitly (the 3 pilot collectors — AWS, GitHub, Postgres — do so in v0.10.0). - An optional
evidentia collect --format ocsfoutput mode.
-
- AI Governance
- Air Gapped Install
- Ci Integration
- CONMON Deployment
- Emit Cyclonedx VEX
- Emit OCSF Detection
- Emit SARIF
- Explain Controls
- Generate And Quantify Risk
- Governance Metrics And Workflows
- Ingest OCSF
- Manage Model Risk
- Manage POAM
- Manage Third Party Risk
- MCP Client Setup
- OSPS Self Assessment
- Run Gap Analysis
- Serve The Web Ui
- Sign And Verify Evidence