Introduction
Checks are the core component of Prowler. A check is a piece of code designed to validate whether a configuration aligns with cybersecurity best practices. Execution of a check yields a finding, which includes the result and contextual metadata (e.g., outcome, risks, remediation).Creating a Check
The most common high level steps to create a new check are:- Prerequisites:
- Verify the check does not already exist by searching Prowler Hub or checking prowler/providers/<provider>/services/<service>/<check_name_want_to_implement>/.
- Ensure required provider and service exist. If not, follow the Provider and Service documentation to create them.
- Confirm the service has implemented all required methods and attributes for the check (in most cases, you will need to add or modify some methods in the service to get the data you need for the check).
 
- Verify the check does not already exist by searching Prowler Hub or checking 
- Navigate to the service directory. The path should be as follows: prowler/providers/<provider>/services/<service>.
- Create a check-specific folder. The path should follow this pattern: prowler/providers/<provider>/services/<service>/<check_name_want_to_implement>. Adhere to the Naming Format for Checks.
- Populate the folder with files as specified in File Creation.
- Run the check locally to ensure it works as expected. For checking you can use the CLI in the next way:
- To ensure the check has been detected by Prowler: poetry run python prowler-cli.py <provider> --list-checks | grep <check_name>.
- To run the check, to find possible issues: poetry run python prowler-cli.py <provider> --log-level ERROR --verbose --check <check_name>.
 
- To ensure the check has been detected by Prowler: 
- Create comprehensive tests for the check that cover multiple scenarios including both PASS (compliant) and FAIL (non-compliant) cases. For detailed information about test structure and implementation guidelines, refer to the Testing documentation.
- If the check and its corresponding tests are working as expected, you can submit a PR to Prowler.
Naming Format for Checks
Checks must be named following the format:service_subservice_resource_action.
The name components are:
- service– The main service being audited (e.g., ec2, entra, iam, etc.)
- subservice– An individual component or subset of functionality within the service that is being audited. This may correspond to a shortened version of the class attribute accessed within the check. If there is no subservice, just omit.
- resource– The specific resource type being evaluated (e.g., instance, policy, role, etc.)
- action– The security aspect or configuration being checked (e.g., public, encrypted, enabled, etc.)
File Creation
Each check in Prowler follows a straightforward structure. Within the newly created folder, three files must be added to implement the check logic:- __init__.py(empty file) – Ensures Python treats the check folder as a package.
- <check_name>.py(code file) – Contains the check logic, following the prescribed format. Please refer to the prowler’s check code structure for more information.
- <check_name>.metadata.json(metadata file) – Defines the check’s metadata for contextual information. Please refer to the check metadata for more information.
Prowler’s Check Code Structure
Prowler’s check structure is designed for clarity and maintainability. It follows a dynamic loading approach based on predefined paths, ensuring seamless integration of new checks into a provider’s service without additional manual steps. Below the code for a generic check is presented. It is strongly recommended to consult other checks from the same provider and service to understand provider-specific details and patterns. This will help ensure consistency and proper implementation of provider-specific requirements. Report fields are the most dependent on the provider, consult theCheckReport<Provider> class for more information on what can be included in the report here.
Legacy providers (AWS, Azure, GCP, Kubernetes) follow the 
Check_Report_<Provider> naming convention. This is not recommended for current instances. Newer providers adopt the CheckReport<Provider> naming convention. Learn more at Prowler Code.Generic Check Class
Data Requirements for Checks in Prowler
One of the most important aspects when creating a new check is ensuring that all required data is available from the service client. Often, default API calls are insufficient. Extending the service class with new methods or resource attributes may be required to fetch and store requisite data.Statuses for Checks in Prowler
Required Fields: status and status_extended Each check must populate thereport.status and report.status_extended fields according to the following criteria:
- 
Status field: report.status- PASS– Assigned when the check confirms compliance with the configured value.
- FAIL– Assigned when the check detects non-compliance with the configured value.
- MANUAL– This status must not be used unless manual verification is necessary to determine whether the status (- report.status) passes (- PASS) or fails (- FAIL).
 
