Detection rules › Panther

Panther rules: gsuite

RuleSeverity
External GSuite File Sharehigh
Gmail Malicious SMTP Responsehigh
Gmail Potential Spoofed Email Deliveredhigh
Google Accessed a GSuite Resourcelow
Google Drive High Download Countmedium
Google Workspace Login Type Anomaly
Google Workspace Login Type Anomalymedium
Google Workspace OAuth Anomalous Privileged Request
Google Workspace OAuth Application Authorized with Privileged Scopesinformational
Google Workspace OAuth Token Requests from New IPmedium
Google Workspace OAuth Token Requests from New IPs
Google Workspace OAuthLogin Scope Anomalous Application Access
Google Workspace Rapid Multi-IP Authentication
Google Workspace Rapid Multi-IP Authenticationmedium
Gsuite Attachments Downloaded from Spam Emailhigh
GSuite Calendar Has Been Made Publicmedium
GSuite Device Suspicious Activitylow
GSuite Document External Ownership Transferlow
GSuite Drive Many Documents Deletedmedium
GSuite Drive Many Documents Deletedmedium
Gsuite Email Bypassed Spam Filtermedium
GSuite External Drive Documentlow
GSuite Government Backed Attackcritical
Gsuite Link Clicked in Spam Emailhigh
GSuite Login Typemedium
Gsuite Mail forwarded to external domainmedium
GSuite Many Docs Deleted Query
GSuite Many Docs Downloaded Query
GSuite Overly Visible Drive Documentinformational
GSuite Passthrough Rule Triggeredinformational
GSuite User Advanced Protection Changelow
GSuite User Banned from Grouplow
GSuite User Device Compromisedmedium
GSuite User Device Unlock Failuresmedium
GSuite User Password Leakedhigh
GSuite User Suspendedhigh
GSuite User Two Step Verification Changelow
GSuite Workspace Calendar External Sharing Setting Changemedium
GSuite Workspace Data Export Has Been Createdmedium
GSuite Workspace Gmail Default Routing Rule Modifiedhigh
GSuite Workspace Gmail Pre-Delivery Message Scanning Disabledmedium
GSuite Workspace Gmail Security Sandbox Disabledmedium
GSuite Workspace Password Reuse Has Been Enabledhigh
GSuite Workspace Strong Password Enforcement Has Been Disabledhigh
GSuite Workspace Trusted Domain Allowlist Modifiedmedium
Malware Detected in Emailhigh
Spam Email Surgemedium
Suspicious GSuite Loginmedium
Suspicious is_suspicious taginformational

External GSuite File Share

#
Severity
high
Log types
GSuite.ActivityEvent
Tags
GSuite, Security Control, Configuration Required, Collection:Data from Information Repositories
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

An employee shared a sensitive file externally with another organization

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Drive (any event)

Rules detecting the same action

These rules filter on the same operation.

Detection logic

import datetime

from panther_base_helpers import pattern_match, pattern_match_list

COMPANY_DOMAIN = "your-company-name.com"
EXCEPTION_PATTERNS = {
    # The glob pattern for the document title (lowercased)
    "1 document title p*": {  # allow any title "all"
        "allowed_to_send": {
            "alice@acme.com",
            "samuel@acme.com",
            "nathan@acme.com",
            "barry@acme.com",
            # Allow any user
            # "all"
            # Allow any user in a specific domain
            # "*@acme.com"
        },
        "allowed_to_receive": {
            "alice@abc.com",
            "samuel@abc.com",
            "nathan@abc.com",
            "barry@abc.com",
            # Allow any user
            # "all"
            # Allow any user in a specific domain
            # "*@acme.com"
        },
        # The time limit for how long the file share stays valid
        "allowed_until": datetime.datetime(year=2030, month=6, day=2),
    },
    "2 document title p*": {
        "allowed_to_send": {
            "alice@abc.com",
        },
        "allowed_to_receive": {
            "*@acme.com",
        },
        # The time limit for how long the file share stays valid
        "allowed_until": datetime.datetime(year=2030, month=6, day=2),
    },
}


def _check_acl_change_event(actor_email, event):
    # For GSuite.ActivityEvent, parameters is a dict, not an array
    parameters = event.get("parameters", {})

    doc_title = parameters.get("doc_title", "TITLE_UNKNOWN")
    old_visibility = parameters.get("old_visibility", "OLD_VISIBILITY_UNKNOWN")
    new_visibility = parameters.get("visibility", "NEW_VISIBILITY_UNKNOWN")
    target_user = parameters.get("target_user") or parameters.get("target_domain") or "USER_UNKNOWN"
    current_time = datetime.datetime.now()

    if (
        new_visibility == "shared_externally"
        and old_visibility == "private"
        and not target_user.endswith(f"@{COMPANY_DOMAIN}")
    ):
        # This is a dangerous share, check exceptions:

        for pattern, details in EXCEPTION_PATTERNS.items():
            proper_title = pattern_match(doc_title.lower(), pattern) or pattern == "all"

            proper_sender = pattern_match_list(
                actor_email, details.get("allowed_to_send")
            ) or details.get("allowed_to_send") == {"all"}

            proper_receiver = pattern_match_list(
                target_user, details.get("allowed_to_receive")
            ) or details.get("allowed_to_receive") == {"all"}

            if (
                proper_title
                and proper_sender
                and proper_receiver
                and current_time < details.get("allowed_until")
            ):
                return False
        # No exceptions match.
        # Return the event summary (which is True) to alert & use in title.
        return {
            "actor": actor_email,
            "doc_title": doc_title,
            "target_user": target_user,
        }
    return False


def rule(event):
    application_name = event.deep_get("id", "applicationName")
    actor_email = event.deep_get("actor", "email", default="EMAIL_UNKNOWN")

    # For GSuite.ActivityEvent, each log is a single event (no events array)
    if application_name == "drive" and event.get("type") == "acl_change":
        # If this event is a dangerous file share, alert:
        return bool(_check_acl_change_event(actor_email, event))
    return False


def title(event):
    actor_email = event.deep_get("actor", "email", default="EMAIL_UNKNOWN")
    matching_event = _check_acl_change_event(actor_email, event)

    if matching_event:
        actor = matching_event.get("actor", "ACTOR_UNKNOWN")
        doc_title = matching_event.get("doc_title", "DOC_TITLE_UNKNOWN")
        target_user = matching_event.get("target_user", "USER_UNKNOWN")
        return f'Dangerous file share by [{actor}]: "{doc_title}" to {target_user}'
    return "No matching events, but DangerousShares still fired"

Rule specification

AnalysisType: rule
Filename: gsuite_drive_external_share.py
RuleID: "GSuite.Drive.ExternalFileShare"
DisplayName: "External GSuite File Share"
Enabled: false
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Security Control
  - Configuration Required
  - Collection:Data from Information Repositories
Reports:
  MITRE ATT&CK:
    - TA0009:T1213
Severity: High
Description: An employee shared a sensitive file externally with another organization
Runbook: |
  Contact the employee who made the share and make sure they redact the access.
  If the share was legitimate, add to the EXCEPTION_PATTERNS in the detection.
Reference: https://support.google.com/docs/answer/2494822?hl=en&co=GENIE.Platform%3DiOS&sjid=864417124752637253-EU

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is drive
  • type is acl_change
  • parameters.visibility is shared_externally
  • parameters.old_visibility is private

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Indicators

These rows show field, operator, and value matches.

Response runbook

Contact the employee who made the share and make sure they redact the access.

If the share was legitimate, add to the EXCEPTION_PATTERNS in the detection.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "example@acme.com",
    "profileId": "1111111111111111111"
  },
  "id": {
    "applicationName": "drive",
    "customerId": "C010qxghg",
    "time": "2020-09-07T15:50:49.617Z",
    "uniqueQualifier": "1111111111111111111"
  },
  "kind": "admin#reports#activity",
  "name": "change_user_access",
  "p_log_type": "GSuite.ActivityEvent",
  "parameters": {
    "doc_id": "1111111111111111111",
    "doc_title": "1 Document Title Primary",
    "doc_type": "document",
    "new_value": [
      "can_edit"
    ],
    "old_value": [
      "none"
    ],
    "old_visibility": "private",
    "originating_app_id": "1111111111111111111",
    "owner_is_shared_drive": false,
    "owner_is_team_drive": false,
    "primary_event": true,
    "target_user": "outside@acme.com",
    "visibility": "shared_externally",
    "visibility_change": "external"
  },
  "type": "acl_change"
}

Gmail Malicious SMTP Response

#
Severity
high
Entities
ip_addresses
Log types
GSuite.ActivityEvent
Tags
GSuite, Gmail, Email Security, Malware, Spam
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

Detects when Gmail blocks or rejects emails due to malicious SMTP response reasons including malware detection, spam/phishing links, low sender reputation, RBL listings, or denial of service attempts. This rule monitors inbound SMTP connections for security threats that Gmail's filters identify.

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Gmail (any event)

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context

# SMTP response reasons that indicate security threats
MALICIOUS_SMTP_RESPONSES = {
    3: "Malware",
    13: "Blatant Spam",
    14: "Denial of Service",
    15: "Malicious or Spam Links",
    16: "Low IP Reputation",
    17: "Low Domain Reputation",
    18: "IP address listed in public real-time block list",
}


def rule(event):
    if event.deep_get("id", "applicationName", default="<UNKNOWN_APPLICATION>") != "gmail":
        return False

    smtp_response_reason = event.deep_get(
        "parameters", "message_info", "connection_info", "smtp_response_reason", default=0
    )

    return smtp_response_reason in MALICIOUS_SMTP_RESPONSES


def title(event):
    user = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    smtp_response_reason = event.deep_get(
        "parameters", "message_info", "connection_info", "smtp_response_reason", default=0
    )
    reason_description = MALICIOUS_SMTP_RESPONSES.get(
        smtp_response_reason, f"Unknown ({smtp_response_reason})"
    )

    sender = event.deep_get(
        "parameters", "message_info", "source", "address", default="<UNKNOWN_SENDER>"
    )

    return f"Gmail blocked email to [{user}] from [{sender}] due to: {reason_description}"


def alert_context(event):
    context = gsuite_activityevent_alert_context(event)

    # Add specific SMTP response information
    smtp_response_reason = event.deep_get(
        "parameters", "message_info", "connection_info", "smtp_response_reason", default=0
    )

    context.update(
        {
            "smtp_response_reason_code": smtp_response_reason,
            "smtp_response_reason": MALICIOUS_SMTP_RESPONSES.get(
                smtp_response_reason, f"Unknown ({smtp_response_reason})"
            ),
            "smtp_reply_code": event.deep_get(
                "parameters", "message_info", "connection_info", "smtp_reply_code", default=0
            ),
        }
    )

    return context

Rule specification

AnalysisType: rule
Filename: gsuite_malicious_smtp_response.py
RuleID: "GSuite.Gmail.Malicious.SMTP.Response"
DisplayName: "Gmail Malicious SMTP Response"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Gmail
  - Email Security
  - Malware
  - Spam
Reports:
  MITRE ATT&CK:
    - TA0001:T1566 # Initial Access: Phishing
Severity: High
Description: >
  Detects when Gmail blocks or rejects emails due to malicious SMTP response reasons including
  malware detection, spam/phishing links, low sender reputation, RBL listings, or denial of service attempts.
  This rule monitors inbound SMTP connections for security threats that Gmail's filters identify.
Reference: https://support.google.com/a/answer/12384955
Runbook: |
  1. Review the sender's email address and domain
  2. Check the SMTP response reason and reply code for details
  3. Investigate the sender's IP address for additional context (geolocation, reputation)
  4. Review authentication status (SPF, DKIM, DMARC)
  5. If malware was detected, check if similar messages were received by other users
  6. Consider adding sender to blocklist if pattern of malicious activity is confirmed
  7. For DoS attempts, review firewall and rate limiting configurations
DedupPeriodMinutes: 60
SummaryAttributes:
  - user_email
  - sender_address
  - smtp_response_reason

Stages and Predicates

Fires on GSuite.ActivityEvent events when the condition below holds.

Condition

  • id.applicationName is gmail

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters
addressparameters.message_info.source.address

Response runbook

1. Review the sender's email address and domain

2. Check the SMTP response reason and reply code for details

3. Investigate the sender's IP address for additional context (geolocation, reputation)

4. Review authentication status (SPF, DKIM, DMARC)

5. If malware was detected, check if similar messages were received by other users

6. Consider adding sender to blocklist if pattern of malicious activity is confirmed

7. For DoS attempts, review firewall and rate limiting configurations

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "frodo@lotr.com",
    "profileId": "123456789"
  },
  "id": {
    "applicationName": "gmail",
    "customerId": "C01abc123",
    "time": "2025-11-04 20:44:43.248000000",
    "uniqueQualifier": "-123456789"
  },
  "ipAddress": "1.1.1.1",
  "kind": "admin#reports#activity",
  "name": "delivery",
  "p_any_ip_addresses": [
    "1.1.1.1"
  ],
  "p_event_time": "2025-11-04 20:44:43.248000000",
  "p_log_type": "GSuite.ActivityEvent",
  "parameters": {
    "event_info": {
      "elapsed_time_usec": 368746,
      "success": false,
      "timestamp_usec": 1730751883248347
    },
    "message_info": {
      "action_type": 2,
      "connection_info": {
        "client_ip": "1.1.1.1",
        "dkim_pass": false,
        "dmarc_pass": false,
        "ip_geo_country": "XX",
        "is_internal": false,
        "smtp_reply_code": 550,
        "smtp_response_reason": 3,
        "spf_pass": false
      },
      "destination": [
        {
          "address": "frodo@lotr.com",
          "service": "gmail-ui"
        }
      ],
      "source": {
        "address": "eve@lexcorp.com",
        "from_header_address": "eve@lexcorp.com"
      },
      "subject": "Invoice Attached - Please Review"
    }
  },
  "type": "message_delivery"
}

Gmail Potential Spoofed Email Delivered

#
Severity
high
Entities
ip_addresses
Log types
GSuite.ActivityEvent
Tags
GSuite, Gmail, Email Security, Spoofing, Phishing
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

Detects when a potentially spoofed email was successfully delivered to a user's inbox despite failing email authentication checks. This rule triggers when: 1. DMARC authentication fails, OR 2. Both SPF and DKIM authentication fail simultaneously These authentication failures indicate the sender may be impersonating a legitimate domain, which is a common tactic in phishing and business email compromise (BEC) attacks.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Gmail (any event)

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):

    if event.deep_get("id", "applicationName", default="<UNKNOWN_APPLICATION>") != "gmail":
        return False

    dmarc_passed = event.deep_get(
        "parameters",
        "message_info",
        "connection_info",
        "dmarc_pass",
        default="<UNKNOWN_DMARC_PASS>",
    )
    spf_passed = event.deep_get(
        "parameters", "message_info", "connection_info", "spf_pass", default="<UNKNOWN_SPF_PASS>"
    )
    dkim_passed = event.deep_get(
        "parameters",
        "message_info",
        "connection_info",
        "dkim_pass",
        default="<UNKNOWN_DKIM_PASS>",
    )

    event_success = event.deep_get("parameters", "event_info", "success", default=False)

    if event_success is True:  # Message was delivered despite failures
        if dmarc_passed is False:
            return True
        if spf_passed is False and dkim_passed is False:
            return True
    return False


def title(event):
    user = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    return f"[{user}] received a potentially spoofed email"


def alert_context(event):
    context = gsuite_activityevent_alert_context(event)
    return context

Rule specification

AnalysisType: rule
Filename: gsuite_potential_spoofed_email.py
RuleID: "GSuite.Gmail.Potential.Spoofed.Email"
DisplayName: "Gmail Potential Spoofed Email Delivered"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Gmail
  - Email Security
  - Spoofing
  - Phishing
Reports:
  MITRE ATT&CK:
    - TA0001:T1566.001 # Initial Access: Phishing - Spearphishing Attachment
    - TA0001:T1566.002 # Initial Access: Phishing - Spearphishing Link
Severity: High
Description: >
  Detects when a potentially spoofed email was successfully delivered to a user's inbox despite
  failing email authentication checks. This rule triggers when:

  1. DMARC authentication fails, OR
  2. Both SPF and DKIM authentication fail simultaneously

  These authentication failures indicate the sender may be impersonating a legitimate domain,
  which is a common tactic in phishing and business email compromise (BEC) attacks.
Reference: https://support.google.com/a/answer/12384955
Runbook: |
  1. Review the sender's email address and compare with the From: header display name
  2. Check if the sender domain is impersonating an internal or partner domain
  3. Verify the authentication status details (SPF, DKIM, DMARC)
  4. Review the message subject and content if available
  5. Check the sender's IP geolocation and reputation
  6. Search for similar messages from the same sender to other users
  7. If confirmed as spoofing:
     - Add sender domain/IP to blocklist
     - Remove the message from user's inbox
     - Notify affected users not to interact with the email
  8. Consider strengthening DMARC policy (quarantine/reject) if not already enforced
DedupPeriodMinutes: 60
SummaryAttributes:
  - user_email
  - sender_address
  - dmarc_pass
  - spf_pass
  - dkim_pass

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is gmail
  • parameters.event_info.success is true
  • any of:
    • parameters.message_info.connection_info.dmarc_pass is false
    • all of:
      • parameters.message_info.connection_info.spf_pass is false
      • parameters.message_info.connection_info.dkim_pass is false

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters

Response runbook

1. Review the sender's email address and compare with the From: header display name

2. Check if the sender domain is impersonating an internal or partner domain

3. Verify the authentication status details (SPF, DKIM, DMARC)

4. Review the message subject and content if available

5. Check the sender's IP geolocation and reputation

6. Search for similar messages from the same sender to other users

7. If confirmed as spoofing:

- Add sender domain/IP to blocklist

- Remove the message from user's inbox

- Notify affected users not to interact with the email

