Email Security Scanner Clicks AI Agent Approval Links A developer's writeup warns that email security scanners which pre-fetch links in messages can silently approve actions for AI agents when approval is implemented as a GET request, since the scanner's fetch is indistinguishable from a human click. The piece cites Microsoft Defender for Office 365 Safe Links, which logs a 'ClickedDuringScan' action, and RFC 8058, which mandates one-click unsubscribe via HTTPS POST precisely because anti-spam software fetches URLs automatically. The recommended fix is to make agent approval a POST submitted from a confirmation page, mirroring the RFC 8058 approach, rather than relying on bot detection or scanner filtering. Originally published on robatdasorvi.com https://www.robatdasorvi.com/chapters/ai-agents/email-scanners-auto-click-ai-agent-approval-links Your email security scanner may be approving things for your AI agent. It fetches each link in a message to check for malware, and that GET request looks exactly like a person clicking. If approval happens on that GET, a machine has said yes. The fix is to make approval a POST, sent from a confirmation page that a scanner has no reason to submit. Picture the approval log for an agent that asks permission by email. Every row is a link. Every row says approved. Most timestamps sit a few seconds after delivery. The log is accurate, as far as it goes. It records that a URL was requested. It has no column for who requested it, and nothing in it says whether a human ever read the message. Email gateways fetch URLs in messages to judge whether they're safe, whether or not anyone opens the message. The scanner doesn't care about your agent. It just wants to be thorough about the link. Microsoft documents this in Defender for Office 365: Safe Links can scan URLs in a message before delivery and again at click time Microsoft Learn https://learn.microsoft.com/defender-office-365/safe-links-about . Its reporting cmdlet Get-SafeLinksDetailReport even records an action called ClickedDuringScan , for a user who clicked while a scan was still running Microsoft Learn https://learn.microsoft.com/powershell/module/exchange/Get-SafeLinksDetailReport . The primary-source precedent for this fix is RFC 8058. It specifies one-click unsubscribe as an HTTPS POST, because anti-spam software often fetches URLs in mail header fields automatically, and a sender cannot tell an automated request from a person's RFC 8058 https://www.rfc-editor.org/rfc/rfc8058 . Approval by POST uses the same move. Identity vendors document the same failure. Stytch's consumer docs say each magic link is one-time use, and a real login consumes the embedded token Stytch https://stytch.com/docs/consumer-auth/authentication/magic-links/overview . Its B2B guide describes the failure directly: a scanner can consume the token before the email reaches the inbox Stytch B2B guide https://stytch.com/docs/b2b/guides/magic-links/overview . Supabase users have reported the same thing. In a user report posted on December 27, 2025, magic links and password reset links were described as "frequently consumed by automated email scanners in enterprise, university, and hospital environments", so the token was already invalid when the user clicked Supabase discussion 41618 https://github.com/orgs/supabase/discussions/41618 . The discussion is a user report, not a vendor postmortem. This isn't only an auth-vendor problem. An approval link is a token that means "yes, do the thing," and a scanner that consumes it has said yes on your behalf. If the approval is a GET, yes. The server can't tell the difference, so the scanner's click gets recorded as consent. It's tempting to treat this as a bot-detection problem and ask how to recognize scanner traffic. That question has answers, but it's the wrong one. Approval shouldn't depend on identifying the requester at all. Stytch's consumer overview says that when a Magic Link has been clicked by an email security scanner, "we do not treat the click as an authentication attempt - meaning the token is not consumed, and a session is not granted" Stytch https://stytch.com/docs/consumer-auth/authentication/magic-links/overview . It says this "comes out of the box for all Stytch customers", and the B2B guide describes the same device-intelligence approach Stytch B2B guide https://stytch.com/docs/b2b/guides/magic-links/overview . That works because Stytch owns the login flow. An agent platform that writes its own approval endpoint doesn't get that behavior for free. No. Filtering is a moving target, and the share of non-human clicks is going up. Omnivery, which sells bot detection, reports that bots rose from 33.8% of the clicks its Bot Detection API screened in 2025 to about 48% in the first half of 2026 Omnivery, The State of Email Bots 2026 https://omnivery.com/blog/the-state-of-email-bots-2026 . The same report's chart "Gmail bot-click share" shows the share of consumer Gmail clicks that were bots jumping to about 22% in late February 2026, after holding around 6% through 2025 and early 2026. These are one vendor's samples, not a census. The direction is what matters. A user-agent blocklist written in 2025 is already stale. Timing filters fail in both directions. Microsoft's ClickedDuringScan action shows that humans sometimes click while a scan is still running, so a rule like "a click within two seconds is a scanner" will likely reject some real people. That is an inference, not something Microsoft measures. Expiry doesn't save you. A five-minute TTL does nothing when the scanner arrives in the first five seconds, and the token is spent before the human opens the inbox. So the approval has to be a different kind of request. The GET handler only renders a confirmation page and changes no state; the POST handler performs the action, and only with a valid, unspent nonce. python @app.get "/approve/