- 
Status extended field: report.status_extended- It must end with a period (.).
- It must include the audited service, the resource, and a concise explanation of the check result, for instance: EC2 AMI ami-0123456789 is not public..
 
- It must end with a period (
Prowler’s Check Severity Levels
The severity of each check is defined in the metadata file using theSeverity field. Severity values are always lowercase and must be one of the predefined categories below.
- critical– Issue that must be addressed immediately.
- high– Issue that should be addressed as soon as possible.
- medium– Issue that should be addressed within a reasonable timeframe.
- low– Issue that can be addressed in the future.
- informational– Not an issue but provides valuable information.
report.check_metadata.Severity attribute:
Resource Identification in Prowler
Each check must populate the report with an unique identifier for the audited resource. This identifier or identifiers are going to depend on the provider and the resource that is being audited. Here are the criteria for each provider:- AWS
- Amazon Resource ID — report.resource_id.- The resource identifier. This is the name of the resource, the ID of the resource, or a resource path. Some resource identifiers include a parent resource (sub-resource-type/parent-resource/sub-resource) or a qualifier such as a version (resource-type:resource-name:qualifier).
- If the resource ID cannot be retrieved directly from the audited resource, it can be extracted from the ARN. It is the last part of the ARN after the last slash (/) or colon (:).
- If no actual resource to audit exists, this format can be used: <resource_type>/unknown
 
- Amazon Resource Name — report.resource_arn.- The Amazon Resource Name (ARN) of the audited entity.
- If the ARN cannot be retrieved directly from the audited resource, construct a valid ARN using the resource_idcomponent as the audited entity. Examples:- Bedrock — arn:<partition>:bedrock:<region>:<account-id>:model-invocation-logging.
- DirectConnect — arn:<partition>:directconnect:<region>:<account-id>:dxcon.
 
- Bedrock — 
- If no actual resource to audit exists, this format can be used: arn:<partition>:<service>:<region>:<account-id>:<resource_type>/unknown.- Examples:
- AWS Security Hub — arn:<partition>:security-hub:<region>:<account-id>:hub/unknown.
- Access Analyzer — arn:<partition>:access-analyzer:<region>:<account-id>:analyzer/unknown.
- GuardDuty — arn:<partition>:guardduty:<region>:<account-id>:detector/unknown.
 
- AWS Security Hub — 
 
- Examples:
 
 
- Amazon Resource ID — 
- GCP
- Resource ID — report.resource_id.- Resource ID represents the full, unambiguous path to a resource, known as the full resource name. Typically, it follows the format: //{api_service/resource_path}.
- If the resource ID cannot be retrieved directly from the audited resource, by default the resource name is used.
 
- Resource ID represents the full, unambiguous path to a resource, known as the full resource name. Typically, it follows the format: 
- Resource Name — report.resource_name.- Resource Name usually refers to the name of a resource within its service.
 
 
- Resource ID — 
- Azure
- Resource ID — report.resource_id.- Resource ID represents the full Azure Resource Manager path to a resource, which follows the format: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}.
 
- Resource ID represents the full Azure Resource Manager path to a resource, which follows the format: 
- Resource Name — report.resource_name.- Resource Name usually refers to the name of a resource within its service.
- If the resource name cannot be retrieved directly from the audited resource, the last part of the resource ID can be used.
 
 
- Resource ID — 
- Kubernetes
- Resource ID — report.resource_id.- The UID of the Kubernetes object. This is a system-generated string that uniquely identifies the object within the cluster for its entire lifetime. See Kubernetes Object Names and IDs - UIDs.
 