8. Consider strengthening DMARC policy (quarantine/reject) if not already enforced

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "frodo@lotr.com",
    "profileId": "123456789"
  },
  "id": {
    "applicationName": "gmail",
    "customerId": "C01abc123",
    "time": "2025-11-05 10:15:30.000000000",
    "uniqueQualifier": "-123456789"
  },
  "ipAddress": "8.8.8.8",
  "kind": "admin#reports#activity",
  "name": "delivery",
  "p_any_ip_addresses": [
    "8.8.8.8"
  ],
  "p_event_time": "2025-11-05 10:15:30.000000000",
  "p_log_type": "GSuite.ActivityEvent",
  "parameters": {
    "event_info": {
      "elapsed_time_usec": 250000,
      "success": true,
      "timestamp_usec": 1730800530000000
    },
    "message_info": {
      "action_type": 2,
      "connection_info": {
        "client_ip": "8.8.8.8",
        "dkim_pass": true,
        "dmarc_pass": false,
        "dmarc_published_domain": "fake-company.com",
        "ip_geo_country": "RU",
        "is_internal": false,
        "spf_pass": true
      },
      "destination": [
        {
          "address": "frodo@lotr.com",
          "service": "gmail-ui"
        }
      ],
      "rfc2822_message_id": "<denethor@lotr.com>",
      "source": {
        "address": "john@justice.org",
        "from_header_address": "john@justice.org",
        "from_header_displayname": "CEO John Smith"
      },
      "subject": "Urgent: Wire Transfer Request"
    }
  },
  "type": "message_delivery"
}

Google Accessed a GSuite Resource

#
Severity
low
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

Google accessed one of your GSuite resources directly, most likely in response to a support incident.

Telemetry coverage

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") != "access_transparency":
        return False

    return bool(event.get("type") == "GSUITE_RESOURCE")

Rule specification

AnalysisType: rule
Filename: gsuite_google_access.py
RuleID: "GSuite.GoogleAccess"
DisplayName: "Google Accessed a GSuite Resource"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: Low
Description: >
  Google accessed one of your GSuite resources directly, most likely in response to a support incident.
Reference: https://support.google.com/a/answer/9230474?hl=en
Runbook: >
  Your GSuite Super Admin can visit the Access Transparency report in the GSuite Admin Dashboard to see more details about the access.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is access_transparency
  • type is GSUITE_RESOURCE

Indicators

These rows show field, operator, and value matches.

Response runbook

Your GSuite Super Admin can visit the Access Transparency report in the GSuite Admin Dashboard to see more details about the access.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "id": {
    "applicationName": "access_transparency"
  },
  "type": "GSUITE_RESOURCE"
}

Google Drive High Download Count

#
Status
Deprecated
Severity
medium
Tags
Deprecated
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

Scheduled rule for the High Google Drive Download Count query which looks for incidents of more than 10 (tunable) downloads by a user in the past day.

Telemetry coverage

PlatformRecord / event type
Google Workspacedownload: Download

Rules detecting the same action

These rules filter on the same operation.

Detection logic

def rule(_):
    return True


def title(event):
    return (
        f"GSuite: [{event.get('user', '<user_not_found>')}] "
        f"downloaded [{event.get('download_count', '<count_not_found>')}] "
        "files from Google Drive."
    )


def alert_context(event):
    return event.to_dict()

Rule specification

AnalysisType: scheduled_rule
Description: Scheduled rule for the High Google Drive Download Count query which looks for incidents of more than 10 (tunable) downloads by a user in the past day.
DisplayName: "Google Drive High Download Count"
Status: Deprecated
Enabled: false
Filename: gsuite_drive_many_docs_downloaded.py
Reference: https://support.google.com/drive/answer/2423534?hl=en&co=GENIE.Platform%3DDesktop
Severity: Medium
Tags:
  - Deprecated
DedupPeriodMinutes: 60
RuleID: "Google.Drive.High.Download.Count"
Threshold: 1
ScheduledQueries:
  - GSuite Many Docs Downloaded Query

Stages and Predicates

Rule logic

This rule alerts on rows returned by its scheduled query GSuite Many Docs Downloaded Query; its Python module (Detection logic above) shapes the alert rather than filtering.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

Field
user
download_count

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "download_count": 23,
  "downloaded_files": [
    "all_hands01.mov",
    "all_hands02.mov",
    "all_hands03.mov",
    "all_hands23.mov"
  ],
  "user": "homer.simpson@simpsons.com"
}

Google Workspace Login Type Anomaly

#
Source
github.com/panther-labs/panther-analysis

Detects users authenticating with login types they haven't used in the past 30 days. May indicate GAIA credential theft where attackers use stolen tokens with different authentication methods than the victim's normal pattern (e.g., google_password instead of SAML).

Rule specification

AnalysisType: scheduled_query
QueryName: "Google Workspace Login Type Anomaly"
Description: |
  Detects users authenticating with login types they haven't used in the past 30 days.
  May indicate GAIA credential theft where attackers use stolen tokens with different
  authentication methods than the victim's normal pattern (e.g., google_password instead of SAML).
Enabled: false
Query: |
  -- pragma: template
  {% import 'anomalies' new_unique_values %}

  WITH subquery AS (
    SELECT
      actor:email AS email,
      parameters:login_type AS login_type,
      ipAddress,
      p_event_time
    FROM panther_logs.public.gsuite_activityevent
    WHERE p_occurs_since('30 days')
      AND id:applicationName = 'login'
      AND name = 'login_success'
      AND parameters:login_type IS NOT NULL
      AND parameters:login_type != 'reauth'
  ),
  {{ new_unique_values('subquery', 'email', 'login_type', '1 day') }}
Schedule:
  RateMinutes: 360
  TimeoutMinutes: 3

Google Workspace Login Type Anomaly

#
Severity
medium
Tags
GSuite, Lateral Movement, Valid Accounts, GAIA
Reference
businessinsights.bitdefender.com
Source
github.com/panther-labs/panther-analysis

Alerts when users authenticate with login types they haven't used in the past 30 days. This may indicate GAIA credential theft where attackers use stolen OAuth tokens with different authentication methods (e.g., google_password instead of SAML). Particularly suspicious when a user who normally uses SSO/SAML suddenly authenticates via password.

MITRE ATT&CK coverage

Detection logic

import re


def normalize_username(email):
    if not email:
        return None
    # Extract username before @ symbol
    username = email.split("@")[0] if "@" in email else email
    # Remove all non-alphanumeric characters and convert to lowercase
    return re.sub(r"[^a-z0-9]", "", username.lower())


def rule(_):
    return True


def title(event):
    user = event.get("email", "<UNKNOWN_USER>")
    login_type = event.get("login_type", "<UNKNOWN_TYPE>")
    return f"Google Workspace: User [{user}] used anomalous login type [{login_type}]"


def severity(event):
    login_type = event.get("login_type", "")

    # Higher severity for password-based auth
    if login_type == "google_password":
        return "HIGH"

    return "MEDIUM"


def alert_context(event):
    email = event.get("email")
    return {
        "user_email": email,
        "username_normalized": normalize_username(email),
        "anomalous_login_type": event.get("login_type"),
        "ip_address": event.get("ipAddress"),
        "description": ("User authenticated with a login type not seen in the previous 30 days"),
    }

Rule specification

AnalysisType: scheduled_rule
DisplayName: "Google Workspace Login Type Anomaly"
DedupPeriodMinutes: 360
RuleID: "Google.Workspace.Login.Type.Anomaly"
Description: >
  Alerts when users authenticate with login types they haven't used in the past 30 days.
  This may indicate GAIA credential theft where attackers use stolen OAuth tokens with
  different authentication methods (e.g., google_password instead of SAML). Particularly
  suspicious when a user who normally uses SSO/SAML suddenly authenticates via password.
ScheduledQueries:
  - Google Workspace Login Type Anomaly
Enabled: false
Filename: gsuite_login_type_anomaly_rule.py
Reference: https://businessinsights.bitdefender.com/the-chain-reaction-new-methods-for-extending-local-breaches-in-google-workspace
Runbook: |
  1. Query GSuite.ActivityEvent for all login events by the user email in the 24 hours before and after the alert to identify login patterns, IP addresses used, and the context around the anomalous login_type
  2. Check if the source IP addresses from recent logins are associated with known VPN services, cloud providers, or corporate network ranges, and compare to the user's typical login locations in the past 30 days
  3. Search for other authentication anomalies or alerts for this user in the past 7 days, including failed logins, OAuth token authorizations, password changes, or suspicious activity warnings
Severity: Medium
Tags:
  - GSuite
  - Lateral Movement
  - Valid Accounts
  - GAIA
Reports:
  MITRE ATT&CK:
    - TA0008:T1078.004
    - TA0006:T1550
SummaryAttributes:
  - email

Stages and Predicates

Rule logic

This rule alerts on rows returned by its scheduled query Google Workspace Login Type Anomaly; its Python module (Detection logic above) shapes the alert rather than filtering.

Alert deduplication
repeat matches within 6h group into one alert

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
user_emailemail
anomalous_login_typelogin_type
ip_addressipAddress

Response runbook

1. Query GSuite.ActivityEvent for all login events by the user email in the 24 hours before and after the alert to identify login patterns, IP addresses used, and the context around the anomalous login_type

2. Check if the source IP addresses from recent logins are associated with known VPN services, cloud providers, or corporate network ranges, and compare to the user's typical login locations in the past 30 days

3. Search for other authentication anomalies or alerts for this user in the past 7 days, including failed logins, OAuth token authorizations, password changes, or suspicious activity warnings

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "email": "user@example.com",
  "ipAddress": "1.1.1.1",
  "login_type": "google_password"
}

Google Workspace OAuth Anomalous Privileged Request

#
Source
github.com/panther-labs/panther-analysis

Detects new OAuth applications authorized with privileged scopes in Google Workspace. Uses anomaly detection to identify users authorizing OAuth apps they haven't used in the past 7 days.

Rule specification

AnalysisType: scheduled_query
QueryName: "Google Workspace OAuth Anomalous Privileged Request"
Description: |
  Detects new OAuth applications authorized with privileged scopes in Google Workspace.
  Uses anomaly detection to identify users authorizing OAuth apps they haven't used in
  the past 7 days.
Enabled: false
Query: |
  -- pragma: template

  {% import 'anomalies' new_unique_values %}
  with subquery as (
      SELECT
        p_event_time,
        actor:email AS actor_email,
        parameters:client_id AS client_id,
        parameters:app_name AS app_name,
        parameters:scope AS scopes,
        ipAddress,
        id:applicationName AS application_name,
        name AS event_name
      FROM panther_logs.public.gsuite_activityevent
      WHERE p_occurs_since('7 day')
      AND id:applicationName = 'token'
      AND name = 'authorize'
      AND (
        LOWER(ARRAY_TO_STRING(parameters:scope, ' ')) LIKE '%admin.directory.user%'
        OR LOWER(ARRAY_TO_STRING(parameters:scope, ' ')) LIKE '%ediscovery%'
        OR LOWER(ARRAY_TO_STRING(parameters:scope, ' ')) LIKE '%drive%'
        OR LOWER(ARRAY_TO_STRING(parameters:scope, ' ')) LIKE '%cloud_search.query%'
      )
      LIMIT 10000
    ),
    {{ new_unique_values('subquery', 'actor_email', 'client_id', '1d') }}
Schedule:
    RateMinutes: 360
    TimeoutMinutes: 3

Google Workspace OAuth Application Authorized with Privileged Scopes

#
Status
Experimental
Severity
informational
Log types
GSuite.ActivityEvent
Tags
GSuite, Initial Access, Persistence, Account Manipulation
Reference
businessinsights.bitdefender.com
Source
github.com/panther-labs/panther-analysis

Detects when a user authorizes an OAuth application with privileged scopes in Google Workspace. Privileged scopes grant broad access to sensitive data and administrative functions.

MITRE ATT&CK coverage

Detection logic

from panther_gsuite_helpers import gsuite_parameter_lookup

PRIVILEGED_SCOPES = [
    "admin.directory.user",
    "admin.directory.group",
    "admin.directory.domain",
    "ediscovery",
    "vault",
    "cloud_search.query",
]


def rule(event):
    scopes = event.deep_get("parameters", "scope", default=[])
    app_name = event.deep_get("id", "applicationName", default="")
    event_name = event.get("name")

    if app_name != "token" or event_name != "authorize":
        return False

    # Handle both list and string formats
    if scopes and isinstance(scopes, str):
        scopes = [scopes]

    # Check if any scope matches privileged scopes
    privileged_scopes_lower = [ps.lower() for ps in PRIVILEGED_SCOPES]

    for scope_url in scopes:
        # Extract the last part of the scope URL (e.g., "admin.directory.user" from full URL)
        scope_name = scope_url.split("/")[-1].lower()
        if scope_name in privileged_scopes_lower:
            return True

    return False


def title(event):
    actor = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    app_name = event.deep_get("parameters", "app_name", default="<UNKNOWN_APP>")

    return (
        f"Google Workspace: User [{actor}] authorized OAuth app [{app_name}] with privileged scopes"
    )


def alert_context(event):
    parameters = event.get("parameters", {})

    return {
        "actor": event.deep_get("actor", "email", default=""),
        "app_name": gsuite_parameter_lookup(parameters, "app_name"),
        "client_id": gsuite_parameter_lookup(parameters, "client_id"),
        "client_type": gsuite_parameter_lookup(parameters, "client_type"),
        "scopes": gsuite_parameter_lookup(parameters, "scope"),
        "scope_data": gsuite_parameter_lookup(parameters, "scope_data"),
        "event_type": event.get("name"),
        "ip_address": event.get("ipAddress"),
    }

Rule specification

AnalysisType: rule
RuleID: "Google.Workspace.OAuth.Privileged.Scopes"
DisplayName: "Google Workspace OAuth Application Authorized with Privileged Scopes"
Filename: gsuite_oauth_privileged_scopes.py
LogTypes:
  - GSuite.ActivityEvent
Enabled: true
Severity: Info
DedupPeriodMinutes: 60
Status: Experimental
Reference: https://businessinsights.bitdefender.com/the-chain-reaction-new-methods-for-extending-local-breaches-in-google-workspace
Description: >
  Detects when a user authorizes an OAuth application with privileged scopes in Google Workspace.
  Privileged scopes grant broad access to sensitive data and administrative functions.
Runbook: |
  1. Query GSuite.ActivityEvent logs for all OAuth token authorize events by actor:email in the 7 days before and after this alert to identify if this is part of a pattern of suspicious OAuth grants
  2. Search for the parameters:client_id across all users in the organization to determine if other users also authorized the same application
  3. Review audit logs for any actions taken using the OAuth token between the authorization timestamp and now, filtering by parameters:app_name and the authorized scopes
Tags:
  - GSuite
  - Initial Access
  - Persistence
  - Account Manipulation
Reports:
  MITRE ATT&CK:
    - TA0001:T1078.004
    - TA0003:T1098
SummaryAttributes:
  - actor:email
  - p_any_ip_addresses

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is token
  • name is authorize

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
event_typename
ip_addressipAddress
app_nameparameters.app_name

Response runbook

1. Query GSuite.ActivityEvent logs for all OAuth token authorize events by actor:email in the 7 days before and after this alert to identify if this is part of a pattern of suspicious OAuth grants

2. Search for the parameters:client_id across all users in the organization to determine if other users also authorized the same application

3. Review audit logs for any actions taken using the OAuth token between the authorization timestamp and now, filtering by parameters:app_name and the authorized scopes

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "user@example.com",
    "profileId": "123456789012345678901"
  },
  "id": {
    "applicationName": "token",
    "customerId": "C01234abc",
    "time": "2024-01-15 10:30:00.000000000",
    "uniqueQualifier": "987654321098765432"
  },
  "ipAddress": "192.0.2.1",
  "kind": "admin#reports#activity",
  "name": "authorize",
  "parameters": {
    "app_name": "SuspiciousThirdPartyApp",
    "client_id": "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com",
    "client_type": "WEB",
    "scope": [
      "https://www.googleapis.com/auth/admin.directory.user",
      "https://www.googleapis.com/auth/ediscovery",
      "https://www.googleapis.com/auth/drive",
      "https://www.googleapis.com/auth/cloud_search.query"
    ]
  },
  "type": "auth"
}

Google Workspace OAuth Token Requests from New IP

#
Severity
medium
Tags
GSuite, Initial Access, Valid Accounts, GAIA, Credential Theft, OAuth
Reference
businessinsights.bitdefender.com
Source
github.com/panther-labs/panther-analysis

Alerts when users request OAuth tokens from IP addresses they haven't used in the past 30 days, with 3+ requests indicating active usage. This may indicate GAIA credential theft where attackers use stolen refresh tokens to request access tokens from their infrastructure.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

import re


def normalize_username(email):
    if not email:
        return None
    # Extract username before @ symbol
    username = email.split("@")[0] if "@" in email else email
    # Remove all non-alphanumeric characters and convert to lowercase
    return re.sub(r"[^a-z0-9]", "", username.lower())


def rule(_):
    return True


def title(event):
    user = event.get("user", "<UNKNOWN_USER>")
    new_ip = event.get("new_ip", "<UNKNOWN_IP>")
    request_count = event.get("request_count", 0)
    return (
        f"Google Workspace: User [{user}] made {request_count} OAuth token requests "
        f"from new IP [{new_ip}]"
    )


def alert_context(event):
    user = event.get("user")
    return {
        "user": user,
        "username_normalized": normalize_username(user),
        "new_ip": event.get("new_ip"),
        "request_count": event.get("request_count"),
        "app_names": event.get("app_names"),
        "client_ids": event.get("client_ids"),
        "first_seen": event.get("first_seen"),
        "last_seen": event.get("last_seen"),
        "description": (
            "User requested OAuth tokens from an IP address not seen in the past 30 days, "
            "with multiple requests indicating active usage"
        ),
    }

Rule specification

AnalysisType: scheduled_rule
DisplayName: "Google Workspace OAuth Token Requests from New IP"
DedupPeriodMinutes: 1440
RuleID: "Google.Workspace.OAuth.Token.New.IP"
Description: |
  Alerts when users request OAuth tokens from IP addresses they haven't used in the past 30 days,
  with 3+ requests indicating active usage. This may indicate GAIA credential theft where attackers
  use stolen refresh tokens to request access tokens from their infrastructure.
