Back to Checks
SPF record exists with strict policy (-all)
zone_record_spf_exists
Service: zoneSeverity: medium
OfficialBuilt-inVerified
Categories:email-security
Resource Type:Zone
Cloudflare zones are assessed for SPF (Sender Policy Framework) records by checking if a TXT record exists that specifies which mail servers are authorized to send email on behalf of the domain, and verifies that the record uses a strict policy (-all) to reject unauthorized senders.
Risk
Without SPF or with a permissive policy (~all, ?all, +all), attackers can forge emails from your domain. Phishing attacks can harvest sensitive information from recipients who trust spoofed emails. Brand reputation is damaged by fraudulent emails. ~all only marks emails as suspicious without rejecting them.
Run this check with Prowler
Run in Prowler CloudRun
prowler cloudflare --checks zone_record_spf_existsRecommendation
Configure SPF records with strict policy (-all) listing authorized mail servers.
- SPF records start with
v=spf1and define authorized senders - Use
-all(hardfail) to reject emails from unauthorized servers - Avoid
~all(softfail) in production as it only marks suspicious emails but does not reject them - Use
include:to authorize third-party mail services - Combine with DKIM and DMARC for comprehensive email authentication
- Test SPF records using online validators before deployment
Remediation
Terraform
# Create SPF record for email authentication with strict policy
resource "cloudflare_record" "spf" {
zone_id = "<ZONE_ID>"
name = "@"
type = "TXT"
content = "v=spf1 include:_spf.google.com -all" # Use -all for strict 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 @ for the Name field
6Enter your SPF record with strict policy (e.g., v=spf1 include:_spf.google.com -all)
7Click Save
Check Code
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_spf_exists(Check):
"""Ensure that SPF record exists with strict policy for Cloudflare zones.
SPF (Sender Policy Framework) is an email authentication method that specifies
which mail servers are authorized to send email on behalf of the domain. This
check verifies that an SPF record exists as a TXT record starting with "v=spf1"
and uses the strict policy qualifier "-all" to instruct receiving servers to
reject emails from unauthorized sources.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the SPF record exists check.
Iterates through all Cloudflare zones and verifies SPF configuration.
The check validates two conditions:
1. An SPF record exists (TXT record starting with "v=spf1")
2. The record uses strict policy "-all" (not ~all, ?all, or +all)
Returns:
A list of CheckReportCloudflare objects with PASS status if an SPF
record with strict policy exists, or FAIL status if no SPF record
is found or it uses a permissive policy.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
# SPF records are TXT records starting with "v=spf1"
spf_records = [
record
for record in dns_client.records
if record.zone_id == zone.id
and record.type == "TXT"
and record.content.strip('"').startswith("v=spf1")
]
if not spf_records:
report.status = "FAIL"
report.status_extended = f"No SPF record found for zone {zone.name}."
else:
# Check if SPF uses strict policy (-all) vs permissive (~all, ?all, +all)
strict_records = [
record
for record in spf_records
if record.content.strip('"').rstrip().endswith("-all")
]
records_str = ", ".join(record.name for record in spf_records)
if strict_records:
report.status = "PASS"
report.status_extended = f"SPF record with strict policy -all exists for zone {zone.name}: {records_str}."
else:
report.status = "FAIL"
report.status_extended = f"SPF record exists for zone {zone.name} but does not use strict policy -all: {records_str}."
findings.append(report)
return findings