Back to Checks
m365

Inbound anti-spam policy does not contain allowed domains

defender_antispam_policy_inbound_no_allowed_domains
Service: defenderSeverity: low
OfficialBuilt-inVerified
Categories:email-securitye3
Resource Type:NotDefined

Microsoft Defender for Office 365 inbound anti-spam policies are evaluated for domains listed in AllowedSenderDomains.

The finding identifies any policy where this list is populated rather than empty.

Risk

Populating AllowedSenderDomains makes messages from those domains skip spam filtering and email authentication (SPF, DKIM, DMARC), often delivered with SCL -1. Attackers can spoof such domains to phish credentials, enable BEC, and alter mailboxes, undermining confidentiality and integrity.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler m365 --checks defender_antispam_policy_inbound_no_allowed_domains

Recommendation

  • Keep AllowedSenderDomains empty.
  • Use narrowly scoped allow logic that requires authentication alignment and additional conditions (sender, IP, headers).
  • Make any exceptions temporary and reviewed.

Apply least privilege and defense in depth to email trust decisions.

Remediation

CLI

Set-HostedContentFilterPolicy -Identity <Policy name> -AllowedSenderDomains $null

Other

1Open Microsoft 365 Defender: https://security.microsoft.com/antispam
2Open each inbound anti-spam policy (Default and any custom).
3Click Edit allowed and blocked senders and domains.
4Select Allow domains.
5Remove all domains, then click Done and Save.
6Repeat for any remaining inbound anti-spam policies.

Check Code

from typing import List

from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.defender.defender_client import defender_client