ScheduledQueries:
  - Google Workspace OAuth Token Requests from New IPs
Enabled: false
Filename: gsuite_oauth_token_new_ip_rule.py
Reference: https://businessinsights.bitdefender.com/the-chain-reaction-new-methods-for-extending-local-breaches-in-google-workspace
Runbook: |
  1. Query GSuite.ActivityEvent for all OAuth token requests (applicationName: "token") by the user in the 24 hours before and after the alert to identify the full scope of token activity from the new IP address
  2. Check if the new IP address is associated with cloud providers, VPN services, proxy networks, or residential ISPs, and compare its geographic location to the user's typical login locations in the past 30 days
  3. Search for other authentication anomalies for this user in the past 7 days, including login type changes, rapid multi-IP authentication, password changes, or device compromise warnings
Severity: Medium
Tags:
  - GSuite
  - Initial Access
  - Valid Accounts
  - GAIA
  - Credential Theft
  - OAuth
Reports:
  MITRE ATT&CK:
    - TA0001:T1078.004
    - TA0006:T1550
SummaryAttributes:
  - user
  - new_ip
  - app_names

Stages and Predicates

Rule logic

This rule alerts on rows returned by its scheduled query Google Workspace OAuth Token Requests from New IPs; its Python module (Detection logic above) shapes the alert rather than filtering.

Alert deduplication
repeat matches within 1d group into one alert

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

Field
user
new_ip
request_count
app_names
client_ids
first_seen
last_seen

Response runbook

1. Query GSuite.ActivityEvent for all OAuth token requests (applicationName: "token") by the user in the 24 hours before and after the alert to identify the full scope of token activity from the new IP address

2. Check if the new IP address is associated with cloud providers, VPN services, proxy networks, or residential ISPs, and compare its geographic location to the user's typical login locations in the past 30 days

3. Search for other authentication anomalies for this user in the past 7 days, including login type changes, rapid multi-IP authentication, password changes, or device compromise warnings

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "app_names": [
    "Google Chrome"
  ],
  "client_ids": [
    "12345.apps.googleusercontent.com"
  ],
  "first_seen": "2024-01-15 10:00:00.000",
  "last_seen": "2024-01-15 10:05:00.000",
  "new_ip": "1.2.3.4",
  "request_count": 5,
  "user": "user@example.com"
}

Google Workspace OAuth Token Requests from New IPs

#
Source
github.com/panther-labs/panther-analysis

Detects users requesting OAuth tokens from IPv4 addresses they haven't used in the past 30 days, with 3+ requests indicating active usage. May indicate GAIA credential theft where attackers use stolen refresh tokens from their infrastructure.

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule specification

AnalysisType: scheduled_query
QueryName: "Google Workspace OAuth Token Requests from New IPs"
Description: |
  Detects users requesting OAuth tokens from IPv4 addresses they haven't used in the past 30 days,
  with 3+ requests indicating active usage. May indicate GAIA credential theft where attackers use
  stolen refresh tokens from their infrastructure.
Enabled: false
SnowflakeQuery: |
  WITH baseline_ips AS (
    SELECT
      actor:email AS user,
      ARRAY_AGG(DISTINCT ipAddress) AS normal_ips
    FROM panther_logs.public.gsuite_activityevent
    WHERE p_event_time >= DATEADD(day, -30, CURRENT_TIMESTAMP)
      AND p_event_time < DATEADD(day, -1, CURRENT_TIMESTAMP)
      AND id:applicationName = 'token'
      AND name = 'authorize'
      AND ipAddress NOT LIKE '%:%'
    GROUP BY actor:email
  ),
  recent_tokens AS (
    SELECT
      actor:email AS user,
      ipAddress,
      parameters:app_name AS app_name,
      parameters:client_id AS client_id,
      p_event_time
    FROM panther_logs.public.gsuite_activityevent
    WHERE p_occurs_since('1 day')
      AND id:applicationName = 'token'
      AND name = 'authorize'
      AND ipAddress NOT LIKE '%:%'
  )
  SELECT
    r.user,
    r.ipAddress AS new_ip,
    COUNT(*) AS request_count,
    ARRAY_UNIQUE_AGG(r.app_name) AS app_names,
    ARRAY_UNIQUE_AGG(r.client_id) AS client_ids,
    MIN(r.p_event_time) AS first_seen,
    MAX(r.p_event_time) AS last_seen
  FROM recent_tokens r
  LEFT JOIN baseline_ips b ON r.user = b.user
  WHERE NOT ARRAY_CONTAINS(r.ipAddress::VARIANT, COALESCE(b.normal_ips, ARRAY_CONSTRUCT()))
  GROUP BY r.user, r.ipAddress
  HAVING COUNT(*) >= 3
  ORDER BY request_count DESC

DatabricksQuery: |
  WITH baseline_ips AS (
    SELECT
      actor:email AS user,
      COLLECT_SET(ipAddress) AS normal_ips
    FROM panther_logs.gsuite_activityevent
    WHERE p_event_time >= CURRENT_TIMESTAMP - INTERVAL 30 DAYS
      AND p_event_time < CURRENT_TIMESTAMP - INTERVAL 1 DAY
      AND id:applicationName = 'token'
      AND name = 'authorize'
      AND ipAddress NOT LIKE '%:%'
    GROUP BY actor:email
  ),
  recent_tokens AS (
    SELECT
      actor:email AS user,
      ipAddress,
      parameters:app_name AS app_name,
      parameters:client_id AS client_id,
      p_event_time
    FROM panther_logs.gsuite_activityevent
    WHERE p_occurs_since('1 day')
      AND id:applicationName = 'token'
      AND name = 'authorize'
      AND ipAddress NOT LIKE '%:%'
  )
  SELECT
    r.user,
    r.ipAddress AS new_ip,
    COUNT(*) AS request_count,
    COLLECT_SET(r.app_name) AS app_names,
    COLLECT_SET(r.client_id) AS client_ids,
    MIN(r.p_event_time) AS first_seen,
    MAX(r.p_event_time) AS last_seen
  FROM recent_tokens r
  LEFT JOIN baseline_ips b ON r.user = b.user
  WHERE NOT ARRAY_CONTAINS(COALESCE(b.normal_ips, ARRAY()), r.ipAddress)
  GROUP BY r.user, r.ipAddress
  HAVING COUNT(*) >= 3
  ORDER BY request_count DESC
Schedule:
  RateMinutes: 1440
  TimeoutMinutes: 5

Stages and Predicates

Stage 1: source

Table
recent_tokens

Stage 2: filter

Grouped by
r.user, r.ipAddress

Stage 3: having

Threshold
ge 3

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
r.user
new_ipr.ipAddress
request_countCOUNT ( * )
app_namesARRAY_UNIQUE_AGG ( r.app_name )
client_idsARRAY_UNIQUE_AGG ( r.client_id )
first_seenMIN ( r.p_event_time )
last_seenMAX ( r.p_event_time )

Google Workspace OAuthLogin Scope Anomalous Application Access

#
Source
github.com/panther-labs/panther-analysis

Detects apps requesting OAuth tokens with the OAuthLogin scope when they haven't requested this scope in the previous 14 days. This scope can be used to access Google's device password escrow endpoint for GAIA credential theft.

Rule specification

AnalysisType: scheduled_query
QueryName: "Google Workspace OAuthLogin Scope Anomalous Application Access"
Description: |
  Detects apps requesting OAuth tokens with the OAuthLogin scope when they haven't requested
  this scope in the previous 14 days. This scope can be used to access Google's device password
  escrow endpoint for GAIA credential theft.
Enabled: false
Query: |
  -- pragma: template
  {% import 'anomalies' new_unique_values %}

  WITH subquery AS (
    SELECT
      parameters:app_name AS app_name,
      parameters:client_id AS client_id,
      actor:email AS user_email,
      ipAddress,
      p_event_time
    FROM panther_logs.public.gsuite_activityevent
    WHERE p_occurs_since('14 days')
      AND id:applicationName = 'token'
      AND name = 'authorize'
      AND ARRAY_TO_STRING(parameters:scope, ',') ILIKE '%accounts/OAuthLogin%'
  ),
  {{ new_unique_values('subquery', 'app_name', 'user_email', '1 day') }}
Schedule:
  RateMinutes: 360
  TimeoutMinutes: 3

Google Workspace Rapid Multi-IP Authentication

#
Source
github.com/panther-labs/panther-analysis

Detects users authenticating from 3+ distinct IPv4 addresses within 6 hours. May indicate GAIA credential theft where stolen OAuth tokens are used across multiple compromised machines simultaneously. IPv6 addresses are excluded to avoid false positives from dual-stack networking.

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule specification

AnalysisType: scheduled_query
QueryName: "Google Workspace Rapid Multi-IP Authentication"
Description: |
  Detects users authenticating from 3+ distinct IPv4 addresses within 6 hours.
  May indicate GAIA credential theft where stolen OAuth tokens are used across
  multiple compromised machines simultaneously. IPv6 addresses are excluded to
  avoid false positives from dual-stack networking.
Enabled: false
SnowflakeQuery: |
  SELECT
    actor:email AS user,
    COUNT(DISTINCT
      CASE
        WHEN ipAddress LIKE '%:%' THEN NULL
        ELSE ipAddress
      END
    ) AS unique_ip_count,
    COUNT(*) AS total_logins,
    ARRAY_UNIQUE_AGG(
      CASE
        WHEN ipAddress LIKE '%:%' THEN NULL
        ELSE ipAddress
      END
    ) AS ip_addresses,
    ARRAY_UNIQUE_AGG(parameters:login_type) AS login_types,
    MIN(p_event_time) AS first_login,
    MAX(p_event_time) AS last_login,
    DATEDIFF('minute', MIN(p_event_time), MAX(p_event_time)) AS time_span_minutes
  FROM panther_logs.public.gsuite_activityevent
  WHERE p_occurs_since('6 hours')
    AND id:applicationName = 'login'
    AND name = 'login_success'
  GROUP BY actor:email
  HAVING COUNT(DISTINCT
    CASE
      WHEN ipAddress LIKE '%:%' THEN NULL
      ELSE ipAddress
    END
  ) >= 3
  ORDER BY unique_ip_count DESC
  LIMIT 10000

DatabricksQuery: |
  SELECT
    actor:email AS user,
    COUNT(DISTINCT
      CASE
        WHEN ipAddress LIKE '%:%' THEN NULL
        ELSE ipAddress
      END
    ) AS unique_ip_count,
    COUNT(*) AS total_logins,
    COLLECT_SET(
      CASE
        WHEN ipAddress LIKE '%:%' THEN NULL
        ELSE ipAddress
      END
    ) AS ip_addresses,
    COLLECT_SET(parameters:login_type) AS login_types,
    MIN(p_event_time) AS first_login,
    MAX(p_event_time) AS last_login,
    TIMESTAMPDIFF(MINUTE, MIN(p_event_time), MAX(p_event_time)) AS time_span_minutes
  FROM panther_logs.gsuite_activityevent
  WHERE p_occurs_since('6 hours')
    AND id:applicationName = 'login'
    AND name = 'login_success'
  GROUP BY actor:email
  HAVING COUNT(DISTINCT
    CASE
      WHEN ipAddress LIKE '%:%' THEN NULL
      ELSE ipAddress
    END
  ) >= 3
  ORDER BY unique_ip_count DESC
  LIMIT 10000
Schedule:
  RateMinutes: 360
  TimeoutMinutes: 3

Stages and Predicates

Stage 1: source

Table
panther_logs.public.gsuite_activityevent

Stage 2: filter

  • id:applicationName is login
  • name is login_success
Grouped by
actor:email
Window
6h

Stage 3: having

Threshold
ge 3
Cardinality
CASE WHEN ipAddress LIKE '%:%' THEN NULL ELSE ipAddress END

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
useractor:email
unique_ip_countCOUNT ( DISTINCT CASE WHEN ipAddress LIKE '%:%' THEN NULL ELSE ipAddress END )
total_loginsCOUNT ( * )
ip_addressesARRAY_UNIQUE_AGG ( CASE WHEN ipAddress LIKE '%:%' THEN NULL ELSE ipAddress END )
login_typesARRAY_UNIQUE_AGG ( parameters:login_type )
first_loginMIN ( p_event_time )
last_loginMAX ( p_event_time )
time_span_minutesDATEDIFF ( 'minute' , MIN ( p_event_time ) , MAX ( p_event_time ) )

Google Workspace Rapid Multi-IP Authentication

#
Severity
medium
Tags
GSuite, Lateral Movement, Valid Accounts, GAIA, Credential Theft
Reference
businessinsights.bitdefender.com
Source
github.com/panther-labs/panther-analysis

Alerts when users authenticate from 3+ distinct IPv4 addresses within 6 hours. This pattern may indicate GAIA credential theft where attackers use stolen OAuth tokens across multiple compromised machines simultaneously. IPv6 addresses are excluded to avoid false positives from dual-stack networking environments.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

def rule(_):
    return True


def title(event):
    user = event.get("user", "<UNKNOWN_USER>")
    ip_count = event.get("unique_ip_count", 0)
    return f"Google Workspace: User [{user}] authenticated from {ip_count} distinct IPs in 6 hours"


def severity(event):
    ip_count = event.get("unique_ip_count", 0)

    # Very high IP count suggests active credential spread
    if ip_count >= 4:
        return "HIGH"

    return "MEDIUM"


def alert_context(event):
    return {
        "user": event.get("user"),
        "unique_ip_count": event.get("unique_ip_count"),
        "ip_addresses": event.get("ip_addresses"),
        "login_types": event.get("login_types"),
        "first_login": event.get("first_login"),
        "last_login": event.get("last_login"),
        "time_span_minutes": event.get("time_span_minutes"),
        "total_logins": event.get("total_logins"),
        "description": (
            "User authenticated from multiple distinct IPv4 addresses in a short time window"
        ),
    }

Rule specification

AnalysisType: scheduled_rule
DisplayName: "Google Workspace Rapid Multi-IP Authentication"
DedupPeriodMinutes: 360
RuleID: "Google.Workspace.Rapid.Multi.IP.Authentication"
Description: |
  Alerts when users authenticate from 3+ distinct IPv4 addresses within 6 hours.
  This pattern may indicate GAIA credential theft where attackers use stolen OAuth
  tokens across multiple compromised machines simultaneously. IPv6 addresses are
  excluded to avoid false positives from dual-stack networking environments.
ScheduledQueries:
  - Google Workspace Rapid Multi-IP Authentication
Enabled: false
Filename: gsuite_rapid_multi_ip_authentication_rule.py
Reference: https://businessinsights.bitdefender.com/the-chain-reaction-new-methods-for-extending-local-breaches-in-google-workspace
Runbook: |
  1. Query GSuite.ActivityEvent for all login events by the user in the 12 hours before and after the alert to establish the full timeline of authentication activity and identify all source IP addresses used
  2. Check if the source IP addresses are associated with cloud providers, VPN services, proxy networks, or known corporate infrastructure, and compare the geographic locations of the IPs to identify impossible travel patterns
  3. Search for other authentication anomalies for this user in the past 7 days, including login type changes, OAuth token authorizations with privileged scopes, failed authentication attempts, or device compromise warnings
Severity: Medium
Tags:
  - GSuite
  - Lateral Movement
  - Valid Accounts
  - GAIA
  - Credential Theft
Reports:
  MITRE ATT&CK:
    - TA0008:T1078.004
    - TA0006:T1550
SummaryAttributes:
  - user
  - ip_addresses

Stages and Predicates

Rule logic

This rule alerts on rows returned by its scheduled query Google Workspace Rapid Multi-IP Authentication; its Python module (Detection logic above) shapes the alert rather than filtering.

Alert deduplication
repeat matches within 6h group into one alert

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

Field
user
unique_ip_count
ip_addresses
login_types
first_login
last_login
time_span_minutes
total_logins

Response runbook

1. Query GSuite.ActivityEvent for all login events by the user in the 12 hours before and after the alert to establish the full timeline of authentication activity and identify all source IP addresses used

2. Check if the source IP addresses are associated with cloud providers, VPN services, proxy networks, or known corporate infrastructure, and compare the geographic locations of the IPs to identify impossible travel patterns

3. Search for other authentication anomalies for this user in the past 7 days, including login type changes, OAuth token authorizations with privileged scopes, failed authentication attempts, or device compromise warnings

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "first_login": "2024-01-15 10:00:00.000",
  "ip_addresses": [
    "1.2.3.4",
    "5.6.7.8",
    "9.10.11.12"
  ],
  "last_login": "2024-01-15 15:30:00.000",
  "login_types": [
    "saml"
  ],
  "time_span_minutes": 330,
  "total_logins": 5,
  "unique_ip_count": 3,
  "user": "user@example.com"
}

Gsuite Attachments Downloaded from Spam Email

#
Severity
high
Entities
actor_ids, domain_names, ip_addresses
Log types
GSuite.ActivityEvent
Tags
GSuite
Source
github.com/panther-labs/panther-analysis

Detects when a user downloads or saves to Google Drive one or more attachments that are classified as spam.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Gmail (any event)

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    if event.deep_get("id", "applicationName", default="<UNKNOWN_APPLICATION>") != "gmail":
        return False
    return event.deep_get(
        "parameters", "message_info", "is_spam", default=False
    ) is True and event.deep_get("parameters", "event_info", "mail_event_type", default=0) in (
        17,
        18,
        19,
    )


def title(event):
    user = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    return f"[{user}] has downloaded potentially malicious attachments from a spam email"


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_attachments_downloaded_from_spam_email.py
RuleID: "GSuite.Gmail.SpamEmail.AttachmentDownload"
DisplayName: "Gsuite Attachments Downloaded from Spam Email"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Reports:
  MITRE ATT&CK:
    - TA0001:T1566.001 # Initial Access: Phishing - Spearphishing Attachment
    - TA0011:T1204.002 # Execution: User Execution - Malicious File
