
Open source tooling to stop ICS phishing (malicious calendar invites)
This toolkit for remediating malicious calendar invites is designed to help teams using email security solutions that don't natively remediate these attacks.
If you're using Sublime, you don't need this toolkit because remediation is natively supported.
Stack-specific standalone scripts (e.g., proofpoint-microsoft365.py, mimecast-google-workspace.py) are
provided for each combination of these email security providers and calendar providers:
Email security providers:
Calendar providers:
Go to Quickstart to get right to it!
Not seeing your stack? Don't hesitate to tell us what stack you want supported at [email protected].
These scripts require an email security solution that is already detecting ICS phishing emails, but that is not removing the calendar events created through those emails.
If you don't have an email security solution detecting ICS phishing, Sublime Core offers free and self-hostable email security with built-in ICS phishing detection and automatic calendar remediation.
ICS phishing is a two-pronged attack that delivers malicious payloads to both a user's inbox and calendar simultaneously. This attack vector saw a ~100x increase from Q1 to Q4 2025, per Sublime's 2026 Email Threat Research Report, with over 20% of Q4 callback phishing attacks using calendar invites as a delivery mechanism.
The critical security gap: calendar events persist even when email security solutions quarantine or delete the malicious email. This affects both secure email gateways (SEGs) and API-based email security solutions, most of which do not clean up malicious calendar events as of February 2026.
Based on Sublime's built-in remediation of malicious calendar events, these Python scripts delete malicious events when your email security solution quarantines ICS phishing emails. The scripts:
.ics calendar attachments and other iCalendar-compliant
attachments from those messagesAs pure Python, you can run these scripts in many SOARs, including Tines, Splunk SOAR, and Cortex XSOAR.
We're also working on SOAR-specific guides. Please reach out to [email protected] if you need help or want to be notified when we publish documentation for a particular SOAR.
Each standalone script requires:
You can run these scripts in any environment with:
requirements.txt (requests and PyJWT)For production deployments, you'll also want to schedule the script to run regularly, e.g., with a cron job.
The scripts run in dry-run mode by default to prevent accidental deletion of events. See configuration options for how to enable deletion and how to implement custom logic in response to discovery of malicious invitations and events.
export PROOFPOINT_CLIENT_ID="your-client-id"
export PROOFPOINT_CLIENT_SECRET="your-client-secret"
PROOFPOINT_CLIENT_ID: OAuth 2.0 client ID for Proofpoint Cloud Threat Response APIPROOFPOINT_CLIENT_SECRET: OAuth 2.0 client secretexport MIMECAST_CLIENT_ID="your-client-id"
export MIMECAST_CLIENT_SECRET="your-client-secret"
export MIMECAST_REGION="global" # Options: global, us, uk (default: global)
MIMECAST_CLIENT_ID: OAuth 2.0 client ID for Mimecast API 2.0MIMECAST_CLIENT_SECRET: OAuth 2.0 client secretMIMECAST_REGION: API region endpoint (optional, defaults to global)Required API Permissions:
Services | Threat Remediation | ReadGateway | Tracking | ReadArchive | Readexport ABNORMAL_API_KEY="your-api-key"
ABNORMAL_API_KEY: API key from Abnormal Security platform# Option 1: Service Account File
export GOOGLE_SERVICE_ACCOUNT_FILE="/path/to/service-account.json"
# Option 2: Service Account JSON (inline)
export GOOGLE_SERVICE_ACCOUNT_JSON='{"type":"service_account",...}'
GOOGLE_SERVICE_ACCOUNT_FILE: Path to service account JSON key fileGOOGLE_SERVICE_ACCOUNT_JSON: Inline service account credentials as JSON stringRequired Setup:
https://www.googleapis.com/auth/calendar.eventsexport MICROSOFT_CLIENT_ID="your-client-id"
export MICROSOFT_CLIENT_SECRET="your-client-secret"
export MICROSOFT_TENANT_ID="your-tenant-id"
MICROSOFT_CLIENT_ID: Azure AD application (client) IDMICROSOFT_CLIENT_SECRET: Azure AD application client secretMICROSOFT_TENANT_ID: Azure AD directory (tenant) IDRequired API Permission: Calendars.ReadWrite (Application permission)
export ICS_PHISHING_REMEDIATION_MODE="dry_run" # "dry_run" is default
export MESSAGE_LOOKBACK_MINUTES=10
export PROOFPOINT_CLIENT_ID="your-client-id"
export PROOFPOINT_CLIENT_SECRET="your-secret"
export MICROSOFT_TENANT_ID="your-tenant"
export MICROSOFT_CLIENT_ID="your-client"
export MICROSOFT_CLIENT_SECRET="your-secret"
python3 proofpoint-microsoft365.py
By default, all scripts run in dry-run mode to prevent accidental deletions. In this mode, the script will:
This allows you to verify the script is working correctly before enabling actual deletion.
To actually delete calendar events, set the ICS_PHISHING_REMEDIATION_MODE environment variable:
export ICS_PHISHING_REMEDIATION_MODE="delete_events"
Valid values:
dry_run (default) - Preview actions without deletingdelete_events - Actually delete matching calendar eventsInvalid values will raise an error to prevent misconfiguration.
All scripts search for quarantined messages within a configurable time window:
export MESSAGE_LOOKBACK_MINUTES=10 # Optional, defaults to 5 minutes
Important: This value should match your cron schedule frequency with a small buffer to ensure no messages are missed. For example, if running every 5 minutes, set the lookback window to 10 minutes.
For additional integrations, such as with logging systems or monitoring tools, you can implement custom callbacks in
your script and pass them to the delete_events_from_remediated_emails function:
on_invitation_found_callback(invitation: CalendarInvitation) - Called when a calendar invitation is found in a
quarantined messageon_event_found_callback(invitation: CalendarInvitation, event: CalendarEvent) - Called for each matching calendar eventExample:
def log_invitation(invitation):
print(f"Found malicious invitation for {invitation.user_id}, UID: {invitation.ics_uid}")
def log_event(invitation, event):
print(f"Found calendar event {event.id} matching invitation {invitation.ics_uid}")
fetcher = ProofpointCloudFetcher()
deleter = GoogleWorkspaceEventDeleter()
delete_events_from_remediated_emails(
fetcher,
deleter,
mode="delete_events", # Override default dry_run mode
on_invitation_found_callback=log_invitation,
on_event_found_callback=log_event
)
Note: Errors in callbacks are logged but do not stop event processing.
All scripts follow a common pattern defined in common.py:
class MessageFetcher:
def yield_remediated_emails(self) -> Iterator[Message]:
"""Fetch quarantined messages from your email security solution"""
class EventDeleter:
def delete_matching_events(
self,
invitation: CalendarInvitation,
mode: Mode,
on_event_found_callback: Optional[Callable[[CalendarInvitation, CalendarEvent], None]]
) -> None:
"""Delete calendar events matching the ICS attachment"""
Stack-specific implementations handle API authentication and provider-specific logic.