- Resource Name — report.resource_name.- The name of the Kubernetes object. This is a client-provided string that must be unique for the resource type within a namespace (for namespaced resources) or cluster (for cluster-scoped resources). Names typically follow DNS subdomain or label conventions. See Kubernetes Object Names and IDs - Names.
 
 
- Resource ID — 
- M365
- Resource ID — report.resource_id.- If the audited resource has a globally unique identifier such as a guid, use it as theresource_id.
- If no guidexists, use another unique and relevant identifier for the resource, such as the tenant domain, the internal policy ID, or a representative string following the format<resource_type>/<name_or_id>.
 
- If the audited resource has a globally unique identifier such as a 
- Resource Name — report.resource_name.- Use the visible or descriptive name of the audited resource. If no explicit name is available, use a clear description of the resource or configuration being evaluated.
 
- Examples:
- For an organization:
- resource_id: Organization GUID
- resource_name: Organization name
 
- For a policy:
- resource_id: Unique policy ID
- resource_name: Policy display name
 
- For global configurations:
- resource_id: Tenant domain or representative string (e.g., “userSettings”)
- resource_name: Description of the configuration (e.g., “SharePoint Settings”)
 
 
- For an organization:
 
- Resource ID — 
- GitHub
- Resource ID — report.resource_id.- The ID of the Github resource. This is a system-generated integer that uniquely identifies the resource within the Github platform.
 
- Resource Name — report.resource_name.- The name of the Github resource. In the case of a repository, this is just the repository name. For full repository names use the resource full_name.
 
- The name of the Github resource. In the case of a repository, this is just the repository name. For full repository names use the resource 
 
- Resource ID — 
Configurable Checks in Prowler
See Configurable Checks for detailed information on making checks configurable using theaudit_config object and configuration file.
Metadata Structure for Prowler Checks
Each Prowler check must include a metadata file named<check_name>.metadata.json that must be located in its directory. This file supplies crucial information for execution, reporting, and context.
Example Metadata File
Below is a generic example of a check metadata file. Do not include comments in actual JSON files.Metadata Fields and Their Purpose
Provider
The Prowler provider related to the check. The name must be lowercase and match the provider folder name. For supported providers refer to Prowler Hub or directly to Prowler Code.CheckID
The unique identifier for the check inside the provider. This field must match the check’s folder, Python file, and JSON metadata file name. For more information about naming, refer to the Naming Format for Checks section.CheckTitle
TheCheckTitle field must be plain text, clearly and succinctly define the best practice being evaluated and which resource(s) each finding applies to. The title should be specific, concise (no more than 150 characters), and reference the relevant resource(s) involved.
Always write the CheckTitle to describe the PASS case, the desired secure or compliant state of the resource(s). This helps ensure that findings are easy to interpret and that the title always reflects the best practice being met.
For detailed guidelines on writing effective check titles, including how to determine singular vs. plural scope and common mistakes to avoid, see Check Title Guidelines.
CheckType
This field is only applicable to the AWS provider.
namespace/category/classifier.
For the complete AWS Security Hub selection guidelines, see Check Type Guidelines.
ServiceName
The name of the provider service being audited. Must be lowercase and match the service folder name. For supported services refer to Prowler Hub or the Prowler Code.SubServiceName
This field is in the process of being deprecated and should be left empty.ResourceIdTemplate
This field is in the process of being deprecated and should be left empty.Severity
Severity level if the check fails. Must be one of:critical, high, medium, low, or informational, and written in lowercase. See Prowler’s Check Severity Levels for details.
ResourceType
The type of resource being audited. This field helps categorize and organize findings by resource type for better analysis and reporting. For each provider:- AWS: Use Security Hub resource types or PascalCase CloudFormation types removing the ::separator used in CloudFormation templates (e.g., in CloudFormation template the type of an EC2 instance isAWS::EC2::Instancebut in the check it should beAwsEc2Instance). UseOtherif none apply.
- Azure: Use types from Azure Resource Graph, for example: Microsoft.Storage/storageAccounts.
- Google Cloud: Use Cloud Asset Inventory asset types, for example: compute.googleapis.com/Instance.
- Kubernetes: Use types shown under KINDfromkubectl api-resources.
- M365 / GitHub: Leave empty due to lack of standardized types.
Description
A concise, natural language explanation that clearly describes what the finding means, focusing on clarity and context rather than technical implementation details. Use simple paragraphs with line breaks if needed, but avoid sections, code blocks, or complex formatting. This field is limited to maximum 400 characters. For detailed writing guidelines and common mistakes to avoid, see Description Guidelines.Risk
A clear, natural language explanation of why this finding poses a cybersecurity risk. Focus on how it may impact confidentiality, integrity, or availability. If those do not apply, describe any relevant operational or financial risks. Use simple paragraphs with line breaks if needed, but avoid sections, code blocks, or complex formatting. Limit your explanation to 400 characters. For detailed writing guidelines and common mistakes to avoid, see Risk Guidelines.RelatedUrl
Deprecated. UseAdditionalURLs for adding your URLs references.
AdditionalURLs
URLs must be valid and not repeated.
Remediation
Provides both code examples and best practice recommendations for addressing the security issue.- 
Code: Contains remediation examples in different formats:
- CLI: Command-line interface commands to make the finding compliant in runtime.
- NativeIaC: Native Infrastructure as Code templates with an example of a compliant configuration. For now it applies to:
- AWS: CloudFormation YAML formatted code (do not use JSON format).
- Azure: Bicep formatted code (do not use ARM templates).
 