Severity: High
Description: Detects when a user downloads or saves to Google Drive one or more attachments that are classified as spam.
Threshold: 1
DedupPeriodMinutes: 60

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is gmail
  • parameters.message_info.is_spam is true
  • parameters.event_info.mail_event_type is one of 17, 18, 19

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "denethor@lotr.com",
    "profileId": "123456789"
  },
  "id": {
    "applicationName": "gmail",
    "customerId": "1A2B3C",
    "time": "2025-11-04 20:44:43.248000000",
    "uniqueQualifier": "-123456789"
  },
  "ipAddress": "1.1.1.1",
  "kind": "admin#reports#activity",
  "name": "delivery",
  "p_any_actor_ids": [
    "1234567891234"
  ],
  "p_any_domain_names": [
    "evil.com"
  ],
  "p_any_ip_addresses": [
    "1.1.1.1"
  ],
  "p_event_time": "2025-11-04 20:44:43.248000000",
  "p_log_type": "GSuite.ActivityEvent",
  "p_parse_time": "2025-11-04 20:49:46.688935963",
  "p_row_id": "0000000000de09c1dc6f0828cbad2ca5",
  "p_schema_version": 0,
  "p_source_id": "7ee69d4d-df1b-40b3-b5e8-6826dee34b1c",
  "p_source_label": "Google Workspace",
  "p_udm": {
    "source": {
      "address": "1.1.1.1",
      "ip": "1.1.1.1"
    },
    "user": {
      "provider_id": "123456789"
    }
  },
  "parameters": {
    "event_info": {
      "elapsed_time_usec": 368746,
      "mail_event_type": 17,
      "timestamp_usec": 1762289083248347
    },
    "message_info": {
      "action_type": 19,
      "flattened_destinations": "gmail-for-work-catchall::denethor@lotr.com",
      "is_spam": true,
      "link_domain": [
        "evil.com"
      ],
      "message_set": {
        "type": 46
      },
      "payload_size": 12345,
      "subject": "You won 1 Million Dollar"
    }
  },
  "type": "delivery_type"
}

GSuite Calendar Has Been Made Public

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A User or Admin Has Modified A Calendar To Be Public

MITRE ATT&CK coverage

TacticTechniques
Discovery

Telemetry coverage

Detection logic

def rule(event):
    return (
        event.get("name") == "change_calendar_acls"
        and event.get("parameters", {}).get("grantee_email")
        == "__public_principal__@public.calendar.google.com"
    )


def title(event):
    return (
        f"GSuite calendar "
        f"[{event.deep_get('parameters', 'calendar_id', default='<NO_CALENDAR_ID>')}] made "
        f"{public_or_private(event)} by "
        f"[{event.deep_get('actor', 'email', default='<NO_ACTOR_FOUND>')}]"
    )


def severity(event):
    return "LOW" if public_or_private(event) == "private" else "MEDIUM"


def public_or_private(event):
    return "private" if event.deep_get("parameters", "access_level") == "none" else "public"

Rule specification

AnalysisType: rule
Filename: gsuite_calendar_made_public.py
RuleID: "GSuite.CalendarMadePublic"
DisplayName: "GSuite Calendar Has Been Made Public"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Reports:
  MITRE ATT&CK:
    - TA0007:T1087
Severity: Medium
Description: >
  A User or Admin Has Modified A Calendar To Be Public
Reference: https://support.google.com/calendar/answer/37083?hl=en&sjid=864417124752637253-EU
Runbook: >
  Follow up with user about this calendar share.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • name is change_calendar_acls
  • parameters.grantee_email is __public_principal__@public.calendar.google.com

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
calendar_idparameters.calendar_id
emailactor.email

Response runbook

Follow up with user about this calendar share.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "user@example.io",
    "profileId": "110111111111111111111"
  },
  "id": {
    "applicationName": "calendar",
    "customerId": "D12345",
    "time": "2022-12-10 22:33:31.852000000",
    "uniqueQualifier": "-2888888888888888888"
  },
  "ipAddress": "1.2.3.4",
  "kind": "admin#reports#activity",
  "name": "change_calendar_acls",
  "ownerDomain": "example.io",
  "parameters": {
    "access_level": "freebusy",
    "api_kind": "web",
    "calendar_id": "user@example.io",
    "grantee_email": "__public_principal__@public.calendar.google.com",
    "user_agent": "Mozilla/5.0"
  },
  "type": "calendar_change"
}

GSuite Device Suspicious Activity

#
Severity
low
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

GSuite reported a suspicious activity on a user's device.

Telemetry coverage

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") != "mobile":
        return False

    return bool(event.get("name") == "SUSPICIOUS_ACTIVITY_EVENT")


def title(event):
    return (
        f"User [{event.deep_get('actor', 'email', default='<UNKNOWN_USER>')}]"
        f"'s device was compromised"
    )

Rule specification

AnalysisType: rule
Filename: gsuite_mobile_device_suspicious_activity.py
RuleID: "GSuite.DeviceSuspiciousActivity"
DisplayName: "GSuite Device Suspicious Activity"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: Low
Description: >
  GSuite reported a suspicious activity on a user's device.
Reference: https://support.google.com/a/answer/7562460?hl=en&sjid=864417124752637253-EU
Runbook: >
  Validate that the suspicious activity was expected by the user.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is mobile
  • name is SUSPICIOUS_ACTIVITY_EVENT

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

Validate that the suspicious activity was expected by the user.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "homer.simpson@example.io"
  },
  "id": {
    "applicationName": "mobile"
  },
  "name": "SUSPICIOUS_ACTIVITY_EVENT",
  "parameters": {
    "USER_EMAIL": "homer.simpson@example.io"
  },
  "type": "device_updates"
}

GSuite Document External Ownership Transfer

#
Severity
low
Log types
GSuite.ActivityEvent
Tags
GSuite, Collection:Data from Information Repositories
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A GSuite document's ownership was transferred to an external party.

MITRE ATT&CK coverage

Telemetry coverage

Detection logic

def rule(event):
    if event.get("name") != "change_owner":
        return False

    if event.deep_get("parameters", "visibility") in (
        "shared_internally",
        "people_within_domain_with_link",
        "private",
    ):
        return False

    previous_owner = event.deep_get("parameters", "owner", default="<UNKNOWN USER>")
    new_owner = event.deep_get("parameters", "new_owner", default="<UNKNOWN USER>")

    previous_owner_domain = previous_owner.split("@")[1] if "@" in previous_owner else None
    new_owner_domain = new_owner.split("@")[1] if "@" in new_owner else None

    if previous_owner_domain is None or new_owner_domain is None:
        return False

    if previous_owner_domain != new_owner_domain:
        return True

    return False


def title(event):
    actor = event.deep_get("actor", "email", default="<UNKNOWN USER>")
    previous_owner = event.deep_get("parameters", "owner", default="<UNKNOWN USER>")
    new_owner = event.deep_get("parameters", "new_owner", default="<UNKNOWN USER>")

    return f"User [{actor}] transferred document ownership from [{previous_owner}] to [{new_owner}]"

Rule specification

AnalysisType: rule
Filename: gsuite_doc_ownership_transfer.py
RuleID: "GSuite.DocOwnershipTransfer"
DisplayName: "GSuite Document External Ownership Transfer"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Collection:Data from Information Repositories
Reports:
  MITRE ATT&CK:
    - TA0009:T1213
Severity: Low
Description: >
  A GSuite document's ownership was transferred to an external party.
Reference: https://support.google.com/drive/answer/2494892?hl=en&co=GENIE.Platform%3DDesktop&sjid=864417124752637253-EU
Runbook: >
  Verify that this document did not contain sensitive or private company information.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • name is change_owner
  • parameters.visibility is not one of shared_internally, people_within_domain_with_link, private

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
nameeq
  • change_owner
field:"name" kind:eq value:"change_owner"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email
ownerparameters.owner
new_ownerparameters.new_owner

Response runbook

Verify that this document did not contain sensitive or private company information.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "alice@panther.com",
    "profileId": "1234567890"
  },
  "id": {
    "applicationName": "drive",
    "customerId": "C123abcde",
    "time": "2025-07-11 19:50:09.324000000",
    "uniqueQualifier": "1234567890"
  },
  "kind": "admin#reports#activity",
  "name": "change_owner",
  "parameters": {
    "billable": true,
    "doc_id": "1234567890",
    "doc_title": "sensitive_document.xlsx",
    "doc_type": "msexcel",
    "new_owner": "bob@example.com",
    "owner": "alice@panther.com",
    "primary_event": true,
    "visibility": "unknown"
  },
  "type": "acl_change"
}

GSuite Drive Many Documents Deleted

#
Status
Deprecated
Severity
medium
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

Scheduled rule for the GSuite Drive Many Documents Deleted query. Looks for users who have deleted more than 10 (tunable) documents the past day.

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

def rule(_):
    return True


def title(event):
    return (
        f"GSuite: [{event.get('user', '<user_not_found>')}] "
        f"has deleted [{event.get('delete_count', '<count_not_found>')}] "
        "documents from Google Drive."
    )


def alert_context(event):
    return event.to_dict()

Rule specification

AnalysisType: scheduled_rule
Description: Scheduled rule for the GSuite Drive Many Documents Deleted query. Looks for users who have deleted more than 10 (tunable) documents the past day.
DisplayName: "GSuite Drive Many Documents Deleted"
Enabled: false
Status: Deprecated
Filename: gsuite_drive_many_docs_deleted.py
Reference: https://support.google.com/drive/answer/2375102?hl=en&co=GENIE.Platform%3DAndroid#:~:text=To%20delete%20your%20Google%20Drive,them%20to%20empty%20your%20trash.
Severity: Medium
DedupPeriodMinutes: 60
RuleID: "GSuite.Drive.Many.Documents.Deleted"
Threshold: 1
ScheduledQueries:
  - GSuite Many Docs Deleted Query

Stages and Predicates

Rule logic

This rule alerts on rows returned by its scheduled query GSuite Many Docs Deleted Query; its Python module (Detection logic above) shapes the alert rather than filtering.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

Field
user
delete_count

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "delete_count": 100,
  "deleted_files": [
    "importantdoc1",
    "importantdoc2",
    "importantdoc100"
  ],
  "user": "homer.simpson@simpsons.com"
}

GSuite Drive Many Documents Deleted

#
Status
Experimental
Severity
medium
Group by
actor.email
Log types
GSuite.ActivityEvent
Tags
GSuite, Impact, Data Destruction
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

Detects when a user moves more than 10 distinct documents to the trash in Google Drive within 60 minutes. This may indicate accidental or malicious bulk deletion of files.

MITRE ATT&CK coverage

TacticTechniques
Impact

Telemetry coverage

PlatformRecord / event type
Google Workspacetrash: Trash

Rules detecting the same action

These rules filter on the same operation.

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") != "drive":
        return False
    return event.get("name") == "trash"


def title(event):
    user = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    return f"Google Workspace: User [{user}] deleted many documents from Google Drive"


def dedup(event):
    return event.deep_get("actor", "email", default="")


def unique(event):
    return event.deep_get("parameters", "doc_id") or None


def severity(event):
    visibility = event.deep_get("parameters", "visibility", default="")
    if visibility == "shared_externally":
        return "HIGH"
    return "DEFAULT"


def alert_context(event):
    return {
        "user": event.deep_get("actor", "email"),
        "doc_title": event.deep_get("parameters", "doc_title"),
        "doc_type": event.deep_get("parameters", "doc_type"),
        "visibility": event.deep_get("parameters", "visibility"),
    }

Rule specification

AnalysisType: rule
Filename: gsuite_drive_many_docs_deleted.py
RuleID: "GSuite.Drive.BulkDocumentDeletion"
DisplayName: "GSuite Drive Many Documents Deleted"
Status: Experimental
Enabled: true
Severity: Medium
DedupPeriodMinutes: 60
Threshold: 11
LogTypes:
  - GSuite.ActivityEvent
Description: >
  Detects when a user moves more than 10 distinct documents to the trash in Google Drive
  within 60 minutes. This may indicate accidental or malicious bulk deletion of files.
Reference: https://support.google.com/drive/answer/2375102
Reports:
  MITRE ATT&CK:
    - TA0040:T1485
Tags:
  - GSuite
  - Impact
  - Data Destruction
Runbook: |
  1. Query GSuite.ActivityEvent for all trash events by actor:email in the 2 hours around this alert to identify the full list of parameters:doc_title values deleted and whether they belong to shared drives
  2. Check parameters:visibility on the deleted documents to determine if externally or internally shared files were affected, and assess the business impact of the deletions
  3. Search for other suspicious drive activity by this user in the past 24 hours, including bulk downloads, sharing changes, or access to sensitive documents prior to deletion

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is drive
  • name is trash
Alert cadence
alerts after 11 matches within 1h

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
useractor.email
doc_titleparameters.doc_title
doc_typeparameters.doc_type
visibilityparameters.visibility

Response runbook

1. Query GSuite.ActivityEvent for all trash events by actor:email in the 2 hours around this alert to identify the full list of parameters:doc_title values deleted and whether they belong to shared drives

2. Check parameters:visibility on the deleted documents to determine if externally or internally shared files were affected, and assess the business impact of the deletions

3. Search for other suspicious drive activity by this user in the past 24 hours, including bulk downloads, sharing changes, or access to sensitive documents prior to deletion

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "user@example.com"
  },
  "id": {
    "applicationName": "drive"
  },
  "name": "trash",
  "parameters": {
    "doc_id": "1ABC123def456GHI789jkl",
    "doc_title": "Q4 Financial Report",
    "doc_type": "spreadsheet",
    "visibility": "shared_internally"
  }
}

Gsuite Email Bypassed Spam Filter

#
Severity
medium
Entities
actor_ids, domain_names, ip_addresses
Log types
GSuite.ActivityEvent
Tags
GSuite
Source
github.com/panther-labs/panther-analysis

Detects if an email received by a user has bypassed the organization's spam filter.

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Gmail (any event)

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    if event.deep_get("id", "applicationName", default="<UNKNOWN_APPLICATION>") != "gmail":
        return False
    return event.deep_get("parameters", "message_info", "message_set", "type", default=0) == 46


def title(event):
    user = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    subject = event.deep_get("parameters", "message_info", "subject", default="<UNKNOWN_SUBJECT>")
    return (
        f"Message [{subject}] received by user [{user}] "
        f"has bypassed your organization's spam filter"
    )


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_bypass_spam_filter_email.py
RuleID: "GSuite.Gmail.Email.SpamFilter.Bypass"
DisplayName: "Gsuite Email Bypassed Spam Filter"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Reports:
  MITRE ATT&CK:
    - TA0001:T1566 # Initial Access: Phishing
Severity: Medium
Description: >
  Detects if an email received by a user has bypassed the organization's spam filter.
Threshold: 1
DedupPeriodMinutes: 60

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is gmail
  • parameters.message_info.message_set.type is 46

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters
subjectparameters.message_info.subject

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "denethor@lotr.com",
    "profileId": "123456789"
  },
  "id": {
    "applicationName": "gmail",
    "customerId": "1A2B3C",
    "time": "2025-11-04 20:44:43.248000000",
    "uniqueQualifier": "-123456789"
  },
  "ipAddress": "1.1.1.1",
  "kind": "admin#reports#activity",
  "name": "delivery",
  "p_any_actor_ids": [
    "1234567891234"
  ],
  "p_any_domain_names": [
    "evil.com"
  ],
  "p_any_ip_addresses": [
    "1.1.1.1"
  ],
  "p_event_time": "2025-11-04 20:44:43.248000000",
  "p_log_type": "GSuite.ActivityEvent",
  "p_parse_time": "2025-11-04 20:49:46.688935963",
  "p_row_id": "0000000000de09c1dc6f0828cbad2ca5",
  "p_schema_version": 0,
  "p_source_id": "7ee69d4d-df1b-40b3-b5e8-6826dee34b1c",
  "p_source_label": "Google Workspace",
  "p_udm": {
    "source": {
      "address": "1.1.1.1",
      "ip": "1.1.1.1"
    },
    "user": {
      "provider_id": "123456789"
    }
  },
  "parameters": {
    "event_info": {
      "elapsed_time_usec": 368746,
      "timestamp_usec": 1762289083248347
    },
    "message_info": {
      "action_type": 19,
      "flattened_destinations": "gmail-for-work-catchall::denethor@lotr.com",
      "link_domain": [
        "evil.com"
      ],
      "message_set": {
        "type": 46
      },
      "payload_size": 12345,
      "subject": "You won 1 Million Dollar"
    }
  },
  "type": "delivery_type"
}

GSuite External Drive Document

#
Severity
low
Group by
actor.email
Log types
GSuite.ActivityEvent
Tags
GSuite, Collection:Data from Information Repositories, Configuration Required
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Google drive resource became externally accessible.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

import json
from unittest.mock import MagicMock

from panther_gsuite_helpers import gsuite_parameter_lookup as param_lookup

# Add any domain name(s) that you expect to share documents with in the ALLOWED_DOMAINS set
ALLOWED_DOMAINS = set()

PUBLIC_PROVIDERS = {
    "gmail.com",
    "yahoo.com",
    "outlook.com",
    "aol.com",
    "yandex.com",
    "protonmail.com",
    "pm.me",
    "icloud.com",
    "tutamail.com",
    "tuta.io",
    "keemail.me",
    "mail.com",
    "zohomail.com",
    "hotmail.com",
    "msn.com",
}

VISIBILITY = {
    "people_with_link",
    "people_within_domain_with_link",
    "public_on_the_web",
    "shared_externally",
    "unknown",
}

ALERT_DETAILS = {}

# Events where documents have changed perms due to parent folder change
INHERITANCE_EVENTS = {
    "change_user_access_hierarchy_reconciled",
    "change_document_access_scope_hierarchy_reconciled",
}


def init_alert_details(log):
    global ALERT_DETAILS  # pylint: disable=global-statement
    ALERT_DETAILS[log] = {
        "ACCESS_SCOPE": "<UNKNOWN_ACCESS_SCOPE>",
        "DOC_TITLE": "<UNKNOWN_TITLE>",
        "NEW_VISIBILITY": "<UNKNOWN_VISIBILITY>",
        "TARGET_USER_EMAILS": ["<UNKNOWN_USER>"],
        "TARGET_DOMAIN": "<UNKNOWN_DOMAIN>",
    }


