56 KiB
name, description, license, metadata, allowed-tools
| name | description | license | metadata | allowed-tools | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| prowler-compliance | Creates, syncs, audits and manages Prowler compliance frameworks end-to-end. Covers the two supported JSON schemas (universal multi-provider and legacy per-provider), the SDK model tree (legacy attribute classes, universal ComplianceFramework, ConfigRequirements guardrails), output formatters (legacy per-framework + universal data-driven), API/UI consumption, upstream sync workflows, and cloud-auditor check-mapping reviews. Trigger: When working with compliance frameworks (CIS, CIS Controls, NIST, PCI-DSS, SOC2, GDPR, ISO27001, ENS, MITRE ATT&CK, CCC, C5, CSA CCM, DORA, KISA ISMS-P, ASD Essential Eight, DISA STIG, CISA SCuBA, SecNumCloud, FedRAMP, HIPAA, NIS2, Prowler ThreatScore), creating a universal multi-provider framework, adding ConfigRequirements guardrails, syncing with upstream catalogs, auditing check-to-requirement mappings, adding output formatters, or fixing compliance JSON bugs (duplicate IDs, empty Version, wrong Section, stale check refs). | Apache-2.0 |
|
Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task |
When to Use
Use this skill when:
- Creating a new compliance framework for any provider — decide universal vs legacy first (see below)
- Syncing an existing framework with an upstream source of truth (CIS, FINOS CCC, CSA CCM, NIST, ENS, etc.)
- Adding requirements to existing frameworks, or extending a universal framework to a new provider
- Mapping checks to compliance controls
- Adding
ConfigRequirementsguardrails so configurable checks can't silently satisfy a requirement with a loosened config - Auditing existing check mappings as a cloud auditor ("are these mappings correct?", "which checks apply?", "review the mappings")
- Adding a new legacy output formatter (table dispatcher + per-provider classes + CSV models)
- Fixing JSON bugs: duplicate IDs, empty Version, wrong Section, stale check refs, inconsistent FamilyName, padded tangential check mappings
- Investigating why a finding/check isn't showing under the expected compliance framework in the UI
- Understanding compliance framework structures and attributes
The authoritative contributor doc is docs/developer-guide/security-compliance-framework.mdx —
keep this skill and that doc consistent when either changes. For reviewing
a compliance PR, use the sister skill
prowler-compliance-review instead.
Universal vs Legacy: The First Decision
Prowler supports two JSON schemas. Choosing wrong means unnecessary Python
code, so decide this before anything else. At load time both converge: legacy
files are adapted into the universal ComplianceFramework model
(adapt_legacy_to_universal()), so the difference is about authoring cost
and capabilities, not about what the rest of Prowler sees.
Side-by-side comparison
| Universal (recommended for new frameworks) | Legacy provider-specific | |
|---|---|---|
| File location | prowler/compliance/<framework>.json (top level) |
prowler/compliance/<provider>/<framework>_<version>_<provider>.json |
| Providers | Any number, one file (checks dict keyed by provider) |
Exactly one provider per file (one file per provider to multi-cover) |
| Key style | lowercase (framework, requirements, checks) |
Capitalized (Framework, Requirements, Checks) |
| Attribute schema | Declared in the JSON itself via attributes_metadata, validated at load |
Pydantic class per framework family in compliance_models.py (code change for new shapes) |
| Attributes per requirement | One flat dict (attributes: {...}) |
List of objects (Attributes: [{...}]) — only Attributes[0] is used downstream |
| Table/CSV/OCSF output | Data-driven from outputs.table_config — zero Python changes |
Formatter package + registrations in compliance.py, __main__.py, export.py |
| Guardrails field | config_requirements (+ mandatory Provider per constraint) |
ConfigRequirements (Provider omitted) |
| Loader behavior on error | Lenient: logs + skips file (load_compliance_framework_universal) |
Fail-fast: sys.exit(1) (load_compliance_framework) |
| Loaded by | Only get_bulk_compliance_frameworks_universal() |
Both loaders (Compliance.get_bulk() + universal, via adapter) |
| Shipped examples | cis_controls_8.1.json, csa_ccm_4.0.json, dora_2022_2554.json |
Everything else (~105 files across 11 providers) |
When to use which
Use universal when (any of these):
- The framework is new to Prowler — no existing attribute class, no existing formatter. This is the default: zero Python changes needed.
- The framework spans (or will span) more than one provider — DORA, CSA
CCM, CIS Controls. One file covers all providers; extending to a new
provider is a one-line
checksedit. - The attribute shape is unique to this framework — declare it in
attributes_metadatainstead of adding a Pydantic class to the Union.
Use legacy only when extending an existing legacy family:
- A new version of a shipped legacy framework (CIS 8.0 for AWS → new
cis_8.0_aws.json, sameCIS_Requirement_Attribute, samecis/formatter). - An existing legacy framework for a new provider (ENS for m365 → new
ens_rd2022_m365.json+ens_m365.pytransformer). - Consistency with the family matters more than the universal benefits — a
lone
cis_8.0_awsin universal format while 20+ CIS files stay legacy would fragment the family.
Never: start a brand-new single-provider framework as legacy "because it's
only AWS today". Universal handles single-provider fine (the checks dict
just has one key) and you skip 3 output files + 3 registrations.
The same requirement in both schemas
Universal (prowler/compliance/my_framework_1.0.json):
{
"framework": "My-Framework",
"name": "My Framework 1.0",
"version": "1.0",
"description": "...",
"attributes_metadata": [
{"key": "Section", "type": "str", "required": true},
{"key": "Service", "type": "str"}
],
"outputs": {"table_config": {"group_by": "Section"}},
"requirements": [
{
"id": "MF-1.1",
"name": "Root MFA",
"description": "Root account must have MFA enabled.",
"attributes": {"Section": "IAM", "Service": "iam"},
"checks": {
"aws": ["iam_root_mfa_enabled"],
"azure": []
}
}
]
}
Legacy (prowler/compliance/aws/my_framework_1.0_aws.json — plus a second
file per extra provider, plus formatter + registrations):
{
"Framework": "My-Framework",
"Name": "My Framework 1.0 for AWS",
"Version": "1.0",
"Provider": "AWS",
"Description": "...",
"Requirements": [
{
"Id": "MF-1.1",
"Name": "Root MFA",
"Description": "Root account must have MFA enabled.",
"Attributes": [
{"ItemId": "MF-1.1", "Section": "IAM", "Service": "iam"}
],
"Checks": ["iam_root_mfa_enabled"]
}
]
}
Same control, but the universal file already covers Azure, validates its own attribute schema, and renders table/CSV/OCSF with no code. Field-by-field references for each schema follow below.
Architecture (Mental Model)
Prowler compliance is a four-layer system. Bugs usually happen where one layer doesn't match another, so know all four before touching anything.
Layer 1: SDK / Core Models — prowler/lib/check/
All in Pydantic v1 (from pydantic.v1 import ...). Three model groups live
in compliance_models.py:
Legacy tree — Compliance → Compliance_Requirement / Mitre_Requirement:
- One
*_Requirement_Attributeclass per framework family. Registered today (Union order matters):ASDEssentialEight,CIS,ENS,ISO27001_2013,AWS_Well_Architected,KISA_ISMSP,Prowler_ThreatScore,CCC,C5Germany,CSA_CCM,STIG(Okta IDaaS), andGeneric_Compliance_Requirement_Attributeas fallback. - Generic MUST stay LAST in
Compliance_Requirement.Attributes: list[Union[...]]— Pydantic v1 tries union members in order; Generic first would swallow every framework-specific attribute. NIST 800-53/CSF, PCI DSS, GDPR, HIPAA, SOC2, FedRAMP, SecNumCloud etc. intentionally use Generic. - A
root_validatorrejects emptyFramework,ProviderorName. - MITRE uses the separate
Mitre_Requirementmodel (Tactics,SubTechniques,Platforms,TechniqueURLat requirement top level, per-providerMitre_Requirement_Attribute_{AWS,Azure,GCP}).
Universal tree — ComplianceFramework → UniversalComplianceRequirement:
- Flat
attributes: dictper requirement, schema declared inattributes_metadata(key, label, type, enum, required,enum_display,enum_order,output_formats). Aroot_validatorrejects missing required keys, unknown keys (drift guard), enum violations, and int/float/bool type mismatches. Ifattributes_metadatais omitted, no validation runs. checks: dict[provider, list[check_id]]— the provider list of the framework is derived from these keys (get_providers()/supports_provider()); the top-levelproviderfield is only a fallback.outputs.table_config(group_by, split_by, scoring, labels) drives the CLI table;outputs.pdf_configexists in the model but is not consumed by the API PDF pipeline yet (see Layer 4).
Guardrails — Compliance_Requirement_ConfigConstraint:
- Fields
Check,ConfigKey,Operator(lte|gte|eq|in|subset|superset),Value, optionalProvider(required in universal multi-provider files). - A
root_validatorrejects Value/Operator type mismatches at load time. - Evaluation is centralized in
prowler/lib/check/compliance_config_eval.py(evaluate_config_constraints,apply_config_status,get_effective_status,CONFIG_NOT_VALID_PREFIX = "Configuration not valid for this requirement."), shared by CSV/OCSF/table outputs and the API backend. A violated constraint forces the requirement to FAIL and prepends the reason tostatus_extended. Constraints whoseConfigKeyis absent fromaudit_configare skipped (defaults assumed compliant).
Loaders:
Compliance.get_bulk(provider)— legacy: scans onlyprowler/compliance/{provider}/(+ external JSONs via theprowler.complianceentry-point group). Does NOT see top-level universal files.get_bulk_compliance_frameworks_universal(provider)— scans both the top-levelprowler/compliance/and every provider subdirectory, adapting legacy files viaadapt_legacy_to_universal()(flattensAttributes[0]to a dict, wrapsChecksas{provider: [...]}, infersattributes_metadata). Also loads external universal frameworks via theprowler.compliance.universalentry-point group (built-ins win collisions).get_check_compliance(finding, provider_type, bulk_checks_metadata)lives inprowler/lib/outputs/compliance/compliance_check.py(not inlib/check/compliance.py). It builds the per-finding dict keyedf"{Framework}-{Version}"only when Version is non-empty — an empty Version silently produces the key"{Framework}"and breaks downstream filters and tests.prowler/lib/check/compliance.pynow contains onlyupdate_checks_metadata_with_compliance().
Layer 2: JSON Catalogs — prowler/compliance/
See "Compliance Catalog Coverage" below.
Layer 3: Output Formatters — prowler/lib/outputs/compliance/
Universal path (no Python needed per framework):
universal/universal_table.py—get_universal_table(), renders the CLI table fromoutputs.table_config+attributes_metadata.universal/universal_output.py—UniversalComplianceOutput, builds the CSV Pydantic model dynamically fromattributes_metadata.universal/ocsf_compliance.py—OCSFComplianceOutput; OCSF output is always generated for universal frameworks regardless of--output-formats.- Orchestrated by
process_universal_compliance_frameworks()incompliance.py, which runs before any legacy dispatch and removes the processed frameworks from the set.
Legacy path — per-framework directory, usually:
{framework}/
├── __init__.py
├── {framework}.py # get_{framework}_table() summary-table function
├── {framework}_{provider}.py # One ComplianceOutput subclass per provider
└── models.py # One Pydantic CSV row model per provider
Directories today: asd_essential_eight, aws_well_architected, c5, ccc,
cis, cisa_scuba, ens, generic, iso27001, kisa_ismsp,
mitre_attack, okta_idaas_stig, prowler_threatscore, universal.
Known deviations (don't "fix" them without a reason): iso27001/ has no table
file (falls to the generic table), aws_well_architected/ has no per-provider
files, cisa_scuba/ only ships googleworkspace.
- CSV writers emit
;-delimited files with UPPERCASE headers (ComplianceOutput.batch_write_data_to_file). Field names inmodels.pyare public API — renaming breaks downstream consumers. - Circular import rule: the table file (
{framework}.py) must not importFindingdirectly or transitively (compliance.compliance→ table module →ComplianceOutput→Finding→get_check_compliance→ cycle). Keep table files bare (colorama,tabulate,prowler.config.config); when a module genuinely needs both, useif TYPE_CHECKING:or function-local imports (seeuniversal_output.py/process_universal_compliance_frameworks). - Legacy table functions have no docstrings; the universal ones do. Match the style of the file family you're touching.
- Dispatcher
display_compliance_table()incompliance.pyorder: universal (table_config) first →cis_→ens_→mitre_attack→kisa→prowler_threatscore_→c5_→ccc_→asd_essential_eight(substring) →okta_idaas_stig→ else provider hook (provider.display_compliance_table(), may raiseNotImplementedError) →get_generic_compliance_table(). iso27001, aws_well_architected and cisa_scuba ride the fallback on purpose.
Layer 4: API / UI
- API lazy loaders:
api/src/backend/api/compliance.py—LazyComplianceTemplate/LazyChecksMapping(per-provider lazy caches overget_bulk_compliance_frameworks_universal, with Gunicorn background warm-up). - API CSV export dispatch:
COMPLIANCE_CLASS_MAPinapi/src/backend/tasks/jobs/export.py, consumed fromtasks/tasks.py. It is a dictprovider → [(predicate, exporter_class)]withGenericComplianceas fallback. Predicates mixstartswithfor multi-version families (cis_,ens_,iso27001_,ccc_,cisa_scuba_, ...) and exactname == ...for true singletons (mitre_attack_aws,prowler_threatscore_*,asd_essential_eight_aws— and inconsistentlyc5_azure/c5_gcp, while aws usesstartswith("c5_")). Rule of thumb: if the framework can ever grow versions or variants, usestartswith. - API overview ingestion:
create_compliance_requirements()inapi/src/backend/tasks/jobs/scan.pybuilds per-region rows from the lazy template and persistsComplianceRequirementOverview(COPY with bulk-create fallback) plusComplianceOverviewSummary. - API PDF reports:
api/src/backend/tasks/jobs/reports/— hardcodedFRAMEWORK_REGISTRY(ownFrameworkConfigdataclass, NOT the SDKPDFConfig) with one generator class per framework. Onlyprowler_threatscore,ens,nis2,csa_ccmandcishave PDFs today; adding one means a generator class + registry entry + wiring inreport.py. - UI mapper routing:
ui/lib/compliance/compliance-mapper.ts—getComplianceMappers()keyed by the JSON'sframeworkvalue (e.g."CIS","CIS-Controls","DORA","Okta-IDaaS-STIG"). Unregistered frameworks fall back to the generic mapper +GenericCustomDetailsautomatically — a dedicated mapper/detail panel is a first-class upgrade, not a requirement to render. - UI grouping varies per mapper: generic/cis group by
Section/SubSection, iso byCategory, ccc byFamilyName. All readattributes[0]— inconsistent values within one JSON become separate tree branches, so normalize before shipping. - UI types:
ui/types/compliance.ts— one*AttributesMetadatainterface per framework, added to theAttributesItemDatametadata union. - UI icons:
ui/components/icons/compliance/+IconCompliance.tsx. Registration is an ordered substring match (COMPLIANCE_LOGOS): put framework-specific keywords before generic ones (nistbeforenis2,cisabeforecis;awsdeliberately last).
The CLI Pipeline (end-to-end)
prowler aws --compliance cis_7.0_aws # framework key = JSON basename
↓
Compliance.get_bulk("aws") # legacy frameworks
get_bulk_compliance_frameworks_universal("aws") # legacy (adapted) + universal
↓
update_checks_metadata_with_compliance() # attaches compliance to CheckMetadata
↓
execute_checks() → Finding objects
↓
get_check_compliance(finding, "aws", bulk) # dict "{Framework}-{Version}" → [req_ids]
↓
process_universal_compliance_frameworks() # universal: CSV + OCSF, then removed from set
per-provider elif branches in __main__.py # legacy: AWSCIS(...).batch_write_data_to_file()
↓
display_compliance_table() # universal table first, then legacy elifs,
# then generic fallback
Compliance Catalog Coverage
Counts as of 2026-07 (109 JSON files). Regenerate before trusting them:
for d in prowler/compliance/*/; do printf "%s: %s\n" "$(basename $d)" "$(ls $d*.json 2>/dev/null | wc -l)"; done
ls prowler/compliance/*.json # universal, top-level
Universal (top-level, multi-provider): cis_controls_8.1.json (18
providers), csa_ccm_4.0.json (aws/azure/gcp/alibabacloud/oraclecloud),
dora_2022_2554.json (aws/azure/gcp/alibabacloud/cloudflare).
Legacy per-provider (families, not exhaustive versions):
| Provider | # | Framework families |
|---|---|---|
| aws | 45 | CIS 1.4–7.0, NIST 800-53 r4/r5, NIST 800-171 r2, NIST CSF 1.1/2.0, PCI 3.2.1/4.0, ISO 27001 2013/2022, HIPAA, GDPR, SOC2, FedRAMP low/moderate r4 + 20x KSI low, ENS RD2022, MITRE ATT&CK, C5, CCC, CISA, FFIEC, RBI, Well-Architected (security/reliability), FTR, FSBP, AWS AI Security Framework, AWS Account Security Onboarding, Audit Manager Control Tower, GxP 21 CFR 11 / EU Annex 11, KISA ISMS-P 2023 (en+ko), NIS2, ASD Essential Eight, SecNumCloud 3.2, Prowler ThreatScore |
| azure | 19 | CIS 2.0–6.0, ISO 27001 2022, ENS RD2022, MITRE ATT&CK, PCI 4.0, HIPAA, SOC2, NIS2, RBI, C5, CCC, FedRAMP 20x KSI low, SecNumCloud 3.2, Prowler ThreatScore |
| gcp | 17 | CIS 2.0–5.0, ISO 27001 2022, ENS RD2022, MITRE ATT&CK, PCI 4.0, HIPAA, SOC2, NIS2, RBI, C5, CCC, FedRAMP 20x KSI low, SecNumCloud 3.2, Prowler ThreatScore |
| kubernetes | 8 | CIS 1.8–2.0.1, ISO 27001 2022, PCI 4.0, Prowler ThreatScore |
| m365 | 5 | CIS 4.0/6.0/7.0, ISO 27001 2022, Prowler ThreatScore |
| alibabacloud | 3 | CIS 2.0, SecNumCloud 3.2, Prowler ThreatScore |
| oraclecloud | 3 | CIS 3.0/3.1, SecNumCloud 3.2 |
| github | 2 | CIS 1.0/1.2.0 |
| googleworkspace | 2 | CIS 1.3, CISA SCuBA 0.6 |
| okta | 1 | Okta IDaaS STIG V1R2 |
| nhn | 1 | ISO 27001 2022 |
Providers with a compliance directory but no frameworks yet: cloudflare, iac,
linode, llm, mongodbatlas, openstack, stackit. Provider keys inside universal
checks dicts must match directory names under prowler/providers/ (lowercase).
Universal Schema Reference
Full spec in docs/developer-guide/security-compliance-framework.mdx. Skeleton:
{
"framework": "DORA",
"name": "Digital Operational Resilience Act (DORA) 2022/2554",
"version": "2022/2554",
"description": "Shown in --list-compliance and PDF reports.",
"icon": "dora",
"attributes_metadata": [
{"key": "Pillar", "label": "Pillar", "type": "str", "required": true,
"enum": ["ICT Risk Management", "..."],
"output_formats": {"csv": true, "ocsf": true}},
{"key": "Article", "type": "str", "required": true}
],
"outputs": {
"table_config": {"group_by": "Pillar"},
"pdf_config": {"group_by_field": "Pillar", "charts": ["..."]}
},
"requirements": [
{
"id": "DORA-Art5",
"name": "Governance and organisation",
"description": "Requirement text verbatim from the source.",
"attributes": {"Pillar": "ICT Risk Management", "Article": "Article 5"},
"checks": {
"aws": ["iam_no_root_access_key"],
"azure": [],
"gcp": []
},
"config_requirements": [
{"Check": "iam_user_accesskey_unused", "Provider": "aws",
"ConfigKey": "max_unused_access_keys_days", "Operator": "lte", "Value": 45}
]
}
]
}
Universal fields, top level (ComplianceFramework)
| Field | Type | Required | Notes |
|---|---|---|---|
framework |
string | Yes | Short identifier (DORA, CSA-CCM, CIS-Controls). This is the key the UI mapper routes on. |
name |
string | Yes | Human-readable full name. |
version |
string | No (never leave empty) | Framework version/edition (8.1, 2022/2554). |
description |
string | Yes | Shown in --list-compliance and PDF reports. |
provider |
string | No | Fallback only — the effective provider list is derived from checks keys across requirements (get_providers()). |
icon |
string | No | Short icon slug. |
attributes_metadata |
array | No (strongly recommended) | Declares the schema of every attributes key. If omitted, no attribute validation runs at all. |
outputs |
object | No | table_config (CLI table) + pdf_config (modeled, not yet consumed by the API). |
requirements |
array | Yes | List of requirement objects (below). |
Universal fields, per requirement (UniversalComplianceRequirement)
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | Yes | Unique within the framework. |
description |
string | Yes | Requirement text verbatim from the source. |
name |
string | No | Short title. |
attributes |
dict | No (default {}) |
Flat dict; every key must be declared in attributes_metadata (unknown keys are rejected at load when metadata exists). |
checks |
dict | No (default {}) |
{provider: [check_ids]}, lowercase keys matching prowler/providers/ dirs. Empty list = manual requirement for that provider. |
config_requirements |
array | No | Guardrails; each constraint must carry Provider. |
tactics, sub_techniques, platforms, technique_url |
— | No | MITRE-style extras (auto-populated when adapting legacy MITRE files). |
attributes_metadata entry fields (AttributeMetadata)
| Field | Type | Notes |
|---|---|---|
key |
string (required) | Attribute name as used in requirement.attributes. |
label |
string | Human-readable label for CSV headers / PDF. |
type |
string | str (default), int, float, bool, list_str, list_dict. Only int/float/bool are enforced at load; the rest are documentation. |
enum |
list | Allowed values — enforced at load. Use it whenever the value set is closed. |
required |
bool | Enforced at load: every requirement must carry the key non-null. |
enum_display / enum_order |
dict / list | Per-enum-value visual metadata (label, abbreviation, color, icon) and ordering for PDF rendering. |
chart_label |
string | Axis label when the attribute is used in charts. |
output_formats |
object | {"csv": bool, "ocsf": bool}, both default true — toggles inclusion per output. |
Key rules:
--compliancekey = JSON basename without.json(dora_2022_2554).- Auto-discovered: no
__init__.py, no formatter, no dispatcher registration. table_config.group_by,pdf_config.group_by_fieldand everycharts[].group_bymust reference a key declared inattributes_metadata.- Runtime type validation only covers
int/float/bool;str/list_str/list_dictare documentation-only. - Extending to a new provider = adding a key to
requirement.checks. Nothing else. - No automatic check-existence validation at load time — a typo'd check id silently produces a requirement with no findings. Always run the check-existence cross-check (see Validation).
- In universal files, always set
Provideron every config constraint so a guardrail authored for an AWS check never affects Azure/GCP scans of the same requirement.
Legacy Schema Reference
Base legacy file structure:
{
"Framework": "FRAMEWORK_NAME",
"Name": "Full Framework Name with Version",
"Version": "X.X",
"Provider": "AWS",
"Description": "Framework description...",
"Requirements": [
{
"Id": "requirement_id",
"Name": "Optional requirement name",
"Description": "Requirement description",
"Attributes": [ ... ],
"Checks": ["check_name_1"],
"ConfigRequirements": [ ... ]
}
]
}
Legacy fields, top level (Compliance)
| Field | Type | Required | Notes |
|---|---|---|---|
Framework |
string | Yes (non-empty, validated) | Canonical identifier (CIS, ENS, NIST-800-53-Revision-5). |
Name |
string | Yes (non-empty, validated) | Human-readable name with version. |
Version |
string | Optional in the model — never leave it empty in practice | Empty Version silently degrades the get_check_compliance() key to "{Framework}" (gotcha #4). Must match the version substring in the filename. |
Provider |
string | Yes (non-empty, validated) | Upper-cased single provider (AWS, AZURE, GCP, M365, ...). One file = one provider. |
Description |
string | Yes | Framework scope and purpose. |
Requirements |
array | Yes | Requirement objects (below), or Mitre_Requirement objects for MITRE files. |
Legacy fields, per requirement (Compliance_Requirement)
| Field | Type | Required | Notes |
|---|---|---|---|
Id |
string | Yes | Unique within the framework; follow the source numbering exactly (1.1, A.5.1, CCC.Core.CN01.AR01). |
Description |
string | Yes | Verbatim from the source catalog. |
Name |
string | No | Optional short title (NIST-style catalogs use it). |
Attributes |
array of objects | Yes | Parsed against the Union of attribute classes below; only Attributes[0] survives the universal adaptation and drives UI grouping. |
Checks |
array of strings | Yes | Check ids automating the requirement; [] = manual. |
ConfigRequirements |
array | No | Guardrails; Provider is omitted (the file is single-provider). |
MITRE files use Mitre_Requirement instead, which adds Tactics,
SubTechniques, Platforms, TechniqueURL at the requirement top level.
Attribute shapes per framework family
Unlike universal (schema in-file), a legacy requirement's Attributes must
match one of the Pydantic classes registered in
Compliance_Requirement.Attributes — a shape matching no class silently
falls through to Generic, dropping its specific fields. The most common
shapes (full field sets in compliance_models.py):
CIS — cis_{version}_{provider}
{
"Section": "1 Identity and Access Management",
"SubSection": "Optional subsection",
"Profile": "Level 1",
"AssessmentStatus": "Automated",
"Description": "...", "RationaleStatement": "...", "ImpactStatement": "...",
"RemediationProcedure": "...", "AuditProcedure": "...",
"AdditionalInformation": "...", "DefaultValue": "...", "References": "https://..."
}
Profile: Level 1|Level 2|E3 Level 1|E3 Level 2|E5 Level 1|E5 Level 2.
AssessmentStatus: Automated|Manual.
ENS — ens_rd2022_{provider}
{
"IdGrupoControl": "op.acc.1", "Marco": "operacional",
"Categoria": "control de acceso", "DescripcionControl": "...",
"Nivel": "alto", "Tipo": "requisito",
"Dimensiones": ["trazabilidad", "autenticidad"],
"ModoEjecucion": "automatico", "Dependencias": []
}
Nivel: opcional|bajo|medio|alto. Tipo: refuerzo|requisito|recomendacion|medida.
Dimensiones: confidencialidad|integridad|trazabilidad|autenticidad|disponibilidad.
ISO 27001 — iso27001_{year}_{provider}
{
"Category": "A.5 Organizational controls",
"Objetive_ID": "A.5.1", "Objetive_Name": "Policies for information security",
"Check_Summary": "Summary of what is being checked"
}
Note: Objetive_ID / Objetive_Name use this exact (mis)spelling.
MITRE ATT&CK — mitre_attack_{provider} (separate requirement model)
{
"Name": "Exploit Public-Facing Application", "Id": "T1190",
"Tactics": ["Initial Access"], "SubTechniques": [],
"Platforms": ["IaaS"], "Description": "...",
"TechniqueURL": "https://attack.mitre.org/techniques/T1190/",
"Checks": ["guardduty_is_enabled"],
"Attributes": [
{"AWSService": "Amazon GuardDuty", "Category": "Detect",
"Value": "Minimal", "Comment": "..."}
]
}
AzureService/GCPService for the other providers. Category:
Detect|Protect|Respond. Value: Minimal|Partial|Significant.
CCC — ccc_{provider}
{
"FamilyName": "Data", "FamilyDescription": "...",
"Section": "CCC.Core.CN01 Encrypt Data for Transmission", "SubSection": "",
"SubSectionObjective": "...",
"Applicability": ["tlp-green", "tlp-amber", "tlp-red"],
"Recommendation": "...",
"SectionThreatMappings": [{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}],
"SectionGuidelineMappings": [{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.DS-02"]}]
}
Applicability holds TLP tags (tlp-clear|tlp-green|tlp-amber|tlp-red).
ASD Essential Eight — asd_essential_eight_aws
{
"Section": "Patch applications", "MaturityLevel": "ML1",
"AssessmentStatus": "Automated", "CloudApplicability": "partial",
"MitigatedThreats": ["..."], "Description": "...",
"RationaleStatement": "...", "ImpactStatement": "...",
"RemediationProcedure": "...", "AuditProcedure": "...",
"AdditionalInformation": "...", "References": "..."
}
MaturityLevel: ML1|ML2|ML3. CloudApplicability: full|partial|limited|non-applicable.
DISA STIG — okta_idaas_stig_v1r2_okta
{
"Section": "...", "Severity": "high", "RuleID": "...", "StigID": "...",
"CCI": ["CCI-000015"], "CheckText": "...", "FixText": "..."
}
Severity: high|medium|low (maps to CAT I/II/III).
Other registered shapes
- AWS Well-Architected (
aws_well_architected_framework_{pillar}_pillar_aws):Name,WellArchitectedQuestionId,WellArchitectedPracticeId,Section,SubSection,LevelOfRisk,AssessmentMethod,Description,ImplementationGuidanceUrl. - KISA ISMS-P (
kisa_isms_p_2023_{provider}):Domain,Subdomain,Section,AuditChecklist,RelatedRegulations,AuditEvidence,NonComplianceCases. - C5 (
c5_{provider}):Section,SubSection,Type,AboutCriteria,ComplementaryCriteria. - CSA CCM (legacy shape; the shipped CSA CCM 4.0 is universal):
Section,CCMLite,IaaS,PaaS,SaaS,ScopeApplicability. - Prowler ThreatScore (
prowler_threatscore_{provider}):Title,Section,SubSection,AttributeDescription,AdditionalInformation,LevelOfRisk(1–5),Weight(1/8/10/100/1000). Pillars: 1 IAM, 2 Attack Surface, 3 Logging and Monitoring, 4 Encryption. Available for aws, azure, gcp, kubernetes, m365, alibabacloud. - Generic (fallback):
ItemId,Section,SubSection,SubGroup,Service,Type,Comment— all optional. Used by NIST, PCI, GDPR, HIPAA, SOC2, FedRAMP, CISA, FFIEC, RBI, NIS2, GxP, SecNumCloud, etc.
Config Guardrails (ConfigRequirements)
Requirements backed by configurable checks
can be silently "satisfied" by a loosened audit_config (e.g. CIS demands
45-day unused credentials but the scan ran with max_unused_access_keys_days: 120).
Guardrails force such requirements to FAIL:
"ConfigRequirements": [
{"Check": "iam_user_accesskey_unused",
"ConfigKey": "max_unused_access_keys_days", "Operator": "lte", "Value": 45}
]
- Operators:
lte/gte(numeric thresholds),eq(toggles/exact — use JSON booleans, not 0/1),in(scalar in allowed set),subset(allowlists — widening breaks it),superset(denylists — removing an entry breaks it). Valuemust be the strictest setting the control text tolerates.ConfigKeymust be spelled exactly as the check reads it; unknown keys are silently skipped (defaults assumed OK).- Guardrails only tighten (PASS→FAIL), never relax.
- Universal files: lowercase
config_requirements+ mandatoryProviderper constraint. - Tests:
tests/lib/check/compliance_config_eval_test.py,compliance_config_constraint_model_test.py,compliance_config_requirements_data_test.py, plus per-output tests undertests/lib/outputs/compliance/.
Workflow A: Sync a Framework With an Upstream Catalog
Use when the framework is maintained upstream (CIS Benchmarks, FINOS CCC, CSA CCM, NIST, ENS, etc.) and Prowler needs to catch up.
Step 1 — Cache the upstream source
Download every upstream file to a local cache so iterations don't hit the network. For FINOS CCC:
mkdir -p /tmp/ccc_upstream
catalogs="core/ccc storage/object management/auditlog management/logging ..."
for p in $catalogs; do
safe=$(echo "$p" | tr '/' '_')
gh api "repos/finos/common-cloud-controls/contents/catalogs/$p/controls.yaml" \
-H "Accept: application/vnd.github.raw" > "/tmp/ccc_upstream/${safe}.yaml"
done
Step 2 — Run the generic sync runner against a framework config
The sync tooling is three layers, so adding a framework only takes a YAML config (plus a parser module for an unfamiliar upstream format):
skills/prowler-compliance/assets/
├── sync_framework.py # generic runner — works for any framework
├── configs/ccc.yaml # per-framework config (canonical example)
└── parsers/finos_ccc.py # parser module for FINOS CCC YAML
python skills/prowler-compliance/assets/sync_framework.py \
skills/prowler-compliance/assets/configs/ccc.yaml
The runner loads the config, dynamically imports parser.module, calls
parse_upstream(config) -> list[dict], then applies generic post-processing
(id-uniqueness safety net, FamilyName normalization, legacy check-mapping
preservation with config-driven fallback keys) and writes the provider JSONs
with Pydantic post-validation.
To add a new framework sync:
- Write
assets/configs/{framework}.yaml(seeccc.yaml). Required sections:framework—name,display_name,version(never empty — the runner refuses to start, because empty Version breaks theget_check_compliance()key),description_template.providers— list of{key, display}pairs.output.path_template— e.g."prowler/compliance/{provider}/cis_{version}_{provider}.json".upstream.dir— local cache (Step 1).parser.module— module underparsers/; the rest ofparser.is passed through opaque.post_processing.check_preservation.primary_key(almost alwaysId) andfallback_keys— lists ofAttributes[0]field names composed into tuples for recovering mappings when ids change. CCC:- [Section, Applicability]; CIS:- [Section, Profile]; NIST:- [ItemId]. List-valued fields are frozen tofrozensetautomatically.post_processing.family_name_normalization(optional) — raw → canonical map; the UI groups by the exact attribute value, so upstream variants otherwise become separate tree branches.
- Reuse an existing parser or write
parsers/{name}.pyimplementingparse_upstream(config) -> list[dict]returning Prowler-format requirements with guaranteed-unique ids. The runner raises on duplicates — it never silently renumbers, because mutating a canonical upstream id (CIS1.1.1, NISTAC-2(1)) would be catastrophic. The parser owns all upstream quirks: foreign-prefix rewriting, genuine collision renumbering, multi-shape handling.
Gotchas the runner already handles (from the FINOS CCC v2025.10 sync):
- Multiple upstream YAML shapes. Most FINOS CCC catalogs use
control-families: [...]butstorage/objectuses top-levelcontrols: [...]. A single-shape parser silently drops entire catalogs — this exact bug dropped ObjStor for a full iteration. Test with one file of each shape. - Whitespace collapse. Upstream
|block scalars keep newlines; Prowler stores single-line. Collapse with" ".join(value.split()). - Foreign-prefix id rewriting. Upstream aliases requirements across
catalogs keeping the original prefix (
CCC.AuditLog.CN08.AR01nested underCCC.Logging.CN03) — rewrite to fit the parent (CCC.Logging.CN03.AR01). - Genuine upstream collisions. Two different requirements sharing one id (upstream typo): renumber the second to the next free number; check-mapping preservation recovers by the fallback keys.
- Populate
Version— fail-fast beats the silent broken-key bug.
Step 3 — Validate before committing
Run the full Validation section below (universal loader + check existence + CLI smoke + pytest).
Step 4 — Add an attribute model if needed
Only if the framework has fields beyond
Generic_Compliance_Requirement_Attribute and must stay legacy. Add the class
to compliance_models.py and register it in the
Compliance_Requirement.Attributes Union before Generic (Generic stays
last). For new frameworks, prefer universal attributes_metadata instead.
Workflow B: Audit Check Mappings as a Cloud Auditor
Use when the user asks to review existing mappings. This is the highest-value compliance task — it surfaces padded mappings with zero actual coverage and missing mappings for legitimate coverage.
The golden rule
A Prowler check's title/risk MUST literally describe what the requirement text says. "Related" is not enough. If no check actually addresses the requirement, leave the checks list empty (MANUAL) — honest MANUAL is worth more than padded coverage.
Audit process
-
Build a per-provider check inventory —
assets/build_inventory.py(writes/tmp/checks_{provider}.jsonfor every provider discovered underprowler/providers/). -
Query it —
assets/query_checks.py(run from the repository root):python skills/prowler-compliance/assets/query_checks.py aws encryption transit # keyword AND-search python skills/prowler-compliance/assets/query_checks.py aws --service iam # all iam checks python skills/prowler-compliance/assets/query_checks.py aws --id kms_cmk_rotation_enabled -
Dump a framework section with current mappings —
assets/dump_section.py:python skills/prowler-compliance/assets/dump_section.py ccc "CCC.Core." python skills/prowler-compliance/assets/dump_section.py cis_5.0_aws "1." -
Encode explicit REPLACE decisions —
assets/audit_framework_template.py:DECISIONS = {} DECISIONS["CCC.Core.CN01.AR01"] = { "aws": ["cloudfront_distributions_https_enabled", ...], "azure": ["storage_secure_transfer_required_is_enabled", ...], "gcp": ["cloudsql_instance_ssl_connections"], # Missing provider key = leave the legacy mapping untouched } # Empty list = EXPLICITLY MANUAL (overwrites legacy) DECISIONS["CCC.Core.CN01.AR07"] = {"aws": [], "azure": [], "gcp": []}REPLACE, not PATCH. Full lists make the audit reproducible and surface hidden assumptions in the legacy data.
-
Pre-validate every check id against the inventory; the script MUST abort with stderr listing typos (real audits caught
storage_secure_transfer_required_enabled→storage_secure_transfer_required_is_enabled,sqlserver_minimum_tls_version_12→sqlserver_recommended_minimal_tls_version, and several checks that simply don't exist). -
Apply + validate + test:
python /path/to/audit_script.py uv run pytest -n auto tests/lib/outputs/compliance/ tests/lib/check/ -q
For the curated mapping table (requirement text → AWS/Azure/GCP checks) and the list of controls Prowler genuinely cannot verify, see references/check-mapping-reference.md.
Workflow C: Add a New Universal Framework
- Author
prowler/compliance/{framework}_{version}.jsonfollowing the Universal Schema Reference above (usedora_2022_2554.jsonorcsa_ccm_4.0.jsonas template). - Declare every attribute in
attributes_metadata(withrequired/enumwhere possible — that's your load-time validation) and aoutputs.table_config.group_by. - Map checks per provider; add
config_requirements(withProvider) for configurable checks; leave empty lists for manual requirements — include every requirement of the source catalog (coverage percentages depend on the full denominator). - Validate (section below). No Python registration of any kind is needed for CLI table/CSV/OCSF.
- Optional first-class UI: mapper in
ui/lib/compliance/{framework}.tsx, registration ingetComplianceMappers()under the JSON'sframeworkvalue, detail panel,*AttributesMetadatatype, and icon (ordered keyword!). Until then the generic mapper renders it. - Optional API extras: CSV exporter entry in
COMPLIANCE_CLASS_MAP; PDF generator +FRAMEWORK_REGISTRYentry if a PDF is required. - Tests: extend
tests/lib/check/universal_compliance_models_test.pywith a case loading the new JSON. The parametrizedtest_loads_as_universalalready picks the file up automatically. - Changelog fragment
prowler/changelog.d/<slug>.added.md+ user-guide tutorial underdocs/user-guide/compliance/tutorials/for high-profile frameworks.
Workflow D: Add a New Legacy Output Formatter
Only for new members of an existing legacy family. Follow the c5/ or ccc/
layout exactly:
mkdir prowler/lib/outputs/compliance/{framework}with__init__.py.{framework}.py— copyc5/c5.py, change function name + framework string; the diff should be just those lines. No docstring (legacy style).models.py— one Pydantic CSV row model per provider. Column sets differ per provider (AccountId/RegionvsSubscriptionId/LocationvsProjectId/Location); per-provider files are the convention — don't collapse them into a parameterized class, reviewers will reject it.{framework}_{provider}.py—{Framework}_{Provider}(ComplianceOutput)withtransform(); this file may importFinding.- Register:
compliance.py→display_compliance_table()elifbranch (+ top import).prowler/__main__.py→ per-providerelif compliance_name.startswith(...)branches instantiating the writer classes.api/src/backend/tasks/jobs/export.py→COMPLIANCE_CLASS_MAPentries (startswithfor families, exact match only for true singletons).
- Tests under
tests/lib/outputs/compliance/{framework}/+ fixtures intests/lib/outputs/compliance/fixtures.py(1 evaluated + 1 manual requirement to exercise bothtransform()paths).
Circular import warning: the table file must not import Finding directly
or transitively (cycle: compliance.compliance → table → ComplianceOutput →
Finding → get_check_compliance → compliance.compliance). Keep it bare;
use TYPE_CHECKING/function-local imports where both are genuinely needed.
Validation (run before every commit)
-
Schema load (both formats):
from prowler.lib.check.compliance_models import ( load_compliance_framework_universal, get_bulk_compliance_frameworks_universal, ) fw = load_compliance_framework_universal("prowler/compliance/<file>.json") assert fw is not None, "check logs for the ValidationError" print(fw.framework, len(fw.requirements), fw.get_providers()) assert "<file_basename>" in get_bulk_compliance_frameworks_universal("aws")Remember: the universal loader is lenient (skips broken files with a log line) — an
assert fw is not Noneis mandatory, a green scan is not proof. -
Check existence — no loader validates this; a stale id is silent dead weight:
import json from pathlib import Path for prov in ["aws", "azure", "gcp"]: real = {p.stem.replace(".metadata", "") for p in Path(f"prowler/providers/{prov}/services").rglob("*.metadata.json")} data = json.load(open(f"prowler/compliance/{prov}/<file>.json")) refs = {c for r in data["Requirements"] for c in r["Checks"]} missing = refs - real assert not missing, f"{prov} missing: {missing}"(For universal files use
r.get("checks", {}).get(prov, [])instead — requirements may legitimately omit a provider key.) -
CLI smoke test:
uv run python prowler-cli.py <provider> --list-compliance # appears? uv run python prowler-cli.py <provider> --compliance <key> --log-level ERRORVerify the CSV under
output/compliance/, the summary table sections, and the findings roll-up. -
Tests:
uv run pytest -n auto tests/lib/check/universal_compliance_models_test.py \ tests/lib/outputs/compliance/test_loads_as_universalis parametrized over every JSON inprowler/compliance/(top-level + subdirectories) — a malformed file fails CI here even if you never wrote a dedicated test. -
What CI/pre-commit do and don't cover: pre-commit only guarantees well-formed/pretty JSON (
check-json,pretty-format-json) — no semantic validation. The workflow.github/workflows/pr-check-compliance-mapping.ymlflags PRs adding new checks without mapping them to any framework (labelneeds-compliance-review; skip with labelno-compliance-check). Semantic validation happens in the pytest suite above and manually viaskills/prowler-compliance-review/assets/validate_compliance.py(note: that validator assumes the legacy schema). -
Prowler Local Server:
docker compose upand confirm the compliance page renders requirements, sections and widgets.
Conventions and Hard-Won Gotchas
- Universal first. A new framework that starts as legacy needs 3 output files + 3 registrations; the same framework as universal needs zero. Only extend legacy families.
Generic_Compliance_Requirement_Attributestays LAST in the legacy Attributes Union — Pydantic v1 tries members in order; Generic first silently swallows every specific shape.- Pydantic v1 everywhere in
compliance_models.py(from pydantic.v1 import ...). Don't mix in v2. get_check_compliance()lives inprowler/lib/outputs/compliance/compliance_check.pyand keys the dictf"{Framework}-{Version}"only when Version is non-empty. Never shipVersion: ""— the key silently degrades to"{Framework}"and breaks filters, tests and--compliance. For legacy files the filename version substring must matchVersion(the CLI readscompliance_framework.split("_")[1]).Compliance.get_bulk()does not see top-level universal files — onlyget_bulk_compliance_frameworks_universal()does. Wire new code paths against the universal loader.- Loader leniency differs: legacy loader exits the process on a broken JSON; universal loader logs and skips. A missing framework after your edit usually means the universal loader dropped it — check the logs.
- Circular import protection: legacy table dispatcher files must not
import
Finding(directly or transitively). UseTYPE_CHECKINGor function-local imports when a module needs both sides (that's how the universal formatter does it). - Per-provider formatter files are the legacy convention — but know the exceptions before flagging them (iso27001 has no table file, aws_well_architected has no per-provider files, cisa_scuba is googleworkspace-only). CSV model field names are public API.
- CSV output:
;delimiter, UPPERCASE headers. OCSF compliance output is always generated for universal frameworks regardless of--output-formats. COMPLIANCE_CLASS_MAPmixes predicate styles:startswithfor multi-version families, exact==for singletons. When in doubt usestartswith— exact match blocked versioned CCC variants until 2026.- UI grouping is per-mapper, always on
attributes[0]: generic/cis →Section/SubSection, iso →Category, ccc →FamilyName. Inconsistent values (or empty Section) create orphan/duplicate tree branches — normalize before shipping. - UI has a generic fallback — an unregistered framework still renders. A dedicated mapper/panel/icon is an upgrade, not a prerequisite.
- Icon registration is ordered substring matching in
IconCompliance.tsx— specific keywords before generic (nistbeforenis2,cisabeforecis,awslast). - API PDF pipeline is not
PDFConfig-driven yet — it has its ownFRAMEWORK_REGISTRY(5 frameworks). Don't assume addingpdf_configto a JSON produces a PDF in Prowler App. - Pre-validate every check id against the per-provider inventory before writing JSON. No loader will catch a typo; the requirement just never matches a finding.
- REPLACE beats PATCH for audit decisions — full explicit lists are reproducible and surface legacy assumptions.
- When no check applies, MANUAL is correct. Don't pad mappings with tangential checks; compliance reports must stay actionable.
- Include every requirement of the source catalog, automated or not — compliance percentages use the full requirement count as denominator.
- Provider coverage is asymmetric (AWS dense; Azure/GCP thinner; new providers minimal). Accept it — don't force parity Prowler can't verify.
- Guardrail authoring: strictest tolerated
Value, exactConfigKeyspelling,Providermandatory in universal files, booleans as JSON booleans. Malformed constraints are treated as satisfied — validate with the config tests, don't trust silence.
Useful One-Liners
# Find duplicate requirement IDs (legacy | universal)
jq -r '.Requirements[].Id' file.json | sort | uniq -d
jq -r '.requirements[].id' file.json | sort | uniq -d
# Count manual requirements (legacy | universal, per provider)
jq '[.Requirements[] | select((.Checks | length) == 0)] | length' file.json
jq '[.requirements[] | select((.checks.aws // [] | length) == 0)] | length' file.json
# List unique check references (legacy | universal)
jq -r '.Requirements[].Checks[]' file.json | sort -u
jq -r '.requirements[].checks[]? | .[]' file.json | sort -u
# Providers covered by a universal framework
jq '[.requirements[].checks | keys[]] | unique' file.json
# Spot inconsistent grouping values (UI tree branches)
jq '[.Requirements[].Attributes[0].Section] | unique' file.json
jq '[.Requirements[].Attributes[0].FamilyName] | unique' file.json
# Requirements with config guardrails (empty arrays are truthy in jq — check length)
jq '[.Requirements[] | select((.ConfigRequirements // []) | length > 0)] | length' file.json
# Diff requirement ids between two versions
diff <(jq -r '.Requirements[].Id' a.json | sort) <(jq -r '.Requirements[].Id' b.json | sort)
# Where is a check mapped across all frameworks?
grep -rl "my_check_name" prowler/compliance/
# Does a check exist?
find prowler/providers/aws/services -name "{check_id}.metadata.json"
# Validate one file with the universal loader
python -c "from prowler.lib.check.compliance_models import load_compliance_framework_universal as l; fw=l('prowler/compliance/aws/cis_7.0_aws.json'); print(fw.framework, len(fw.requirements))"
Commands
prowler {provider} --list-compliance
prowler {provider} --compliance cis_7.0_aws
prowler aws --compliance cis_7.0_aws pci_4.0_aws
prowler aws --compliance dora_2022_2554 # universal key = file basename
prowler aws --list-compliance-requirements cis_7.0_aws
prowler aws --compliance cis_7.0_aws -M csv json html
Code References
Layer 1 — SDK / Core
prowler/lib/check/compliance_models.py— legacy + universal model trees,Compliance_Requirement_ConfigConstraint, all loaders and the legacy→universal adapterprowler/lib/check/compliance.py—update_checks_metadata_with_complianceprowler/lib/check/compliance_config_eval.py— guardrail evaluation (shared with the API)prowler/lib/outputs/compliance/compliance_check.py—get_check_complianceprowler/lib/check/utils.py—list_compliance_modules
Layer 2 — JSON Catalogs
prowler/compliance/*.json— universal, multi-provider (auto-discovered)prowler/compliance/{provider}/— legacy, per-provider (auto-discovered)
Layer 3 — Output Formatters
prowler/lib/outputs/compliance/universal/—universal_table.py,universal_output.py,ocsf_compliance.pyprowler/lib/outputs/compliance/{framework}/— legacy per-framework packagesprowler/lib/outputs/compliance/compliance.py—process_universal_compliance_frameworks,display_compliance_tableprowler/lib/outputs/compliance/compliance_output.py—ComplianceOutputbase + CSV writerprowler/__main__.py— universal processing + per-provider legacy writer branches
Layer 4 — API / UI
api/src/backend/api/compliance.py—LazyComplianceTemplate,LazyChecksMapping, cache warm-upapi/src/backend/tasks/jobs/export.py—COMPLIANCE_CLASS_MAPapi/src/backend/tasks/jobs/scan.py—create_compliance_requirements(overview ingestion)api/src/backend/tasks/jobs/reports/— PDF generators +FRAMEWORK_REGISTRYui/lib/compliance/compliance-mapper.ts— mapper routing + generic fallbackui/lib/compliance/{framework}.tsx— per-framework mappersui/components/compliance/compliance-custom-details/— detail panelsui/types/compliance.ts— attribute metadata typesui/components/icons/compliance/+IconCompliance.tsx— icons (ordered)
Tests
tests/lib/check/universal_compliance_models_test.py— includes the parametrizedtest_loads_as_universalover every shipped JSONtests/lib/check/compliance_check_test.py,compliance_config_eval_test.py,compliance_config_constraint_model_test.py,compliance_config_requirements_data_test.py,mitre_config_requirements_test.pytests/lib/outputs/compliance/— per-framework + universal + dispatcher + config-status coverage tests; sharedfixtures.py
Resources
- Docs (source of truth for contributors):
docs/developer-guide/security-compliance-framework.mdx(both schemas, guardrails, validation, PR process),docs/user-guide/compliance/tutorials/compliance.mdx,docs/user-guide/compliance/tutorials/cross-provider-compliance.mdx - Repo tooling (
util/compliance/): CSV→JSON generators (generate_json_from_csv/),ccc/from_yaml_to_json.py,compliance_mapper/,threatscore/ - Skill assets (assets/):
sync_framework.py+configs/ccc.yaml+parsers/finos_ccc.py— config-driven upstream sync (Workflow A)build_inventory.py,query_checks.py,dump_section.py,audit_framework_template.py— audit tooling (Workflow B)- Legacy JSON templates:
cis_framework.json,ens_framework.json,iso27001_framework.json,mitre_attack_framework.json,prowler_threatscore_framework.json,generic_framework.json
- References: references/compliance-docs.md — model/loader quick reference; references/check-mapping-reference.md — curated requirement-text → checks mapping table + honest-MANUAL list
- Sister skill:
prowler-compliance-review — PR
review checklist +
validate_compliance.py(legacy-schema validator) - After editing this skill's frontmatter, run
./skills/skill-sync/assets/sync.shto regenerate the AGENTS.md auto-invoke tables.