class defender_antispam_policy_inbound_no_allowed_domains(Check):
    """
    Check if the inbound anti-spam policies do not contain allowed domains in Microsoft 365 Defender.

    Attributes:
        metadata: Metadata associated with the check (inherited from Check).
    """

    def execute(self) -> List[CheckReportM365]:
        """
        Execute the check to verify if inbound anti-spam policies do not contain allowed domains.

        This method checks each inbound anti-spam policy to determine if the AllowedSenderDomains
        list is empty or undefined.

        Returns:
            List[CheckReportM365]: A list of reports containing the result of the check.
        """
        findings = []

        if defender_client.inbound_spam_policies:
            # Only Default Defender Inbound Spam Policy exists
            if not defender_client.inbound_spam_rules:
                policy = defender_client.inbound_spam_policies[0]

                report = CheckReportM365(
                    metadata=self.metadata(),
                    resource=policy,
                    resource_name=policy.identity,
                    resource_id=policy.identity,
                )

                if self._has_no_allowed_domains(policy):
                    # Case 1: Default policy exists and has no allowed domains
                    report.status = "PASS"
                    report.status_extended = f"{policy.identity} is the only policy and it does not contain allowed domains."
                else:
                    # Case 5: Default policy exists but contains allowed domains
                    report.status = "FAIL"
                    report.status_extended = f"{policy.identity} is the only policy and it contains allowed domains: {', '.join(policy.allowed_sender_domains)}."
                findings.append(report)

            # Multiple Defender Inbound Spam Policies exist
            else:
                default_policy_well_configured = False

                for policy in defender_client.inbound_spam_policies:
                    # Preset security policies are scoped by protection policy rules, not filter rules
                    if (
                        not policy.default
                        and policy.identity not in defender_client.inbound_spam_rules
                    ):
                        continue
                    report = CheckReportM365(
                        metadata=self.metadata(),
                        resource=policy,
                        resource_name=policy.identity,
                        resource_id=policy.identity,
                    )

                    if policy.default:
                        if not self._has_no_allowed_domains(policy):
                            # Case 4: Default policy contains allowed domains
                            report.status = "FAIL"
                            report.status_extended = (
                                f"{policy.identity} is the default policy and it contains allowed domains: {', '.join(policy.allowed_sender_domains)}, "
                                "but it could be overridden by another well-configured Custom Policy."
                            )
                            findings.append(report)
                        else:
                            # Case 2: Default policy has no allowed domains and there are other policies
                            report.status = "PASS"
                            report.status_extended = (
                                f"{policy.identity} is the default policy and it does not contain allowed domains, "
                                "but it could be overridden by another misconfigured Custom Policy."
                            )
                            default_policy_well_configured = True
                            findings.append(report)
                    else:
                        if not self._has_no_allowed_domains(policy):
                            included_resources = []

                            if defender_client.inbound_spam_rules[
                                policy.identity
                            ].users:
                                included_resources.append(
                                    f"users: {', '.join(defender_client.inbound_spam_rules[policy.identity].users)}"
                                )
                            if defender_client.inbound_spam_rules[
                                policy.identity
                            ].groups:
                                included_resources.append(
                                    f"groups: {', '.join(defender_client.inbound_spam_rules[policy.identity].groups)}"
                                )
                            if defender_client.inbound_spam_rules[
                                policy.identity
                            ].domains:
                                included_resources.append(
                                    f"domains: {', '.join(defender_client.inbound_spam_rules[policy.identity].domains)}"
                                )

                            included_resources_str = "; ".join(included_resources)
                            priority = defender_client.inbound_spam_rules[
                                policy.identity
                            ].priority

                            if default_policy_well_configured:
                                # Case 3: Default policy has no allowed domains but custom one does
                                report.status = "FAIL"
                                report.status_extended = (
                                    f"Custom Inbound Spam policy {policy.identity} contains allowed domains and includes {included_resources_str}, "
                                    f"with priority {priority} (0 is the highest). However, the default policy does not contain allowed domains, "
                                    "so entities not included by this custom policy could be correctly protected."
                                )
                            else:
                                # Case 5: Neither default nor custom policies are correctly configured
                                report.status = "FAIL"
                                report.status_extended = (
                                    f"Custom Inbound Spam policy {policy.identity} contains allowed domains and includes {included_resources_str}, "
                                    f"with priority {priority} (0 is the highest). Also, the default policy contains allowed domains, "
                                    "so entities not included by this custom policy could not be correctly protected."
                                )
                            findings.append(report)
                        else:
                            included_resources = []

                            if defender_client.inbound_spam_rules[
                                policy.identity
                            ].users:
                                included_resources.append(
                                    f"users: {', '.join(defender_client.inbound_spam_rules[policy.identity].users)}"
                                )
                            if defender_client.inbound_spam_rules[
                                policy.identity
                            ].groups:
                                included_resources.append(
                                    f"groups: {', '.join(defender_client.inbound_spam_rules[policy.identity].groups)}"
                                )
                            if defender_client.inbound_spam_rules[
                                policy.identity
                            ].domains:
                                included_resources.append(
                                    f"domains: {', '.join(defender_client.inbound_spam_rules[policy.identity].domains)}"
                                )

                            included_resources_str = "; ".join(included_resources)
                            priority = defender_client.inbound_spam_rules[
                                policy.identity
                            ].priority

                            if default_policy_well_configured:
                                # Case 2: Both default and custom policies do not contain allowed domains
                                report.status = "PASS"
                                report.status_extended = (
                                    f"Custom Inbound Spam policy {policy.identity} does not contain allowed domains and includes {included_resources_str}, "
                                    f"with priority {priority} (0 is the highest). Also, the default policy does not contain allowed domains, "
                                    "so entities not included by this custom policy could still be correctly protected."
                                )
                            else:
                                # Case 6: Default policy contains allowed domains, custom policy does not
                                report.status = "PASS"
                                report.status_extended = (
                                    f"Custom Inbound Spam policy {policy.identity} does not contain allowed domains and includes {included_resources_str}, "
                                    f"with priority {priority} (0 is the highest). However, the default policy contains allowed domains, "
                                    "so entities not included by this custom policy could not be correctly protected."
                                )
                            findings.append(report)

        return findings

    def _has_no_allowed_domains(self, policy) -> bool:
        """
        Check if the policy has no allowed domains.

        Args:
            policy: The inbound spam policy to check.

        Returns:
            bool: True if the policy has no allowed domains, False otherwise.
        """
        return (
            policy.default
            or defender_client.inbound_spam_rules[policy.identity].state.lower()
            == "enabled"
        ) and not policy.allowed_sender_domains