def user_is_external(target_user):
    global ALLOWED_DOMAINS  # pylint: disable=global-statement
    # We need to type-cast ALLOWED_DOMAINS for unit testing mocks
    if isinstance(ALLOWED_DOMAINS, MagicMock):
        ALLOWED_DOMAINS = set(json.loads(ALLOWED_DOMAINS()))  # pylint: disable=not-callable
    for domain in ALLOWED_DOMAINS:
        if domain in target_user:
            return False
    return True


def rule(event):
    # pylint: disable=too-complex
    global ALLOWED_DOMAINS  # pylint: disable=global-statement
    if event.deep_get("id", "applicationName") != "drive":
        return False

    # Events that have the types in INHERITANCE_EVENTS are
    # changes to documents and folders that occur due to
    # a change in the parent folder's permission. We ignore
    # these events to prevent every folder change from
    # generating multiple alerts.
    if event.get("name") in INHERITANCE_EVENTS:
        return False

    log = event.get("p_row_id")
    init_alert_details(log)

    # We need to type-cast ALLOWED_DOMAINS for unit testing mocks
    if isinstance(ALLOWED_DOMAINS, MagicMock):
        ALLOWED_DOMAINS = set(json.loads(ALLOWED_DOMAINS()))  # pylint: disable=not-callable

    # For GSuite.ActivityEvent, each log is a single event.
    # Check if this event is a visibility change for a domain
    if (
        event.get("type") == "acl_change"
        and event.get("name") == "change_document_visibility"
        and param_lookup(event.get("parameters", {}), "new_value") != ["private"]
        and not param_lookup(event.get("parameters", {}), "target_domain") in ALLOWED_DOMAINS
        and param_lookup(event.get("parameters", {}), "visibility") in VISIBILITY
    ):
        ALERT_DETAILS[log]["TARGET_DOMAIN"] = param_lookup(
            event.get("parameters", {}), "target_domain"
        )
        ALERT_DETAILS[log]["NEW_VISIBILITY"] = param_lookup(
            event.get("parameters", {}), "visibility"
        )
        ALERT_DETAILS[log]["DOC_TITLE"] = param_lookup(event.get("parameters", {}), "doc_title")
        if param_lookup(event.get("parameters", {}), "new_value") != ["none"]:
            ALERT_DETAILS[log]["ACCESS_SCOPE"] = param_lookup(
                event.get("parameters", {}), "new_value"
            )
        return True

    # For visibility changes that apply to a user
    if (
        event.get("type") == "acl_change"
        and event.get("name") == "change_user_access"
        and param_lookup(event.get("parameters", {}), "new_value") != ["none"]
        and user_is_external(param_lookup(event.get("parameters", {}), "target_user"))
    ):
        if ALERT_DETAILS[log]["TARGET_USER_EMAILS"] != ["<UNKNOWN_USER>"]:
            ALERT_DETAILS[log]["TARGET_USER_EMAILS"].append(
                param_lookup(event.get("parameters", {}), "target_user")
            )
        else:
            ALERT_DETAILS[log]["TARGET_USER_EMAILS"] = [
                param_lookup(event.get("parameters", {}), "target_user")
            ]
            ALERT_DETAILS[log]["DOC_TITLE"] = param_lookup(event.get("parameters", {}), "doc_title")
            ALERT_DETAILS[log]["ACCESS_SCOPE"] = param_lookup(
                event.get("parameters", {}), "new_value"
            )
        return True

    return False


def alert_context(event):
    log = event.get("p_row_id")
    if ALERT_DETAILS[log]["TARGET_USER_EMAILS"] != ["<UNKNOWN_USER>"]:
        return {"target users": ALERT_DETAILS[log]["TARGET_USER_EMAILS"]}
    return {}


def dedup(event):
    return event.deep_get("actor", "email", default="<UNKNOWN_USER>")


def title(event):
    log = event.get("p_row_id")
    if ALERT_DETAILS[log]["TARGET_USER_EMAILS"] != ["<UNKNOWN_USER>"]:
        if len(ALERT_DETAILS[log]["TARGET_USER_EMAILS"]) == 1:
            sharing_scope = ALERT_DETAILS[log]["TARGET_USER_EMAILS"][0]
        else:
            sharing_scope = "multiple users"
        if ALERT_DETAILS[log]["NEW_VISIBILITY"] == "shared_externally":
            sharing_scope += " (outside the document's current domain)"
    elif ALERT_DETAILS[log]["TARGET_DOMAIN"] == "all":
        sharing_scope = "the entire internet"
        if ALERT_DETAILS[log]["NEW_VISIBILITY"] == "people_with_link":
            sharing_scope += " (anyone with the link)"
        elif ALERT_DETAILS[log]["NEW_VISIBILITY"] == "public_on_the_web":
            sharing_scope += " (link not required)"
    else:
        sharing_scope = f"the {ALERT_DETAILS[log]['TARGET_DOMAIN']} domain"
        if ALERT_DETAILS[log]["NEW_VISIBILITY"] == "people_within_domain_with_link":
            sharing_scope += f" (anyone in {ALERT_DETAILS[log]['TARGET_DOMAIN']} with the link)"
        elif ALERT_DETAILS[log]["NEW_VISIBILITY"] == "public_in_the_domain":
            sharing_scope += f" (anyone in {ALERT_DETAILS[log]['TARGET_DOMAIN']})"

    # alert_access_scope = ALERT_DETAILS[log]["ACCESS_SCOPE"][0].replace("can_", "")

    return (
        f"User [{event.deep_get('actor', 'email', default='<UNKNOWN_USER>')}] made documents "
        f"externally visible"
    )


def severity(event):
    log = event.get("p_row_id")
    if ALERT_DETAILS[log]["TARGET_USER_EMAILS"] != ["<UNKNOWN_USER>"]:
        for address in ALERT_DETAILS[log]["TARGET_USER_EMAILS"]:
            domain = address.split("@")[1]
            if domain in PUBLIC_PROVIDERS:
                return "LOW"
    return "INFO"

Rule specification

AnalysisType: rule
Filename: gsuite_drive_visibility_change.py
RuleID: "GSuite.DriveVisibilityChanged"
DisplayName: "GSuite External Drive Document"
Enabled: false
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Collection:Data from Information Repositories
  - Configuration Required
Reports:
  MITRE ATT&CK:
    - TA0009:T1213
Severity: Low
Description: >
  A Google drive resource became externally accessible.
Reference: https://support.google.com/a/users/answer/12380484?hl=en&sjid=864417124752637253-EU
Runbook: >
  Investigate whether the drive document is appropriate to be publicly accessible.
SummaryAttributes:
  - actor:email
DedupPeriodMinutes: 360 # 6 hours

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is drive
  • name is not one of change_user_access_hierarchy_reconciled, change_document_access_scope_hierarchy_reconciled
  • any of:
    • all of:
      • type is acl_change
      • name is change_document_visibility
    • all of:
      • any of:
        • type is not acl_change
        • name is not change_document_visibility
      • type is acl_change
      • name is change_user_access

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Alert deduplication
repeat matches within 6h group into one alert

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
nameinchange_document_access_scope_hierarchy_reconciled, change_user_access_hierarchy_reconciledexcludes:name field:"name" value:"change_document_access_scope_hierarchy_reconciled" field:"name" value:"change_user_access_hierarchy_reconciled"

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

Investigate whether the drive document is appropriate to be publicly accessible.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "bobert@gmail.com"
  },
  "id": {
    "applicationName": "drive"
  },
  "name": "change_document_visibility",
  "p_log_type": "GSuite.ActivityEvent",
  "p_row_id": "111222",
  "parameters": {
    "doc_title": "my shared document",
    "new_value": [
      "people_with_link"
    ],
    "target_domain": "all",
    "visibility": "people_with_link",
    "visibility_change": "external"
  },
  "type": "acl_change"
}

GSuite Government Backed Attack

#
Severity
critical
Log types
GSuite.ActivityEvent
Tags
GSuite, APT, Advanced Persistent Threat, Nation State, Targeted Attack, Reconnaissance, Initial Access, Government Backed Attack, High Profile Target
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

Detects Google Workspace warnings of government-backed attacks targeting user accounts, issued only when indicators match nation-state threat actors or APT groups. These sophisticated attacks target high-value individuals using advanced tactics including zero-day exploits, spear-phishing, and social engineering. Successful compromise can lead to persistent access, intellectual property theft, and supply chain attacks.

MITRE ATT&CK coverage

Telemetry coverage

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") != "login":
        return False

    return bool(event.get("name") == "gov_attack_warning")


def title(event):
    return (
        f"User [{event.deep_get('actor', 'email', default='<UNKNOWN_EMAIL>')}] may have been "
        f"targeted by a government attack"
    )

Rule specification

AnalysisType: rule
Filename: gsuite_gov_attack.py
RuleID: "GSuite.GovernmentBackedAttack"
DisplayName: "GSuite Government Backed Attack"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - APT
  - Advanced Persistent Threat
  - Nation State
  - Targeted Attack
  - Reconnaissance
  - Initial Access
  - Government Backed Attack
  - High Profile Target
Severity: Critical
Reports:
  MITRE ATT&CK:
    - TA0043:T1595
    - TA0001:T1566
    - TA0042:T1589
Description: >
  Detects Google Workspace warnings of government-backed attacks targeting user accounts, issued only when indicators match nation-state threat actors or APT groups. These sophisticated attacks target high-value individuals using advanced tactics including zero-day exploits, spear-phishing, and social engineering. Successful compromise can lead to persistent access, intellectual property theft, and supply chain attacks.
Reference: https://support.google.com/a/answer/9007870?hl=en
Runbook: |
  1. Query GSuite.ActivityEvent logs for all login events, OAuth app authorizations, email forwarding rules, delegated access grants, and data exports by actor:email in the 90 days before this warning to identify suspicious activity indicating potential compromise
  2. Review login locations, IP addresses, and device information from the targeted user's recent authentication events to detect unusual geographic access patterns or impossible travel that may indicate reconnaissance or account takeover attempts
  3. Contact Google Workspace enterprise support to obtain additional threat intelligence about the government-backed attack including threat actor attribution, attack vectors observed, and specific indicators of compromise related to this incident
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is login
  • name is gov_attack_warning

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

1. Query GSuite.ActivityEvent logs for all login events, OAuth app authorizations, email forwarding rules, delegated access grants, and data exports by actor:email in the 90 days before this warning to identify suspicious activity indicating potential compromise

2. Review login locations, IP addresses, and device information from the targeted user's recent authentication events to detect unusual geographic access patterns or impossible travel that may indicate reconnaissance or account takeover attempts

3. Contact Google Workspace enterprise support to obtain additional threat intelligence about the government-backed attack including threat actor attribution, attack vectors observed, and specific indicators of compromise related to this incident

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "homer.simpson@example.com"
  },
  "id": {
    "applicationName": "login"
  },
  "name": "gov_attack_warning",
  "parameters": {
    "is_suspicious": null,
    "login_challenge_method": [
      "none"
    ]
  },
  "type": "login"
}

Gsuite Link Clicked in Spam Email

#
Severity
high
Entities
actor_ids, domain_names, ip_addresses
Log types
GSuite.ActivityEvent
Tags
GSuite
Source
github.com/panther-labs/panther-analysis

Detects when a user click links contained in a received email that is classified as spam.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Gmail (any event)

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    if event.deep_get("id", "applicationName", default="<UNKNOWN_APPLICATION>") != "gmail":
        return False
    return event.deep_get(
        "parameters", "message_info", "is_spam", default=False
    ) is True and event.deep_get("parameters", "event_info", "mail_event_type", default=0) in (
        15,
        16,
    )


def title(event):
    user = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    return f"[{user}] has clicked potentially malicious links contained in a spam email"


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_links_clicked_in_spam_email.py
RuleID: "GSuite.Gmail.SpamEmail.LinkClicked"
DisplayName: "Gsuite Link Clicked in Spam Email"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Reports:
  MITRE ATT&CK:
    - TA0001:T1566.002 # Initial Access: Phishing - Spearphishing Link
    - TA0011:T1204.001 # Execution: User Execution - Malicious Link
Severity: High
Description: Detects when a user click links contained in a received email that is classified as spam.
Threshold: 1
DedupPeriodMinutes: 60

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is gmail
  • parameters.message_info.is_spam is true
  • parameters.event_info.mail_event_type is one of 15, 16

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "denethor@lotr.com",
    "profileId": "123456789"
  },
  "id": {
    "applicationName": "gmail",
    "customerId": "1A2B3C",
    "time": "2025-11-04 20:44:43.248000000",
    "uniqueQualifier": "-123456789"
  },
  "ipAddress": "1.1.1.1",
  "kind": "admin#reports#activity",
  "name": "delivery",
  "p_any_actor_ids": [
    "1234567891234"
  ],
  "p_any_domain_names": [
    "evil.com"
  ],
  "p_any_ip_addresses": [
    "1.1.1.1"
  ],
  "p_event_time": "2025-11-04 20:44:43.248000000",
  "p_log_type": "GSuite.ActivityEvent",
  "p_parse_time": "2025-11-04 20:49:46.688935963",
  "p_row_id": "0000000000de09c1dc6f0828cbad2ca5",
  "p_schema_version": 0,
  "p_source_id": "7ee69d4d-df1b-40b3-b5e8-6826dee34b1c",
  "p_source_label": "Google Workspace",
  "p_udm": {
    "source": {
      "address": "1.1.1.1",
      "ip": "1.1.1.1"
    },
    "user": {
      "provider_id": "123456789"
    }
  },
  "parameters": {
    "event_info": {
      "elapsed_time_usec": 368746,
      "mail_event_type": 15,
      "timestamp_usec": 1762289083248347
    },
    "message_info": {
      "action_type": 19,
      "flattened_destinations": "gmail-for-work-catchall::denethor@lotr.com",
      "is_spam": true,
      "link_domain": [
        "evil.com"
      ],
      "message_set": {
        "type": 46
      },
      "payload_size": 12345,
      "subject": "You won 1 Million Dollar"
    }
  },
  "type": "delivery_type"
}

GSuite Login Type

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite, Configuration Required, Initial Access:Valid Accounts
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A login of a non-approved type was detected for this user.

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Detection logic

# allow-list of approved login types
# comment or uncomment approved login types as needed
APPROVED_LOGIN_TYPES = {
    "exchange",
    "google_password",
    "reauth",
    "saml",
    # "unknown",
}

# allow-list any application names here
APPROVED_APPLICATION_NAMES = {"saml"}


def rule(event):
    if event.get("type") != "login":
        return False

    if event.get("name") == "logout":
        return False

    if (
        event.deep_get("parameters", "login_type") in APPROVED_LOGIN_TYPES
        or event.deep_get("id", "applicationName") in APPROVED_APPLICATION_NAMES
    ):
        return False

    return True


def title(event):
    return (
        f"A login attempt of a non-approved type was detected for user "
        f"[{event.deep_get('actor', 'email', default='<UNKNOWN_USER>')}]"
    )

Rule specification

AnalysisType: rule
Filename: gsuite_login_type.py
RuleID: "GSuite.LoginType"
DisplayName: "GSuite Login Type"
Enabled: false
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Configuration Required
  - Initial Access:Valid Accounts
Reports:
  MITRE ATT&CK:
    - TA0001:T1078
Severity: Medium
Description: >
  A login of a non-approved type was detected for this user.
Reference: https://support.google.com/a/answer/9039184?hl=en&sjid=864417124752637253-EU
Runbook: >
  Correct the user account settings so that only logins of approved types are available.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • type is login
  • name is not logout
  • parameters.login_type is not one of exchange, google_password, reauth, saml
  • id.applicationName is not one of saml

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
id.applicationNameeqsamlexcludes:id.applicationName field:"id.applicationName" value:"saml"
parameters.login_typeinexchange, google_password, reauth, samlexcludes:parameters.login_type

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

Correct the user account settings so that only logins of approved types are available.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "some.user@somedomain.com"
  },
  "id": {
    "applicationName": "login"
  },
  "name": "login_success",
  "parameters": {
    "login_type": "turbo-snail"
  },
  "type": "login"
}

Gsuite Mail forwarded to external domain

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite, Collection:Email Collection
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A user has configured mail forwarding to an external domain

MITRE ATT&CK coverage

TacticTechniques
Collection

Telemetry coverage

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") not in ("user_accounts", "login"):
        return False

    if event.get("name") == "email_forwarding_out_of_domain":
        actor_domain = event.deep_get("actor", "email", default="@").split("@")[-1]
        target_domain = event.deep_get(
            "parameters", "email_forwarding_destination_address", default="@"
        ).split("@")[-1]
        if actor_domain != target_domain:
            return True

    return False


def title(event):
    external_address = event.deep_get("parameters", "email_forwarding_destination_address")
    user = event.deep_get("actor", "email")

    return f"An email forwarding rule was created by {user} to {external_address}"

Rule specification

AnalysisType: rule
Filename: gsuite_external_forwarding.py
RuleID: "GSuite.ExternalMailForwarding"
DisplayName: "Gsuite Mail forwarded to external domain"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Collection:Email Collection
Reports:
  MITRE ATT&CK:
    - TA0009:T1114
Severity: Medium
Description: >
  A user has configured mail forwarding to an external domain
Reference: https://support.google.com/mail/answer/10957?hl=en&sjid=864417124752637253-EU
Runbook: >
  Follow up with user to remove this forwarding rule if not allowed.
SummaryAttributes:
  - p_any_emails

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is one of user_accounts, login
  • name is email_forwarding_out_of_domain

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email
email_forwarding_destination_addressparameters.email_forwarding_destination_address

Response runbook

Follow up with user to remove this forwarding rule if not allowed.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "homer.simpson@.springfield.io"
  },
  "id": {
    "applicationName": "user_accounts",
    "customerId": "D12345"
  },
  "name": "email_forwarding_out_of_domain",
  "parameters": {
    "email_forwarding_destination_address": "HSimpson@gmail.com"
  },
  "type": "email_forwarding_change"
}

