Back to Checks
DMARC record exists with enforcement policy (p=reject or p=quarantine)
zone_record_dmarc_exists
Service: zoneSeverity: medium
OfficialBuilt-inVerified
Categories:email-security
Resource Type:Zone
Cloudflare zones are assessed for DMARC (Domain-based Message Authentication, Reporting, and Conformance) records by checking if a TXT record exists at _dmarc subdomain with an enforcement policy (p=reject or p=quarantine) to actively block or quarantine spoofed emails.
Risk
Without DMARC or with p=none, there is no active protection against email spoofing. Attackers can spoof emails for phishing campaigns to steal credentials. Domain reputation is damaged by spoofed emails. The p=none policy only generates reports but does not block or quarantine spoofed emails.
Run this check with Prowler
Run in Prowler CloudRun
prowler cloudflare --checks zone_record_dmarc_existsRecommendation
Implement DMARC with an enforcement policy (p=reject or p=quarantine).
p=reject: receiving servers should reject emails that fail authenticationp=quarantine: receiving servers should send suspicious emails to spam folderp=none: only monitoring, provides no protection (will fail this check)- Ensure SPF and DKIM are properly configured before enabling enforcement
- Configure
ruato receive aggregate reports on authentication results - Use
pctparameter to gradually roll out enforcement (e.g.,pct=10for 10% of emails)
Remediation
Terraform
# Create DMARC record with enforcement policy
resource "cloudflare_record" "dmarc" {
zone_id = "<ZONE_ID>"
name = "_dmarc"
type = "TXT"
content = "v=DMARC1; p=reject; rua=mailto:dmarc@example.com" # Use p=reject or p=quarantine for enforcement
ttl = 3600
}Other
1Log in to the Cloudflare dashboard and select your account and domain
2Go to DNS > Records
3Click Add record
4Select TXT as the record type
5Enter _dmarc for the Name field
6Enter your DMARC policy with enforcement (e.g., v=DMARC1; p=reject; rua=mailto:dmarc@example.com)
7Click Save
Check Code
import re
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.dns.dns_client import dns_client
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_record_dmarc_exists(Check):
"""Ensure that DMARC record exists with enforcement policy for Cloudflare zones.
DMARC (Domain-based Message Authentication, Reporting, and Conformance) is an
email authentication protocol that builds on SPF and DKIM. It allows domain
owners to specify how receiving mail servers should handle emails that fail
authentication checks. This check verifies that a DMARC record exists at the
_dmarc subdomain with an enforcement policy (p=reject or p=quarantine) to
actively block or quarantine spoofed emails, not just monitor them (p=none).
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the DMARC record exists check.
Iterates through all Cloudflare zones and verifies DMARC configuration.
The check validates two conditions:
1. A DMARC record exists (TXT record at _dmarc subdomain with "v=DMARC1")
2. The record uses an enforcement policy (p=reject or p=quarantine)
Returns:
A list of CheckReportCloudflare objects with PASS status if a DMARC
record with enforcement policy exists, or FAIL status if no DMARC
record is found or it uses monitoring-only policy (p=none).
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
# DMARC records are TXT records at _dmarc subdomain starting with "v=DMARC1"
dmarc_records = [
record
for record in dns_client.records
if record.zone_id == zone.id
and record.type == "TXT"
and record.name
and record.name.startswith("_dmarc")
and "V=DMARC1" in record.content.strip('"').upper()
]
if not dmarc_records:
report.status = "FAIL"
report.status_extended = f"No DMARC record found for zone {zone.name}."
else:
# Check if DMARC uses enforcement policy (p=reject or p=quarantine) vs monitoring (p=none)
enforcement_records = [
record
for record in dmarc_records
if self._get_policy_value(record.content)
in ("reject", "quarantine")
]
records_str = ", ".join(record.name for record in dmarc_records)
if enforcement_records:
# Get the actual policy value from the first enforcement record
policy = self._get_policy_value(enforcement_records[0].content)
report.status = "PASS"
report.status_extended = f"DMARC record with enforcement policy p={policy} exists for zone {zone.name}: {records_str}."
else:
# Get the actual policy value to show in the message
policy = self._get_policy_value(dmarc_records[0].content) or "none"
report.status = "FAIL"
report.status_extended = f"DMARC record exists for zone {zone.name} but uses monitoring-only policy p={policy}: {records_str}."
findings.append(report)
return findings
def _get_policy_value(self, content: str) -> str | None:
"""Extract the DMARC policy value (reject, quarantine, or none)."""
# Strip quotes that may be present from Cloudflare API
content_clean = content.strip('"')
# Match p=<value> (with optional spaces around =)
match = re.search(r"p\s*=\s*(\w+)", content_clean, re.IGNORECASE)
if match:
return match.group(1).lower()
return None