Block Mail-Rule and Webhook Persistence
Unconditionally denies the classic business-email-compromise (BEC) persistence surface in Microsoft 365: creating or updating Outlook mail rules, changing…
- Direction
- ingress
- Rego package
ms365.ingress.guard_mailbox_persistence- App
- ms365
- Bundle
- soc2
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:3307b4d034a1506135787bca00e71ce57bc7d91f601db73238dc1e32f6dd827f
ms365guard-mailbox-persistenceingressbecemailfinserv-commssoc2
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny on match, allow otherwise
Package: ms365.ingress.guard_mailbox_persistence
What it does
Unconditionally denies the classic business-email-compromise (BEC) persistence surface in Microsoft 365: creating or updating Outlook mail rules, changing mailbox settings, and creating Microsoft Graph change-notification subscriptions (webhooks). All other tool calls pass through unchanged.
Mail rules can silently auto-forward incoming mail to an external address or delete/hide it (the hide-and-forward filter attackers plant after a compromise). Mailbox-settings changes can enable automatic external forwarding for the whole mailbox. A Graph subscription is a standing webhook that streams change notifications to an attacker-controlled URL — a persistent exfiltration channel that survives the session that created it.
Agents have no legitimate reason to configure any of these. This policy is therefore deny-for-everyone: there is no group exemption and no role check. A compromised or prompt-injected agent gets a hard stop, not a permission ladder to climb. The deny reason flags the attempt for security review — treat every denial from this policy as a log-and-alert signal, not routine noise.
Compliance alignment
- SEC 17a-4(b) / FINRA 4511(c) — supports record-integrity requirements: mail rules that auto-delete or divert incoming correspondence would silently destroy or reroute business communications before they can be preserved; blocking rule creation on the agent path supports record anti-destruction.
- SOC 2 CC6.6 — supports boundary protection against external threats: mail rules and Graph change-notification subscriptions are the standing channels a compromised agent uses to auto-forward or stream tenant content to an outside endpoint, and denying their creation on the agent path closes that boundary hole.
- GDPR Art. 32 / Art. 5(1)(f) — supports security of processing: an auto-forwarding mail rule or a webhook subscription is a persistent personal-data exfiltration channel, so blocking its creation on the agent channel reduces the risk of unauthorized disclosure of personal data.
- Anti-BEC hardening generally: mailbox-rule and forwarding persistence is the most common post-compromise action in real-world BEC incidents, so denying it on the agent channel removes a high-value attacker foothold.
Tool name matching
The policy matches tools case-insensitively by verb-noun suffix:
*create-mail-rule*update-mail-rule*update-mailbox-settings*create-subscription(the Graph change-notification webhook)
These suffixes are verified names from the softeria ms-365-mcp-server inventory (observed live behind a gateway with the ms365- prefix, e.g. ms365-create-mail-rule). The DTwo gateway prefixes tool names with the configured MCP server name, and that prefix — and the separator it uses — is not standardized, so the policy anchors on the bare verb-noun suffix (no leading -). This matches whether the gateway joins the prefix with a hyphen (ms365-create-mail-rule), a dot or underscore (ms365.create-mail-rule, ms365_create-mail-rule), or exposes the unprefixed name (create-mail-rule). No benign ms365 tool ends in these suffixes — the read counterparts are list-mail-rules, get-mailbox-settings, and get/list-subscription(s) — so there are no false positives. Verify the exact names your gateway sends with the dump-input debug technique before relying on this in production.
Argument shape
None inspected. The deny is keyed entirely on the tool name — arguments are irrelevant because no invocation of these tools is acceptable. Calls with missing or empty args are still denied (fail closed on the name match alone).
Examples
Allowed
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "ms365-list-mail-rules", "type": "tool" },
"payload": { "name": "ms365-list-mail-rules", "args": {} }
}
}
allow = true, no reason — reading existing rules is fine (and useful for detection).
Denied
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "ms365-create-mail-rule", "type": "tool" },
"payload": {
"name": "ms365-create-mail-rule",
"args": { "body": { "displayName": "sync", "actions": { "forwardTo": [ /* external address */ ], "delete": true } } }
}
}
}
allow = false, denied with the security-review reason below.
Composition
apps/ms365/freeze-destructive-ops— covers the companion surfaces*-delete-mail-ruleand*-delete-subscription(destroying rules/subscriptions is a destructive op, not a persistence op, so it lives there).apps/ms365/role-gate-writes— the outer least-privilege write gate; this policy is the unconditional inner wall for the BEC surface specifically.apps/ms365/deny-graph-batch— must ship together with this policy.graph-batchcan reach the same Graph endpoints (/me/mailFolders/inbox/messageRules,/subscriptions,/me/mailboxSettings) without touching these tool names.
Known limitations
graph-batchbypass if unaccompanied. This policy matches tool names only. Ifdeny-graph-batchis not attached to the same pipeline, a batched Graph request can create the same mail rule or subscription this policy blocks. Attach both.- Generic passthrough servers. Lokka-style servers (
Lokka-Microsoft) expose one dynamic tool whose name never matches these suffixes; they need argument-level (method/path) policies instead. - Existing subscriptions are not torn down.
*-update-subscriptionand*-reauthorize-subscription(which extend the lifetime of an already-existing webhook, but cannot create a new one or change its notification URL) are left torole-gate-writes; this policy only blocks the creation of new persistence. - Focused-inbox overrides are out of scope.
*-create-focused-inbox-override(and itsupdate-/delete-siblings) plant a standing rule that pins a chosen sender to the Focused or Other tab. This is a weaker persistence surface than a mail rule — it only reclassifies which tab mail lands in and cannot forward externally or delete — but an attacker can use it to bury a legitimate vendor's fraud-warning mail in Other. This policy deliberately covers only mail rules, mailbox settings, and subscriptions, so a focused-inbox override passes through here; gate it viarole-gate-writes(least-privilege write gate) instead. - Suffix anchoring. Matching is anchored on the bare verb-noun suffix (
create-mail-rule, not-create-mail-rule), so it catches prefixed names regardless of the separator the gateway uses (-,.,_) as well as the unprefixed namecreate-mail-rule. A server that renames the tool entirely (e.g.add-inbox-filter) would still slip past — verify your gateway's naming with the dump-input technique. Combined with thedefault-deny-unknown-tools(PF-28) allowlist this residual is closed. - MCP path only. Rules created via Outlook, OWA, PowerShell, or direct Graph API calls are outside the gateway's reach.
Compliance note. This policy supports alignment with the cited framework controls on the MCP path only. No policy or bundle makes an organization compliant with any framework; web-UI, native-API, and in-app access are outside the gateway's reach by design. Validate against your own compliance program before relying on it.
Policy source (Rego)
package ms365.ingress.guard_mailbox_persistence
# Deny-by-default: only the explicit allow rule below permits the request.
default allow := false
# Mailbox-persistence surfaces (classic BEC foothold). The gateway prefixes
# tool names with the configured MCP server name (observed live as `ms365-`),
# so we match case-insensitively on the verb-noun suffix WITHOUT requiring a
# specific prefix separator. Anchoring on the bare `create-mail-rule` suffix
# (rather than `-create-mail-rule`) also catches gateways that join the prefix
# with a non-hyphen separator (`ms365.create-mail-rule`, `ms365_create-mail-rule`)
# and servers that expose the unprefixed tool name — all of which would slip past
# a leading-hyphen anchor. No benign ms365 tool ends in these verb-noun suffixes
# (reads are `list-mail-rules`, `get-mailbox-settings`, `get/list-subscription(s)`),
# so dropping the hyphen adds no false positives.
# Creates an Outlook inbox rule (can auto-forward or silently delete mail).
is_persistence_tool if {
endswith(lower(input.resource.name), "create-mail-rule")
}
# Rewrites an existing inbox rule (same forward/hide capability).
is_persistence_tool if {
endswith(lower(input.resource.name), "update-mail-rule")
}
# Changes mailbox settings (can enable automatic external forwarding).
is_persistence_tool if {
endswith(lower(input.resource.name), "update-mailbox-settings")
}
# Creates a Graph change-notification webhook (standing exfiltration channel).
is_persistence_tool if {
endswith(lower(input.resource.name), "create-subscription")
}
# Allow everything that is not a mailbox-persistence tool.
allow if {
not is_persistence_tool
}
# No allow rule exists for persistence tools: the deny is unconditional.
# There is deliberately no group exemption — agents never configure
# mail rules, mailbox settings, or webhooks.
reasons contains "Mailbox rules, mailbox settings, and Graph change-notification subscriptions must be configured by a human in Outlook or the Microsoft 365 admin center — agents are not permitted to create or modify them. This attempt has been flagged for security review. Contact your InfoSec team if you believe this block is a mistake." if {
is_persistence_tool
}
reason := joined if {
count(reasons) > 0
reason_list := sort([r | some r in reasons])
joined := concat("; ", reason_list)
} Canonical source: policy.md on GitHub · raw · raw on this site (.md)
Used in these guides
Related policies
Airtable: Redact PII in Record Reads
Scans the responses of the Airtable record-read tools — the calls that return row fields values — and rewrites high-confidence PII shapes to a fixed…
Asana: Redact PII in Task & Comment Reads
On the Asana MCP read path, this transform scans the free-text business fields that ride back in task, comment/story, and status-update responses — notes,…
BigQuery: Redact PII in Query Results
Scans the content returned by BigQuery's result-returning tools and rewrites high-confidence PII shapes to fixed, non-recoverable redaction tokens before the…
Block Agent Email to External Recipients
Blocks agent-initiated Microsoft 365 email sends when any recipient address falls outside a corporate-domain allowlist.
Block BigQuery Exfiltration and Cross-Project Writes
Inspects the raw GoogleSQL string carried by BigQuery SQL tools and denies any statement that moves data out of the tenant's own project — even when the call…
bigqueryguard-warehouse-exportingresssqlexfiltrationsoc2pci-dssgdpr-ccpa
Block Bulk Export & External Staging (Snowflake)
Blocks Snowflake SQL-execution tool calls whose query text moves whole tables off the Snowflake perimeter — bulk export to cloud storage or a stage, and…
snowflakeguard-warehouse-sqlexportexfiltrationingresssoc2pci-dssgdpr-ccpa