GSuite Many Docs Deleted Query

#
Source
github.com/panther-labs/panther-analysis

Query to search for a user deleting many documents.

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule specification

AnalysisType: scheduled_query
Description: Query to search for a user deleting many documents.
Enabled: false
SnowflakeQuery: |
  SELECT
      actor:email AS user,
      ARRAY_AGG( DISTINCT parameters:doc_title) AS deleted_files,
      ARRAY_SIZE(deleted_files) as delete_count,
      TIME_SLICE(p_event_time, 60, 'minute') as t_s
  FROM panther_logs.public.gsuite_activityevent
  WHERE p_occurs_since('1 day')
      AND name = 'trash'
  GROUP BY actor:email, t_s
  HAVING delete_count > 10
  ORDER BY delete_count DESC

DatabricksQuery: |
  SELECT
      actor:email AS user,
      COLLECT_SET(parameters:doc_title) AS deleted_files,
      SIZE(COLLECT_SET(parameters:doc_title)) AS delete_count,
      DATE_TRUNC('hour', p_event_time) AS t_s
  FROM panther_logs.gsuite_activityevent
  WHERE p_occurs_since('1 day')
      AND name = 'trash'
  GROUP BY actor:email, DATE_TRUNC('hour', p_event_time)
  HAVING delete_count > 10
  ORDER BY delete_count DESC
QueryName: "GSuite Many Docs Deleted Query"
Schedule:
  RateMinutes: 1440
  TimeoutMinutes: 1

Stages and Predicates

Stage 1: source

Table
panther_logs.public.gsuite_activityevent

Stage 2: filter

  • name is trash
Grouped by
actor:email, t_s
Window
1d

Stage 3: having

  • delete_count is greater than 10

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
useractor:email
deleted_filesARRAY_AGG ( DISTINCT parameters:doc_title )
delete_countARRAY_SIZE ( deleted_files )
t_sTIME_SLICE ( p_event_time , 60 , 'minute' )

GSuite Many Docs Downloaded Query

#
Source
github.com/panther-labs/panther-analysis

Query to search high document download counts by users.

Telemetry coverage

PlatformRecord / event type
Google Workspacedownload: Download

Rules detecting the same action

These rules filter on the same operation.

Rule specification

AnalysisType: scheduled_query
Description: Query to search high document download counts by users.
Enabled: false
SnowflakeQuery: |
  SELECT
      actor:email AS user,
      ARRAY_SLICE(ARRAY_UNIQUE_AGG(parameters:doc_title), 0, 100) AS downloaded_files,
      count(distinct parameters:doc_title) as download_count,
      TIME_SLICE(p_event_time, 60, 'minute') as t_s
  FROM panther_logs.public.gsuite_activityevent
  WHERE p_occurs_since('1 day')
      AND name = 'download'
  GROUP BY actor:email, t_s
  HAVING download_count > 10
  ORDER BY download_count DESC

DatabricksQuery: |
  SELECT
      actor:email AS user,
      SLICE(COLLECT_SET(parameters:doc_title), 1, 100) AS downloaded_files,
      COUNT(DISTINCT parameters:doc_title) AS download_count,
      DATE_TRUNC('hour', p_event_time) AS t_s
  FROM panther_logs.gsuite_activityevent
  WHERE p_occurs_since('1 day')
      AND name = 'download'
  GROUP BY actor:email, DATE_TRUNC('hour', p_event_time)
  HAVING download_count > 10
  ORDER BY download_count DESC
QueryName: "GSuite Many Docs Downloaded Query"
Schedule:
  RateMinutes: 1440
  TimeoutMinutes: 2

Stages and Predicates

Stage 1: source

Table
panther_logs.public.gsuite_activityevent

Stage 2: filter

  • name is download
Grouped by
actor:email, t_s
Window
1d

Stage 3: having

  • download_count is greater than 10

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
useractor:email
downloaded_filesARRAY_SLICE ( ARRAY_UNIQUE_AGG ( parameters:doc_title ) , 0 , 100 )
download_countcount ( DISTINCT parameters:doc_title )
t_sTIME_SLICE ( p_event_time , 60 , 'minute' )

GSuite Overly Visible Drive Document

#
Severity
informational
Group by
actor.profileId
Log types
GSuite.ActivityEvent
Tags
GSuite, Collection:Data from Information Repositories
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Google drive resource that is overly visible has been modified.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Drive (any event)

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_details_lookup as details_lookup
from panther_gsuite_helpers import gsuite_parameter_lookup as param_lookup

RESOURCE_CHANGE_EVENTS = {
    "create",
    "move",
    "upload",
    "edit",
}

PERMISSIVE_VISIBILITY = {
    "people_with_link",
    "public_on_the_web",
}


def rule(event):
    if event.deep_get("id", "applicationName") != "drive":
        return False

    details = details_lookup("access", RESOURCE_CHANGE_EVENTS, event)
    return (
        bool(details)
        and param_lookup(details.get("parameters", {}), "visibility") in PERMISSIVE_VISIBILITY
    )


def dedup(event):
    user = event.deep_get("actor", "email")
    if user is None:
        user = event.deep_get("actor", "profileId", default="<UNKNOWN_PROFILEID>")
    return user


def title(event):
    details = details_lookup("access", RESOURCE_CHANGE_EVENTS, event)
    doc_title = param_lookup(details.get("parameters", {}), "doc_title")
    share_settings = param_lookup(details.get("parameters", {}), "visibility")
    user = event.deep_get("actor", "email")
    if user is None:
        user = event.deep_get("actor", "profileId", default="<UNKNOWN_PROFILEID>")
    return (
        f"User [{user}]"
        f" modified a document [{doc_title}] that has overly permissive share"
        f" settings [{share_settings}]"
    )

Rule specification

AnalysisType: rule
Filename: gsuite_drive_overly_visible.py
RuleID: "GSuite.DriveOverlyVisible"
DisplayName: "GSuite Overly Visible Drive Document"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Collection:Data from Information Repositories
Reports:
  MITRE ATT&CK:
    - TA0009:T1213
Severity: Info
Description: >
  A Google drive resource that is overly visible has been modified.
Reference: https://support.google.com/docs/answer/2494822?hl=en&co=GENIE.Platform%3DDesktop&sjid=864417124752637253-EU
Runbook: >
  Investigate whether the drive document is appropriate to be this visible.
SummaryAttributes:
  - actor:email
DedupPeriodMinutes: 360 # 6 hours

Stages and Predicates

Fires on GSuite.ActivityEvent events when the condition below holds.

Condition

  • id.applicationName is drive

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Alert deduplication
repeat matches within 6h group into one alert

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
profileIdactor.profileId

Response runbook

Investigate whether the drive document is appropriate to be this visible.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "bobert@example.com"
  },
  "id": {
    "applicationName": "drive"
  },
  "name": "edit",
  "p_log_type": "GSuite.ActivityEvent",
  "p_row_id": "111222",
  "parameters": {
    "doc_title": "my shared document",
    "visibility": "people_with_link"
  },
  "type": "access"
}

GSuite Passthrough Rule Triggered

#
Severity
informational
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A GSuite rule was triggered.

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Rules (any event)

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") != "rules":
        return False

    if not event.deep_get("parameters", "triggered_actions"):
        return False
    return True


def title(event):
    rule_severity = event.deep_get("parameters", "severity")
    if event.deep_get("parameters", "rule_name"):
        return (
            "GSuite "
            + rule_severity
            + " Severity Rule Triggered: "
            + event.deep_get("parameters", "rule_name")
        )
    return "GSuite " + rule_severity + " Severity Rule Triggered"


def severity(event):
    return event.deep_get("parameters", "severity", default="INFO")

Rule specification

AnalysisType: rule
Filename: gsuite_passthrough_rule.py
RuleID: "GSuite.Rule"
DisplayName: "GSuite Passthrough Rule Triggered"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: Info
Description: >
  A GSuite rule was triggered.
Reference: https://support.google.com/a/answer/9420866
Runbook: >
  Investigate what triggered the rule.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is rules
  • parameters.triggered_actions is present

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
severityparameters.severity
rule_nameparameters.rule_name

Response runbook

Investigate what triggered the rule.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "some.user@somedomain.com"
  },
  "id": {
    "applicationName": "rules"
  },
  "parameters": {
    "data_source": "DRIVE",
    "severity": "HIGH",
    "triggered_actions": [
      {
        "action_type": "DRIVE_UNFLAG_DOCUMENT"
      }
    ]
  }
}

GSuite User Advanced Protection Change

#
Severity
low
Log types
GSuite.ActivityEvent
Tags
GSuite, Defense Evasion:Impair Defenses
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A user disabled advanced protection for themselves.

MITRE ATT&CK coverage

TacticTechniques
Defense Impairment

Telemetry coverage

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") != "user_accounts":
        return False

    return bool(event.get("name") == "titanium_unenroll")


def title(event):
    return (
        f"Advanced protection was disabled for user "
        f"[{event.deep_get('actor', 'email', default='<UNKNOWN_EMAIL>')}]"
    )

Rule specification

AnalysisType: rule
Filename: gsuite_advanced_protection.py
RuleID: "GSuite.AdvancedProtection"
DisplayName: "GSuite User Advanced Protection Change"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Defense Evasion:Impair Defenses
Reports:
  MITRE ATT&CK:
    - TA0005:T1562
Severity: Low
Description: >
  A user disabled advanced protection for themselves.
Reference: https://support.google.com/a/answer/9378686?hl=en&sjid=864417124752637253-EU
Runbook: >
  Have the user re-enable Google Advanced Protection
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is user_accounts
  • name is titanium_unenroll

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

Have the user re-enable Google Advanced Protection

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "homer.simpson@example.com"
  },
  "id": {
    "applicationName": "user_accounts"
  },
  "name": "titanium_unenroll",
  "type": "titanium_change"
}

GSuite User Banned from Group

#
Severity
low
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A GSuite user was banned from an enterprise group by moderator action.

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") != "groups_enterprise":
        return False

    if event.get("type") == "moderator_action":
        return bool(event.get("name") == "ban_user_with_moderation")

    return False


def title(event):
    return (
        f"User [{event.deep_get('actor', 'email', default='<UNKNOWN_EMAIL>')}] "
        f"banned another user from a group."
    )

Rule specification

AnalysisType: rule
Filename: gsuite_group_banned_user.py
RuleID: "GSuite.GroupBannedUser"
DisplayName: "GSuite User Banned from Group"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: Low
Description: >
  A GSuite user was banned from an enterprise group by moderator action.
Reference: https://support.google.com/a/users/answer/9303224?hl=en&sjid=864417124752637253-EU
Runbook: >
  Investigate the banned user to see if further disciplinary action needs to be taken.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is groups_enterprise
  • type is moderator_action
  • name is ban_user_with_moderation

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

Investigate the banned user to see if further disciplinary action needs to be taken.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "homer.simpson@example.com"
  },
  "id": {
    "applicationName": "groups_enterprise"
  },
  "name": "ban_user_with_moderation",
  "type": "moderator_action"
}

GSuite User Device Compromised

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

GSuite reported a user's device has been compromised.

Telemetry coverage

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") != "mobile":
        return False

    if event.get("name") == "DEVICE_COMPROMISED_EVENT":
        return bool(event.deep_get("parameters", "DEVICE_COMPROMISED_STATE") == "COMPROMISED")

    return False


def title(event):
    return (
        f"User [{event.deep_get('parameters', 'USER_EMAIL', default='<UNKNOWN_USER>')}]'s "
        f"device was compromised"
    )

Rule specification

AnalysisType: rule
Filename: gsuite_mobile_device_compromise.py
RuleID: "GSuite.DeviceCompromise"
DisplayName: "GSuite User Device Compromised"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: Medium
Description: >
  GSuite reported a user's device has been compromised.
Reference: https://support.google.com/a/answer/7562165?hl=en&sjid=864417124752637253-EU
Runbook: >
  Have the user change their passwords and reset the device.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is mobile
  • name is DEVICE_COMPROMISED_EVENT
  • parameters.DEVICE_COMPROMISED_STATE is COMPROMISED

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
USER_EMAILparameters.USER_EMAIL

Response runbook

Have the user change their passwords and reset the device.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "homer.simpson@example.io"
  },
  "id": {
    "applicationName": "mobile"
  },
  "name": "DEVICE_COMPROMISED_EVENT",
  "parameters": {
    "DEVICE_COMPROMISED_STATE": "COMPROMISED",
    "USER_EMAIL": "homer.simpson@example.io"
  },
  "type": "device_updates"
}

GSuite User Device Unlock Failures

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite, Credential Access:Brute Force
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

Someone failed to unlock a user's device multiple times in quick succession.

MITRE ATT&CK coverage

TacticTechniques
Credential Access

Telemetry coverage

Detection logic

MAX_UNLOCK_ATTEMPTS = 10


def rule(event):
    if event.deep_get("id", "applicationName") != "mobile":
        return False

    if event.get("name") == "FAILED_PASSWORD_ATTEMPTS_EVENT":
        attempts = event.deep_get("parameters", "FAILED_PASSWD_ATTEMPTS")
        return int(attempts if attempts else 0) > MAX_UNLOCK_ATTEMPTS

    return False


def title(event):
    return (
        f"User [{event.deep_get('actor', 'email', default='<UNKNOWN_USER>')}]"
        f"'s device had multiple failed unlock attempts"
    )

Rule specification

AnalysisType: rule
Filename: gsuite_mobile_device_screen_unlock_fail.py
RuleID: "GSuite.DeviceUnlockFailure"
DisplayName: "GSuite User Device Unlock Failures"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Credential Access:Brute Force
Reports:
  MITRE ATT&CK:
    - TA0006:T1110
Severity: Medium
Description: >
  Someone failed to unlock a user's device multiple times in quick succession.
Reference: https://support.google.com/a/answer/6350074?hl=en
Runbook: >
  Verify that these unlock attempts came from the user, and not a malicious actor which has acquired the user's device.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is mobile
  • name is FAILED_PASSWORD_ATTEMPTS_EVENT

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

Verify that these unlock attempts came from the user, and not a malicious actor which has acquired the user's device.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "homer.simpson@example.io"
  },
  "id": {
    "applicationName": "mobile"
  },
  "name": "FAILED_PASSWORD_ATTEMPTS_EVENT",
  "parameters": {
    "FAILED_PASSWD_ATTEMPTS": 100,
    "USER_EMAIL": "homer.simpson@example.io"
  },
  "type": "device_updates"
}

GSuite User Password Leaked

#
Severity
high
Log types
GSuite.ActivityEvent
Tags
GSuite, Credential Access:Unsecured Credentials
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

GSuite reported a user's password has been compromised, so they disabled the account.

MITRE ATT&CK coverage

TacticTechniques
Credential Access

Telemetry coverage

Detection logic

PASSWORD_LEAKED_EVENTS = {
    "account_disabled_password_leak",
}


def rule(event):
    if event.deep_get("id", "applicationName") != "login":
        return False

    if event.get("type") == "account_warning":
        return bool(event.get("name") in PASSWORD_LEAKED_EVENTS)
    return False


def title(event):
    user = event.deep_get("parameters", "affected_email_address")
    if not user:
        user = "<UNKNOWN_USER>"
    return f"User [{user}]'s account was disabled due to a password leak"

Rule specification

AnalysisType: rule
Filename: gsuite_leaked_password.py
RuleID: "GSuite.LeakedPassword"
DisplayName: "GSuite User Password Leaked"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Credential Access:Unsecured Credentials
Reports:
  MITRE ATT&CK:
    - TA0006:T1552
Severity: High
Description: >
  GSuite reported a user's password has been compromised, so they disabled the account.
Reference: https://support.google.com/a/answer/2984349?hl=en#zippy=%2Cstep-temporarily-suspend-the-suspected-compromised-user-account%2Cstep-investigate-the-account-for-unauthorized-activity%2Cstep-revoke-access-to-the-affected-account%2Cstep-return-access-to-the-user-again%2Cstep-enroll-in--step-verification-with-security-keys%2Cstep-add-secure-or-update-recovery-options%2Cstep-enable-account-activity-alerts
Runbook: >
  GSuite has already disabled the compromised user's account. Consider investigating how the user's account was compromised, and reset their account and password. Advise the user to change any other passwords in use that are the sae as the compromised password.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is login
  • type is account_warning
  • name is one of account_disabled_password_leak

Indicators

These rows show field, operator, and value matches.

Response runbook

GSuite has already disabled the compromised user's account. Consider investigating how the user's account was compromised, and reset their account and password. Advise the user to change any other passwords in use that are the sae as the compromised password.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "id": {
    "applicationName": "login"
  },
  "name": "account_disabled_password_leak",
  "parameters": {
    "affected_email_address": "homer.simpson@example.com"
  },
  "type": "account_warning"
}

GSuite User Suspended

#
Severity
high
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A GSuite user was suspended, the account may have been compromised by a spam network.

Telemetry coverage

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context

USER_SUSPENDED_EVENTS = {
    "account_disabled_generic",
    "account_disabled_spamming_through_relay",
    "account_disabled_spamming",
    "account_disabled_hijacked",
}


def rule(event):
    if event.deep_get("id", "applicationName") != "login":
        return False

    return bool(event.get("name") in USER_SUSPENDED_EVENTS)


def title(event):
    user = event.deep_get("parameters", "affected_email_address")
    if not user:
        user = "<UNKNOWN_USER>"
    return f"User [{user}]'s account was disabled"


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_user_suspended.py
RuleID: "GSuite.UserSuspended"
DisplayName: "GSuite User Suspended"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: High
Description: >
  A GSuite user was suspended, the account may have been compromised by a spam network.
Reference: https://support.google.com/drive/answer/40695?hl=en&sjid=864417124752637253-EU
Runbook: >
  Investigate the behavior that got the account suspended. Verify with the user that this intended behavior. If not, the account may have been compromised.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is login
  • name is one of account_disabled_generic, account_disabled_spamming_through_relay, account_disabled_spamming, account_disabled_hijacked

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
id.applicationNameeq
  • login