- Terraform: HashiCorp Configuration Language (HCL) code with an example of a compliant configuration.
- Other: Manual steps through web interfaces or other tools to make the finding compliant.
 
- 
Recommendation
- Text: Generic best practice guidance in natural language using Markdown format (maximum 400 characters). For writing guidelines, see Recommendation Guidelines.
- Url: Prowler Hub URL of the check. This URL is always composed by https://hub.prowler.com/check/<check_id>.
 
Categories
One or more functional groupings used for execution filtering (e.g.,internet-exposed). You can define new categories just by adding to this field.
For the complete list of available categories, see Categories Guidelines.
DependsOn
List of check IDs of checks that if are compliant, this check will be a compliant too or it is not going to give any finding.RelatedTo
List of check IDs of checks that are conceptually related, even if they do not share a technical dependency.Notes
Any additional information not covered in the above fields.Python Model Reference
The metadata structure is enforced in code using a Pydantic model. For reference, see theCheckMetadata.
Generic Check Patterns and Best Practices
Common Patterns
- Every check is implemented as a class inheriting from Check(fromprowler.lib.check.models).
- The main logic is implemented in the execute()method (only method that must be implemented), which always returns a list of provider-specific report objects (e.g.,CheckReport<Provider>)—one per finding/resource. If there are no findings/resources, return an empty list.
- Never use the provider’s client directly; instead, use the service client (e.g., <service>_client) and iterate over its resources.
- For each resource, create a provider-specific report object, populate it with metadata, resource details, status (PASS,FAIL, etc.), and a human-readablestatus_extendedmessage.
- Use the metadata()method to attach check metadata to each report.
- Checks are designed to be idempotent and stateless: they do not modify resources, only report on their state.
Best Practices
- Use clear, actionable, and user-friendly language in status_extendedto explain the result. Always provide information to identify the resource.
- Use helper functions/utilities for repeated logic to avoid code duplication. Save them in the libfolder of the service.
- Handle exceptions gracefully: catch errors per resource, log them, and continue processing other resources.
- Document the check with a class and function level docstring explaining what it does, what it checks, and any caveats or provider-specific behaviors.
- Use type hints for the execute()method (e.g.,-> list[CheckReport<Provider>]) for clarity and static analysis.
- Ensure checks are efficient; avoid excessive nested loops. If the complexity is high, consider refactoring the check.
- Keep the check logic focused: one check = one control/requirement. Avoid combining unrelated logic in a single check.
Specific Check Patterns
Details for specific providers can be found in documentation pages named using the pattern<provider_name>-details.