field:"id.applicationName" kind:eq value:"login"
namein
  • account_disabled_generic
  • account_disabled_hijacked
  • account_disabled_spamming
  • account_disabled_spamming_through_relay
field:"name" kind:in

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters

Response runbook

Investigate the behavior that got the account suspended. Verify with the user that this intended behavior. If not, the account may have been compromised.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "id": {
    "applicationName": "login"
  },
  "kind": "admin#reports#activity",
  "name": "account_disabled_spamming",
  "parameters": {
    "affected_email_address": "bobert@ext.runpanther.io"
  },
  "type": "account_warning"
}

GSuite User Two Step Verification Change

#
Severity
low
Log types
GSuite.ActivityEvent
Tags
GSuite, Defense Evasion:Modify Authentication Process
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A user disabled two step verification for themselves.

MITRE ATT&CK coverage

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

def rule(event):
    if event.deep_get("id", "applicationName") != "user_accounts":
        return False

    if event.get("type") == "2sv_change" and event.get("name") == "2sv_disable":
        return True

    return False


def title(event):
    return (
        f"Two step verification was disabled for user"
        f" [{event.deep_get('actor', 'email', default='<UNKNOWN_USER>')}]"
    )

Rule specification

AnalysisType: rule
Filename: gsuite_two_step_verification.py
RuleID: "GSuite.TwoStepVerification"
DisplayName: "GSuite User Two Step Verification Change"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Defense Evasion:Modify Authentication Process
Reports:
  MITRE ATT&CK:
    - TA0005:T1556
Severity: Low
Description: >
  A user disabled two step verification for themselves.
Reference: https://support.google.com/mail/answer/185839?hl=en&co=GENIE.Platform%3DDesktop&sjid=864417124752637253-EU
Runbook: >
  Depending on company policy, either suggest or require the user re-enable two step verification.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is user_accounts
  • type is 2sv_change
  • name is 2sv_disable

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

Depending on company policy, either suggest or require the user re-enable two step verification.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "some.user@somedomain.com"
  },
  "id": {
    "applicationName": "user_accounts"
  },
  "kind": "admin#reports#activity",
  "name": "2sv_disable",
  "type": "2sv_change"
}

GSuite Workspace Calendar External Sharing Setting Change

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Workspace Admin Changed The Sharing Settings for Primary Calendars

MITRE ATT&CK coverage

TacticTechniques
Discovery

Telemetry coverage

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    if not all(
        [
            (event.get("name", "") == "CHANGE_CALENDAR_SETTING"),
            (event.deep_get("parameters", "SETTING_NAME", default="") == "SHARING_OUTSIDE_DOMAIN"),
        ]
    ):
        return False
    return event.deep_get("parameters", "NEW_VALUE", default="") in [
        "READ_WRITE_ACCESS",
        "READ_ONLY_ACCESS",
        "MANAGE_ACCESS",
    ]


def title(event):
    return (
        f"GSuite workspace setting for default calendar sharing was changed by "
        f"[{event.deep_get('actor', 'email', default='<UNKNOWN_EMAIL>')}] "
        + f"from [{event.deep_get('parameters', 'OLD_VALUE', default='<NO_OLD_SETTING_FOUND>')}] "
        + f"to [{event.deep_get('parameters', 'NEW_VALUE', default='<NO_NEW_SETTING_FOUND>')}]"
    )


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_workspace_calendar_external_sharing.py
RuleID: "GSuite.Workspace.CalendarExternalSharingSetting"
DisplayName: "GSuite Workspace Calendar External Sharing Setting Change"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Reports:
  MITRE ATT&CK:
    - TA0007:T1087
Severity: Medium
Description: >
  A Workspace Admin Changed The Sharing Settings for Primary Calendars
Reference: https://support.google.com/a/answer/60765?hl=en
Runbook: >
  Restore the calendar sharing setting to the previous value.
  If unplanned, use indicator search to identify other activity from this administrator.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • name is CHANGE_CALENDAR_SETTING
  • parameters.SETTING_NAME is SHARING_OUTSIDE_DOMAIN
  • parameters.NEW_VALUE is one of READ_WRITE_ACCESS, READ_ONLY_ACCESS, MANAGE_ACCESS

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters
OLD_VALUEparameters.OLD_VALUE
NEW_VALUEparameters.NEW_VALUE

Response runbook

Restore the calendar sharing setting to the previous value. If unplanned, use indicator search to identify other activity from this administrator.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "example@example.io",
    "profileId": "12345"
  },
  "id": {
    "applicationName": "admin",
    "customerId": "D12345",
    "time": "2022-12-11 01:06:26.303000000",
    "uniqueQualifier": "-12345"
  },
  "ipAddress": "12.12.12.12",
  "kind": "admin#reports#activity",
  "name": "CHANGE_CALENDAR_SETTING",
  "parameters": {
    "DOMAIN_NAME": "example.io",
    "NEW_VALUE": "READ_ONLY_ACCESS",
    "OLD_VALUE": "DEFAULT",
    "ORG_UNIT_NAME": "Example IO",
    "SETTING_NAME": "SHARING_OUTSIDE_DOMAIN"
  },
  "type": "CALENDAR_SETTINGS"
}

GSuite Workspace Data Export Has Been Created

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Workspace Admin Has Created a Data Export

Telemetry coverage

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    return event.get("name", "").startswith("CUSTOMER_TAKEOUT_")


def title(event):
    return (
        f"GSuite Workspace Data Export "
        f"[{event.get('name', '<NO_EVENT_NAME>')}] "
        f"performed by [{event.deep_get('actor', 'email', default='<NO_ACTOR_FOUND>')}]"
    )


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_workspace_data_export_created.py
RuleID: "GSuite.Workspace.DataExportCreated"
DisplayName: "GSuite Workspace Data Export Has Been Created"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: Medium
Description: >
  A Workspace Admin Has Created a Data Export
Reference: https://support.google.com/a/answer/100458?hl=en&sjid=864417124752637253-EU
Runbook: |
  Verify the intent of this Data Export. If intent cannot be verified, then
  a search on the actor's other activities is advised.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when the condition below holds.

Condition

  • name starts with CUSTOMER_TAKEOUT_

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
namestarts_with
  • CUSTOMER_TAKEOUT_
field:"name" kind:starts_with value:"CUSTOMER_TAKEOUT_"

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters

Response runbook

Verify the intent of this Data Export. If intent cannot be verified, then

a search on the actor's other activities is advised.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "admin@example.io",
    "profileId": "11011111111111111111111"
  },
  "id": {
    "applicationName": "admin",
    "customerId": "D12345",
    "time": "2022-12-10 22:21:40.079000000",
    "uniqueQualifier": "-2833899999999999999"
  },
  "kind": "admin#reports#activity",
  "name": "CUSTOMER_TAKEOUT_CREATED",
  "parameters": {
    "OBFUSCATED_CUSTOMER_TAKEOUT_REQUEST_ID": "00mmmmmmmmmmmmm"
  },
  "type": "CUSTOMER_TAKEOUT"
}

GSuite Workspace Gmail Default Routing Rule Modified

#
Severity
high
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Workspace Admin Has Modified A Default Routing Rule In Gmail

MITRE ATT&CK coverage

TacticTechniques
Persistence

Telemetry coverage

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    if all(
        [
            (event.get("type", "") == "EMAIL_SETTINGS"),
            (event.get("name", "").endswith("_GMAIL_SETTING")),
            (event.deep_get("parameters", "SETTING_NAME", default="") == "MESSAGE_SECURITY_RULE"),
        ]
    ):
        return True
    return False


def title(event):
    # Gmail records the event name as DELETE_GMAIL_SETTING/CREATE_GMAIL_SETTING
    # We shouldn't be able to enter title() unless event[name] ends with
    #  _GMAIL_SETTING, and as such change_type assumes the happy path.
    change_type = f"{event.get('name', '').split('_')[0].lower()}d"
    return (
        f"GSuite Gmail Default Routing Rule Was "
        f"[{change_type}] "
        f"by [{event.deep_get('actor', 'email', default='<UNKNOWN_EMAIL>')}]"
    )


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_workspace_gmail_default_routing_rule.py
RuleID: "GSuite.Workspace.GmailDefaultRoutingRuleModified"
DisplayName: "GSuite Workspace Gmail Default Routing Rule Modified"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Reports:
  MITRE ATT&CK:
    - TA0003:T1098
Severity: High
Description: >
  A Workspace Admin Has Modified A Default Routing Rule In Gmail
Reference: https://support.google.com/a/answer/2368153?hl=en
Runbook: |
  Administrators use Default Routing to set up how inbound email is
  delivered within an organization. The configuration of the default routing
  rule needs to be inspected in order to verify the intent of the rule is benign.

  If this change was not planned, inspect the other actions taken by this actor.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • type is EMAIL_SETTINGS
  • name ends with _GMAIL_SETTING
  • parameters.SETTING_NAME is MESSAGE_SECURITY_RULE

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters

Response runbook

Administrators use Default Routing to set up how inbound email is

delivered within an organization. The configuration of the default routing

rule needs to be inspected in order to verify the intent of the rule is benign.

If this change was not planned, inspect the other actions taken by this actor.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "user@example.io",
    "profileId": "110555555555555555555"
  },
  "id": {
    "applicationName": "admin",
    "customerId": "D12345",
    "time": "2022-12-11 00:50:03.493000000",
    "uniqueQualifier": "-6333333333333333333"
  },
  "ipAddress": "12.12.12.12",
  "kind": "admin#reports#activity",
  "name": "CREATE_GMAIL_SETTING",
  "parameters": {
    "SETTING_NAME": "MESSAGE_SECURITY_RULE",
    "USER_DEFINED_SETTING_NAME": "44444"
  },
  "type": "EMAIL_SETTINGS"
}

GSuite Workspace Gmail Pre-Delivery Message Scanning Disabled

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Workspace Admin Has Disabled Pre-Delivery Scanning For Gmail.

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    # the shape of the items in parameters can change a bit ( like NEW_VALUE can be an array )
    #  when the applicationName is something other than admin
    if event.deep_get("id", "applicationName", default="").lower() != "admin":
        return False
    if all(
        [
            (event.get("name", "") == "CHANGE_APPLICATION_SETTING"),
            (event.deep_get("parameters", "APPLICATION_NAME", default="").lower() == "gmail"),
            (event.deep_get("parameters", "NEW_VALUE", default="").lower() == "true"),
            (
                event.deep_get("parameters", "SETTING_NAME", default="")
                == "DelayedDeliverySettingsProto disable_delayed_delivery_for_suspicious_email"
            ),
        ]
    ):
        return True
    return False


def title(event):
    return (
        f"GSuite Gmail Enhanced Pre-Delivery Scanning was disabled "
        f"for [{event.deep_get('parameters', 'ORG_UNIT_NAME', default='<NO_ORG_UNIT_NAME>')}] "
        f"by [{event.deep_get('actor', 'email', default='<UNKNOWN_EMAIL>')}]"
    )


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_workspace_gmail_enhanced_predelivery_scanning.py
RuleID: "GSuite.Workspace.GmailPredeliveryScanningDisabled"
DisplayName: "GSuite Workspace Gmail Pre-Delivery Message Scanning Disabled"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Reports:
  MITRE ATT&CK:
    - TA0001:T1566
Severity: Medium
Description: >
  A Workspace Admin Has Disabled Pre-Delivery Scanning For Gmail.
Reference: https://support.google.com/a/answer/7380368
Runbook: |
  Pre-delivery scanning is a feature in Gmail that subjects suspicious emails
  to additional automated scrutiny by Google.

  If this change was not intentional, inspect the other actions taken by this actor.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is admin (case-insensitive)
  • name is CHANGE_APPLICATION_SETTING
  • parameters.APPLICATION_NAME is gmail (case-insensitive)
  • parameters.NEW_VALUE is true (case-insensitive)
  • parameters.SETTING_NAME is DelayedDeliverySettingsProto disable_delayed_delivery_for_suspicious_email

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters
ORG_UNIT_NAMEparameters.ORG_UNIT_NAME

Response runbook

Pre-delivery scanning is a feature in Gmail that subjects suspicious emails

to additional automated scrutiny by Google.

If this change was not intentional, inspect the other actions taken by this actor.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "example@example.io",
    "profileId": "12345"
  },
  "id": {
    "applicationName": "admin",
    "customerId": "D12345",
    "time": "2022-12-11 03:42:54.859000000",
    "uniqueQualifier": "-12345"
  },
  "ipAddress": "12.12.12.12",
  "kind": "admin#reports#activity",
  "name": "CHANGE_APPLICATION_SETTING",
  "parameters": {
    "APPLICATION_EDITION": "business_plus_2021",
    "APPLICATION_NAME": "Gmail",
    "NEW_VALUE": "true",
    "ORG_UNIT_NAME": "Example IO",
    "SETTING_NAME": "DelayedDeliverySettingsProto disable_delayed_delivery_for_suspicious_email"
  },
  "type": "APPLICATION_SETTINGS"
}

GSuite Workspace Gmail Security Sandbox Disabled

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Workspace Admin Has Disabled The Security Sandbox

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    if event.deep_get("id", "applicationName", default="").lower() != "admin":
        return False
    if all(
        [
            (event.get("name", "") == "CHANGE_APPLICATION_SETTING"),
            (event.deep_get("parameters", "APPLICATION_NAME", default="").lower() == "gmail"),
            (event.deep_get("parameters", "NEW_VALUE", default="").lower() == "false"),
            (
                event.deep_get("parameters", "SETTING_NAME", default="")
                == "AttachmentDeepScanningSettingsProto deep_scanning_enabled"
            ),
        ]
    ):
        return True
    return False


def title(event):
    return (
        f"GSuite Gmail Security Sandbox was disabled "
        f"for [{event.deep_get('parameters', 'ORG_UNIT_NAME', default='<NO_ORG_UNIT_NAME>')}] "
        f"by [{event.deep_get('actor', 'email', default='<UNKNOWN_EMAIL>')}]"
    )


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_workspace_gmail_security_sandbox_disabled.py
RuleID: "GSuite.Workspace.GmailSecuritySandboxDisabled"
DisplayName: "GSuite Workspace Gmail Security Sandbox Disabled"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Reports:
  MITRE ATT&CK:
    - TA0001:T1566
Severity: Medium
Description: >
  A Workspace Admin Has Disabled The Security Sandbox
Reference: https://support.google.com/a/answer/7676854?hl=en#zippy=%2Cfind-security-sandbox-settings%2Cabout-security-sandbox-rules-and-other-scans
Runbook: >
  Gmail's Security Sandbox enables rule based scanning of email content.

  If this change was not intentional, inspect the other actions taken by this actor.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is admin (case-insensitive)
  • name is CHANGE_APPLICATION_SETTING
  • parameters.APPLICATION_NAME is gmail (case-insensitive)
  • parameters.NEW_VALUE is false (case-insensitive)
  • parameters.SETTING_NAME is AttachmentDeepScanningSettingsProto deep_scanning_enabled

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters
ORG_UNIT_NAMEparameters.ORG_UNIT_NAME

Response runbook

Gmail's Security Sandbox enables rule based scanning of email content.

If this change was not intentional, inspect the other actions taken by this actor.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "example@example.io",
    "profileId": "12345"
  },
  "id": {
    "applicationName": "admin",
    "customerId": "D12345",
    "time": "2022-12-11 03:31:41.212000000",
    "uniqueQualifier": "-12345"
  },
  "ipAddress": "12.12.12.12",
  "kind": "admin#reports#activity",
  "name": "CHANGE_APPLICATION_SETTING",
  "parameters": {
    "APPLICATION_EDITION": "enterprise",
    "APPLICATION_NAME": "Gmail",
    "NEW_VALUE": "false",
    "ORG_UNIT_NAME": "Example IO",
    "SETTING_NAME": "AttachmentDeepScanningSettingsProto deep_scanning_enabled"
  },
  "type": "APPLICATION_SETTINGS"
}

GSuite Workspace Password Reuse Has Been Enabled

#
Severity
high
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Workspace Admin Has Enabled Password Reuse

MITRE ATT&CK coverage

TacticTechniques
Credential Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    if event.deep_get("id", "applicationName", default="").lower() != "admin":
        return False
    if all(
        [
            (event.get("name", "") == "CHANGE_APPLICATION_SETTING"),
            (event.get("type", "") == "APPLICATION_SETTINGS"),
            (event.deep_get("parameters", "NEW_VALUE", default="").lower() == "true"),
            (
                event.deep_get("parameters", "SETTING_NAME", default="")
                == "Password Management - Enable password reuse"
            ),
        ]
    ):
        return True
    return False


def title(event):
    return (
        f"GSuite Workspace Password Reuse Has Been Enabled "
        f"By [{event.deep_get('actor', 'email', default='<NO_ACTOR_FOUND>')}]"
    )


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_workspace_password_reuse_enabled.py
RuleID: "GSuite.Workspace.PasswordReuseEnabled"
DisplayName: "GSuite Workspace Password Reuse Has Been Enabled"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: High
Reports:
  MITRE ATT&CK:
    - TA0006:T1110
Description: >
  A Workspace Admin Has Enabled Password Reuse
Reference: https://support.google.com/a/answer/139399?hl=en#
Runbook: |
  Verify the intent of this Password Reuse Setting Change. If intent cannot be verified, then
  a search on the actor's other activities is advised.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is admin (case-insensitive)
  • name is CHANGE_APPLICATION_SETTING
  • type is APPLICATION_SETTINGS
  • parameters.NEW_VALUE is true (case-insensitive)
  • parameters.SETTING_NAME is Password Management - Enable password reuse

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters

Response runbook

Verify the intent of this Password Reuse Setting Change. If intent cannot be verified, then

a search on the actor's other activities is advised.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "example@example.io",
    "profileId": "12345"
  },
  "id": {
    "applicationName": "admin",
    "customerId": "D12345",
    "time": "2022-12-11 01:18:47.973000000",
    "uniqueQualifier": "-12345"
  },
  "ipAddress": "12.12.12.12",
  "kind": "admin#reports#activity",
  "name": "CHANGE_APPLICATION_SETTING",
  "parameters": {
    "APPLICATION_EDITION": "standard",
    "APPLICATION_NAME": "Security",
    "NEW_VALUE": "true",
    "OLD_VALUE": "false",
    "ORG_UNIT_NAME": "Example IO",
    "SETTING_NAME": "Password Management - Enable password reuse"
  },
  "type": "APPLICATION_SETTINGS"
}

GSuite Workspace Strong Password Enforcement Has Been Disabled

#
Severity
high
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Workspace Admin Has Disabled The Enforcement Of Strong Passwords

MITRE ATT&CK coverage

TacticTechniques
Credential Access

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    if event.deep_get("id", "applicationName", default="").lower() != "admin":
        return False
    if all(
        [
            (event.get("name", "") == "CHANGE_APPLICATION_SETTING"),
            (event.get("type", "") == "APPLICATION_SETTINGS"),
            (event.deep_get("parameters", "NEW_VALUE", default="").lower() == "off"),
            (
                event.deep_get("parameters", "SETTING_NAME", default="")
                == "Password Management - Enforce strong password"
            ),
        ]
    ):
        return True
    return False


def title(event):
    return (
        f"GSuite Workspace Strong Password Enforcement Has Been Disabled "
        f"By [{event.deep_get('actor', 'email', default='<NO_ACTOR_FOUND>')}]"
    )


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_workspace_password_enforce_strong_disabled.py
RuleID: "GSuite.Workspace.PasswordEnforceStrongDisabled"
DisplayName: "GSuite Workspace Strong Password Enforcement Has Been Disabled"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: High
Reports:
  MITRE ATT&CK:
    - TA0006:T1110
Description: >
  A Workspace Admin Has Disabled The Enforcement Of Strong Passwords
Reference: https://support.google.com/a/answer/139399?hl=en
Runbook: |
  Verify the intent of this Password Strength Setting Change. If intent cannot be verified, then
  a search on the actor's other activities is advised.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is admin (case-insensitive)
  • name is CHANGE_APPLICATION_SETTING
  • type is APPLICATION_SETTINGS
  • parameters.NEW_VALUE is off (case-insensitive)
  • parameters.SETTING_NAME is Password Management - Enforce strong password

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters

Response runbook

Verify the intent of this Password Strength Setting Change. If intent cannot be verified, then

a search on the actor's other activities is advised.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "user@example.io",
    "profileId": "110111111111111111111"
  },
  "id": {
    "applicationName": "admin",
    "customerId": "D12345",
    "time": "2022-12-11 01:33:56.306000000",
    "uniqueQualifier": "-6444444444444444444"
  },
  "ipAddress": "12.12.12.12",
  "kind": "admin#reports#activity",
  "name": "CHANGE_APPLICATION_SETTING",
  "parameters": {
    "APPLICATION_EDITION": "enterprise",
    "APPLICATION_NAME": "Security",
    "NEW_VALUE": "off",
    "OLD_VALUE": "on",
    "ORG_UNIT_NAME": "Example IO",
    "SETTING_NAME": "Password Management - Enforce strong password"
  },
  "type": "APPLICATION_SETTINGS"
}

GSuite Workspace Trusted Domain Allowlist Modified

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

A Workspace Admin Has Modified The Trusted Domains List

MITRE ATT&CK coverage

TacticTechniques
Persistence

Telemetry coverage

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    return event.get("type") == "DOMAIN_SETTINGS" and event.get("name", "").endswith(
        "_TRUSTED_DOMAINS"
    )


def title(event):
    return (
        f"GSuite Workspace Trusted Domains Modified "
        f"[{event.get('name', '<NO_EVENT_NAME>')}] "
        f"with [{event.deep_get('parameters', 'DOMAIN_NAME', default='<NO_DOMAIN_NAME>')}] "
        f"performed by [{event.deep_get('actor', 'email', default='<NO_ACTOR_FOUND>')}]"
    )


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_workspace_trusted_domains_allowlist.py
RuleID: "GSuite.Workspace.TrustedDomainsAllowlist"
DisplayName: "GSuite Workspace Trusted Domain Allowlist Modified"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: Medium
Description: >
  A Workspace Admin Has Modified The Trusted Domains List
Reference: https://support.google.com/a/answer/6160020?hl=en&sjid=864417124752637253-EU
Runbook: |
  Verify the intent of this modification. If intent cannot be verified, then
  an indicator search on the actor is advised.
SummaryAttributes:
  - actor:email
Reports:
  MITRE ATT&CK:
    - TA0003:T1098

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • type is DOMAIN_SETTINGS
  • name ends with _TRUSTED_DOMAINS

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters
DOMAIN_NAMEparameters.DOMAIN_NAME

Response runbook

Verify the intent of this modification. If intent cannot be verified, then

an indicator search on the actor is advised.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "user@example.io",
    "profileId": "110506209185950390992"
  },
  "id": {
    "applicationName": "admin",
    "customerId": "D12345",
    "time": "2022-12-11 00:01:34.643000000",
    "uniqueQualifier": "-2972206985263071668"
  },
  "kind": "admin#reports#activity",
  "name": "REMOVE_TRUSTED_DOMAINS",
  "p_source_label": "Staging",
  "parameters": {
    "DOMAIN_NAME": "evilexample.com"
  },
  "type": "DOMAIN_SETTINGS"
}

Malware Detected in Email

#
Severity
high
Entities
actor_ids, domain_names, ip_addresses
Log types
GSuite.ActivityEvent
Tags
GSuite, Gmail, Malware, Initial Access
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

Detects when malware is found in an email received by a user. Identifies different malware families including known malicious programs, viruses, worms, harmful content, and unwanted content. Severity is dynamically assigned based on the malware type, with known malicious programs and viruses triggering high-severity alerts.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Gmail (any event)

Rules detecting the same action

These rules filter on the same operation.

Detection logic

# Malware family type mapping based on Google Workspace Gmail schema
# Reference: https://support.google.com/a/answer/12384955
MALWARE_FAMILY_TYPES = {
    1: "Known malicious program",
    2: "Virus or worm",
    3: "Possible harmful message content",
    4: "Possible unwanted message content",
    5: "Other malware type",
}


def rule(event):
    if event.deep_get("id", "applicationName", default="<UNKNOWN_APPLICATION>") != "gmail":
        return False

    # Check if malware was detected in the message
    malware_family = event.deep_get(
        "parameters", "message_info", "attachment", "malware_family", default=None
    )

    return malware_family is not None


def title(event):
    user = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    malware_family = event.deep_get(
        "parameters", "message_info", "attachment", "malware_family", default=0
    )
    malware_type = MALWARE_FAMILY_TYPES.get(malware_family, f"Unknown Type ({malware_family})")
    malware_sha256 = event.deep_get(
        "parameters", "message_info", "attachment", "sha256", default="<UNKNOWN_SHA256>"
    )

    return (
        f"Malicious attachment of type [{malware_type}] "
        f"with SHA256 [{malware_sha256}] "
        f"detected in email to [{user}]"
    )


def alert_context(event):
    malware_family = event.deep_get(
        "parameters", "message_info", "attachment", "malware_family", default=0
    )
    malware_sha256 = event.deep_get(
        "parameters", "message_info", "attachment", "sha256", default="<UNKNOWN_SHA256>"
    )
    filename = event.deep_get(
        "parameters", "message_info", "attachment", "file_name", default="<UNKNOWN_FILENAME>"
    )
    context = {
        "recipient": event.deep_get("actor", "email", default="<UNKNOWN_USER>"),
        "malware_family_code": malware_family,
        "malware_type": MALWARE_FAMILY_TYPES.get(malware_family, "Unknown"),
        "source_ip": event.get("ipAddress"),
        "sha256": malware_sha256,
        "filename": filename,
    }

    return context

Rule specification

AnalysisType: rule
Filename: gsuite_malware_in_email.py
RuleID: "GSuite.Gmail.Malware.In.Email"
DisplayName: "Malware Detected in Email"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
  - Gmail
  - Malware
  - Initial Access
Reports:
  MITRE ATT&CK:
    - TA0001:T1566.001 # Initial Access: Phishing - Spearphishing Attachment
    - TA0011:T1204.002 # Execution: User Execution - Malicious File
Severity: High
Description: >
  Detects when malware is found in an email received by a user. Identifies different malware families including known malicious programs, viruses, worms, harmful content, and unwanted content. Severity is dynamically assigned based on the malware type, with known malicious programs and viruses triggering high-severity alerts.
Runbook: |
  1. Review the malware type and affected user
  2. Check if the email was quarantined or delivered
  3. Verify if the user interacted with the email or opened attachments
  4. Check for similar emails to other users in the organization
  5. Consider blocking the sender domain if appropriate
  6. Notify the affected user and provide security awareness guidance
Reference: https://support.google.com/a/answer/12384955
DedupPeriodMinutes: 60

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is gmail
  • parameters.message_info.attachment.malware_family is present

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
recipientactor.email
malware_family_codeparameters.message_info.attachment.malware_family
source_ipipAddress
sha256parameters.message_info.attachment.sha256
filenameparameters.message_info.attachment.file_name

Response runbook

1. Review the malware type and affected user

2. Check if the email was quarantined or delivered

3. Verify if the user interacted with the email or opened attachments

4. Check for similar emails to other users in the organization

5. Consider blocking the sender domain if appropriate

6. Notify the affected user and provide security awareness guidance

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "oliver@justice.org",
    "profileId": "123456789"
  },
  "id": {
    "applicationName": "gmail",
    "customerId": "C12345",
    "time": "2025-11-04 20:44:43.248000000",
    "uniqueQualifier": "-123456789"
  },
  "ipAddress": "1.1.1.1",
  "kind": "admin#reports#activity",
  "name": "delivery",
  "p_any_actor_ids": [
    "1234567891234"
  ],
  "p_any_domain_names": [
    "malicious-sender.com"
  ],
  "p_any_ip_addresses": [
    "1.1.1.1"
  ],
  "p_event_time": "2025-11-04 20:44:43.248000000",
  "p_log_type": "GSuite.ActivityEvent",
  "p_parse_time": "2025-11-04 20:49:46.688935963",
  "p_row_id": "0000000000de09c1dc6f0828cbad2ca5",
  "p_schema_version": 0,
  "p_source_id": "7ee69d4d-df1b-40b3-b5e8-6826dee34b1c",
  "p_source_label": "Google Workspace",
  "parameters": {
    "event_info": {
      "elapsed_time_usec": 368746,
      "timestamp_usec": 1730751883248347
    },
    "message_info": {
      "action_type": 19,
      "attachment": {
        "file_name": "invoice.exe",
        "malware_family": 1,
        "sha256": "000000000045c5798d026b67c03d54273fd0996f5cb789d0a959dac0c7cc456c"
      },
      "link_domain": [
        "malicious-sender.com"
      ],
      "num_message_attachments": 1,
      "payload_size": 54321,
      "subject": "Important Invoice Attached"
    }
  },
  "type": "delivery_type"
}

Spam Email Surge

#
Status
Experimental
Severity
medium
Entities
actor_ids, domain_names, ip_addresses
Log types
GSuite.ActivityEvent
Tags
GSuite
Source
github.com/panther-labs/panther-analysis

Detects a high number of spam emails received by a single user in a short timeframe. This could indicate the user's email has appeared in data leaks and is being targeted for spam.

MITRE ATT&CK coverage

TacticTechniques
Reconnaissance
Initial Access

Telemetry coverage

PlatformRecord / event type
Google Workspaceany: Gmail (any event)

Rules detecting the same action

These rules filter on the same operation.

Detection logic

from panther_gsuite_helpers import gsuite_activityevent_alert_context


def rule(event):
    if event.deep_get("id", "applicationName", default="<UNKNOWN_APPLICATION>") != "gmail":
        return False
    # Exclude domain-level actor
    if "/hd/domain/" in event.deep_get("actor", "email"):
        return False
    return event.deep_get("parameters", "message_info", "is_spam", default=False) is True


def title(event):
    user = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    return f"Surge in spam emails received by user [{user}]"


def alert_context(event):
    return gsuite_activityevent_alert_context(event)

Rule specification

AnalysisType: rule
Filename: gsuite_spam_email.py
RuleID: "GSuite.Gmail.Spam.Email.Surge"
DisplayName: "Spam Email Surge"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Reports:
  MITRE ATT&CK:
    - TA0001:T1566 # Initial Access: Phishing
    - TA0043:T1598 # Reconnaissance: Phishing for Information
Severity: Medium
Status: Experimental
Description: >
  Detects a high number of spam emails received by a single user in a short timeframe. This could indicate the user's email has appeared in data leaks and is being targeted for spam.
Threshold: 50
DedupPeriodMinutes: 60

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is gmail
  • actor.email does not contain /hd/domain/
  • parameters.message_info.is_spam is true
Alert cadence
alerts after 50 matches within 1h

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
actor.emailcontains/hd/domain/excludes:actor.email field:"actor.email" value:"/hd/domain/"

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
actoractor.email
applicationNameid.applicationName
name
type
parameters

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "callerType": "USER",
    "email": "denethor@lotr.com",
    "profileId": "123456789"
  },
  "id": {
    "applicationName": "gmail",
    "customerId": "1A2B3C",
    "time": "2025-11-04 20:44:43.248000000",
    "uniqueQualifier": "-123456789"
  },
  "ipAddress": "1.1.1.1",
  "kind": "admin#reports#activity",
  "name": "delivery",
  "p_any_actor_ids": [
    "1234567891234"
  ],
  "p_any_domain_names": [
    "evil.com"
  ],
  "p_any_ip_addresses": [
    "1.1.1.1"
  ],
  "p_event_time": "2025-11-04 20:44:43.248000000",
  "p_log_type": "GSuite.ActivityEvent",
  "p_parse_time": "2025-11-04 20:49:46.688935963",
  "p_row_id": "0000000000de09c1dc6f0828cbad2ca5",
  "p_schema_version": 0,
  "p_source_id": "7ee69d4d-df1b-40b3-b5e8-6826dee34b1c",
  "p_source_label": "Google Workspace",
  "p_udm": {
    "source": {
      "address": "1.1.1.1",
      "ip": "1.1.1.1"
    },
    "user": {
      "provider_id": "123456789"
    }
  },
  "parameters": {
    "event_info": {
      "elapsed_time_usec": 368746,
      "timestamp_usec": 1762289083248347
    },
    "message_info": {
      "action_type": 19,
      "flattened_destinations": "gmail-for-work-catchall::denethor@lotr.com",
      "is_spam": true,
      "link_domain": [
        "evil.com"
      ],
      "payload_size": 12345,
      "subject": "You won 1 Million Dollar"
    }
  },
  "type": "delivery_type"
}

Suspicious GSuite Login

#
Severity
medium
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

GSuite reported a suspicious login for this user.

Telemetry coverage

Detection logic

SUSPICIOUS_LOGIN_TYPES = {
    "suspicious_login",
    "suspicious_login_less_secure_app",
    "suspicious_programmatic_login",
}


def rule(event):
    if event.deep_get("id", "applicationName") != "login":
        return False

    if event.get("name") in SUSPICIOUS_LOGIN_TYPES:
        return True

    return False


def title(event):
    user = event.deep_get("actor", "email") or event.deep_get(
        "parameters", "affected_email_address", default="<UNKNOWN_USER>"
    )
    return f"A suspicious login was reported for user [{user}]"

Rule specification

AnalysisType: rule
Filename: gsuite_suspicious_logins.py
RuleID: "GSuite.SuspiciousLogins"
DisplayName: "Suspicious GSuite Login"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Severity: Medium
Description: >
  GSuite reported a suspicious login for this user.
Reference: https://support.google.com/a/answer/7102416?hl=en
Runbook: >
  Checkout the details of the login and verify this behavior with the user to ensure the account wasn't compromised.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when all of the conditions below hold.

Condition

  • id.applicationName is login
  • name is one of suspicious_login, suspicious_login_less_secure_app, suspicious_programmatic_login

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
id.applicationNameeq
  • login
field:"id.applicationName" kind:eq value:"login"
namein
  • suspicious_login
  • suspicious_login_less_secure_app
  • suspicious_programmatic_login
field:"name" kind:in

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

Checkout the details of the login and verify this behavior with the user to ensure the account wasn't compromised.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "id": {
    "applicationName": "login"
  },
  "kind": "admin#reports#activity",
  "name": "suspicious_login",
  "parameters": {
    "affected_email_address": "bobert@ext.runpanther.io"
  },
  "type": "account_warning"
}

Suspicious is_suspicious tag

#
Status
Experimental
Severity
informational
Log types
GSuite.ActivityEvent
Tags
GSuite
Reference
support.google.com
Source
github.com/panther-labs/panther-analysis

GSuite reported a suspicious activity for this user.

Detection logic

def rule(event):
    return event.deep_get("parameters", "is_suspicious") is True


def title(event):
    user = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    return f"A suspicious action was reported for user [{user}]"

Rule specification

AnalysisType: rule
Filename: gsuite_is_suspicious_tag.py
RuleID: "GSuite.IsSuspiciousTag"
DisplayName: "Suspicious is_suspicious tag"
Enabled: true
LogTypes:
  - GSuite.ActivityEvent
Tags:
  - GSuite
Status: Experimental
Severity: Info # Will be Medium in the future
Description: >
  GSuite reported a suspicious activity for this user.
Reference: https://support.google.com/a/answer/7102416?hl=en
Runbook: >
  Checkout the details of the activity and verify this behavior with the user to ensure the account wasn't compromised.
SummaryAttributes:
  - actor:email

Stages and Predicates

Fires on GSuite.ActivityEvent events when the condition below holds.

Condition

  • parameters.is_suspicious is true

Indicators

These rows show field, operator, and value matches.

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

FieldSource
emailactor.email

Response runbook

Checkout the details of the activity and verify this behavior with the user to ensure the account wasn't compromised.

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "email": "bobert@ext.runpanther.io"
  },
  "id": {
    "applicationName": "login"
  },
  "kind": "admin#reports#activity",
  "name": "login_success",
  "parameters": {
    "affected_email_address": "bobert@ext.runpanther.io",
    "is_suspicious": true
  },
  "type": "login"
}