Detection rules › Panther

Panther rules: openai

OpenAI Admin Role Assignment

#
Severity
high
Log types
OpenAI.Audit
Reference
platform.openai.com
Source
github.com/panther-labs/panther-analysis

Detects when an admin or owner role is assigned to a user or group in OpenAI. Admin and owner roles grant elevated privileges that allow significant control over the organization, including managing users, API keys, billing, and security settings. Unauthorized or unexpected admin role assignments can indicate: - Privilege escalation attempts - Insider threats - Compromised administrator accounts - Policy violations This rule alerts on all admin role assignments for visibility and audit purposes.

MITRE ATT&CK coverage

Detection logic

ADMIN_KEYWORDS = {"owner", "admin"}


def rule(event):
    if event.get("type") != "role.assignment.created":
        return False

    assignment_id = event.deep_get("role_assignment_created", "id", default="")
    if not assignment_id:
        return False

    # Parse: role_assignment_role-api-organization-owner__..._user-123
    # Extract: api-organization-owner
    parts = assignment_id.split("_")
    if len(parts) > 2 and parts[0] == "role" and parts[1] == "assignment":
        role_part = parts[2]
        if role_part.startswith("role-"):
            role_name = role_part[5:].lower()
            return any(keyword in role_name for keyword in ADMIN_KEYWORDS)

    return False


def title(event):
    principal_id = event.deep_get("role_assignment_created", "principal_id", default="Unknown")
    email = event.deep_get("actor", "session", "user", "email", default="<UNKNOWN_USER>")
    return f"OpenAI Admin Role Assigned to {principal_id} by [{email}]"


def severity(event):
    assignment_id = event.deep_get("role_assignment_created", "id", default="")
    resource_type = event.deep_get("role_assignment_created", "resource_type", default="")

    if not assignment_id:
        return "DEFAULT"

    assignment_lower = assignment_id.lower()

    if "organization" in resource_type.lower() and "owner" in assignment_lower:
        return "CRITICAL"
    if "admin" in assignment_lower:
        return "HIGH"

    return "DEFAULT"


def alert_context(event):
    return {
        "event_type": event.get("type", "<UNKNOWN_EVENT_TYPE>"),
        "event_id": event.get("id", "<UNKNOWN_EVENT_ID>"),
        "assignment_id": event.deep_get(
            "role_assignment_created", "id", default="<UNKNOWN_ASSIGNMENT_ID>"
        ),
        "principal_id": event.deep_get(
            "role_assignment_created", "principal_id", default="<UNKNOWN_PRINCIPAL_ID>"
        ),
        "principal_type": event.deep_get(
            "role_assignment_created", "principal_type", default="<UNKNOWN_PRINCIPAL_TYPE>"
        ),
        "resource_id": event.deep_get(
            "role_assignment_created", "resource_id", default="<UNKNOWN_RESOURCE_ID>"
        ),
        "resource_type": event.deep_get(
            "role_assignment_created", "resource_type", default="<UNKNOWN_RESOURCE_TYPE>"
        ),
        "actor_email": event.deep_get(
            "actor", "session", "user", "email", default="<UNKNOWN_ACTOR_EMAIL>"
        ),
        "actor_id": event.deep_get("actor", "session", "user", "id", default="<UNKNOWN_ACTOR_ID>"),
        "source_ip": event.deep_get(
            "actor", "session", "ip_address", default="<UNKNOWN_SOURCE_IP>"
        ),
        "user_agent": event.deep_get(
            "actor", "session", "user_agent", default="<UNKNOWN_USER_AGENT>"
        ),
    }

Rule specification

AnalysisType: rule
Description: |
  Detects when an admin or owner role is assigned to a user or group in OpenAI.
  Admin and owner roles grant elevated privileges that allow significant control over
  the organization, including managing users, API keys, billing, and security settings.
  Unauthorized or unexpected admin role assignments can indicate:
  - Privilege escalation attempts
  - Insider threats
  - Compromised administrator accounts
  - Policy violations
  This rule alerts on all admin role assignments for visibility and audit purposes.
DisplayName: "OpenAI Admin Role Assignment"
Enabled: true
Filename: openai_admin_role_assignment.py
Reference: https://platform.openai.com/docs/api-reference/audit-logs
Runbook: |
  1. Verify the role assignment was authorized. Confirm the assigned user/group should have admin privileges and the assignment aligns with approved access requests or organizational policy.
  2. Review the assignment context including the actor performing the assignment, source IP address, geolocation, and timing. Check for anomalous patterns such as assignments from unusual locations or during off-hours.
  3. If the assignment is suspicious or unauthorized, immediately revoke the admin role. Review all actions performed by the principal since the role was granted, investigate potential account compromise of the actor who granted the role, and escalate for security review.
Reports:
  MITRE ATT&CK:
    - TA0003:T1098  # Account Manipulation
    - TA0004:T1078  # Valid Accounts
    - TA0005:T1098.003  # Additional Cloud Roles
Severity: High
DedupPeriodMinutes: 60
Threshold: 1
LogTypes:
  - OpenAI.Audit
RuleID: "OpenAI.Admin.Role.Assignment"

Stages and Predicates

Fires on OpenAI.Audit events when all of the conditions below hold.

Condition

  • type is role.assignment.created
  • role_assignment_created.id is present

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
event_typetype
event_idid
assignment_idrole_assignment_created.id
principal_idrole_assignment_created.principal_id
principal_typerole_assignment_created.principal_type
resource_idrole_assignment_created.resource_id
resource_typerole_assignment_created.resource_type
actor_emailactor.session.user.email
actor_idactor.session.user.id
source_ipactor.session.ip_address
user_agentactor.session.user_agent

Response runbook

1. Verify the role assignment was authorized. Confirm the assigned user/group should have admin privileges and the assignment aligns with approved access requests or organizational policy.

2. Review the assignment context including the actor performing the assignment, source IP address, geolocation, and timing. Check for anomalous patterns such as assignments from unusual locations or during off-hours.

3. If the assignment is suspicious or unauthorized, immediately revoke the admin role. Review all actions performed by the principal since the role was granted, investigate potential account compromise of the actor who granted the role, and escalate for security review.

Worked example

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

Sample Test Event
{
  "actor": {
    "session": {
      "ip_address": "203.0.113.100",
      "ip_address_details": {
        "city": "San Francisco",
        "country": "US",
        "region": "California"
      },
      "user": {
        "email": "admin@company.com",
        "id": "user-admin123"
      },
      "user_agent": "Mozilla/5.0"
    },
    "type": "session"
  },
  "effective_at": 1702857600,
  "id": "audit_log-test001",
  "object": "organization.audit_log",
  "role_assignment_created": {
    "id": "role_assignment_role-api-organization-owner__api-organization__org-abc123_user-newadmin456",
    "principal_id": "user-newadmin456",
    "principal_type": "user",
    "resource_id": "org-abc123",
    "resource_type": "api.organization"
  },
  "type": "role.assignment.created"
}

OpenAI Anomalous API Key Activity

#
Severity
medium
Log types
OpenAI.Audit
Reference
platform.openai.com
Source
github.com/panther-labs/panther-analysis

Detects anomalous OpenAI API key activity indicative of potential key compromise, unauthorized access, or preparation for malicious misuse (e.g., C2, phishing, automation). OpenAI API keys provide programmatic access to powerful LLM capabilities. Abuse or compromise of these keys enables attackers to blend malicious activity into legitimate cloud traffic, bypassing traditional network-based detections. This rule alerts on: - API keys created or updated with elevated or unrestricted permissions (all, models:write, organization:write, api_keys:write, admin)

MITRE ATT&CK coverage

Detection logic

ELEVATED_SCOPES = {"all", "models:write", "organization:write", "api_keys:write", "admin"}


def rule(event):
    if event.get("type") not in ["api_key.created", "api_key.updated"]:
        return False

    field = "api_key_created" if event.get("type") == "api_key.created" else "api_key_updated"
    scopes = event.deep_get(field, "data", "scopes", default=[])

    return any(scope.lower() in {s.lower() for s in ELEVATED_SCOPES} for scope in scopes if scope)


def title(event):
    event_type = event.get("type", "")
    email = event.deep_get("actor", "session", "user", "email", default="<UNKNOWN_USER>")
    return f"OpenAI API Key with Elevated Scopes: {event_type} by [{email}]"


def severity(event):
    field = "api_key_created" if event.get("type") == "api_key.created" else "api_key_updated"
    scopes = event.deep_get(field, "data", "scopes", default=[])

    if not scopes:
        return "DEFAULT"

    if any(s.lower() in ["all", "admin"] for s in scopes):
        return "HIGH"
    if any(s.lower() in {s.lower() for s in ELEVATED_SCOPES} for s in scopes):
        return "MEDIUM"

    return "DEFAULT"


def alert_context(event):
    field = "api_key_created" if event.get("type") == "api_key.created" else "api_key_updated"
    return {
        "event_type": event.get("type", "<UNKNOWN_EVENT_TYPE>"),
        "event_id": event.get("id", "<UNKNOWN_EVENT_ID>"),
        "api_key_id": event.deep_get(field, "id", default="<UNKNOWN_API_KEY_ID>"),
        "api_key_scopes": event.deep_get(field, "data", "scopes", default=[]),
        "actor_email": event.deep_get(
            "actor", "session", "user", "email", default="<UNKNOWN_ACTOR_EMAIL>"
        ),
        "actor_id": event.deep_get("actor", "session", "user", "id", default="<UNKNOWN_ACTOR_ID>"),
        "source_ip": event.deep_get(
            "actor", "session", "ip_address", default="<UNKNOWN_SOURCE_IP>"
        ),
        "user_agent": event.deep_get(
            "actor", "session", "user_agent", default="<UNKNOWN_USER_AGENT>"
        ),
    }

Rule specification

AnalysisType: rule
Description: |
  Detects anomalous OpenAI API key activity indicative of potential key compromise,
  unauthorized access, or preparation for malicious misuse (e.g., C2, phishing, automation).
  OpenAI API keys provide programmatic access to powerful LLM capabilities. Abuse or
  compromise of these keys enables attackers to blend malicious activity into legitimate
  cloud traffic, bypassing traditional network-based detections.
  This rule alerts on:
  - API keys created or updated with elevated or unrestricted permissions (all, models:write, organization:write, api_keys:write, admin)
DisplayName: "OpenAI Anomalous API Key Activity"
Enabled: true
Filename: openai_api_key_anomalous_activity.py
Reference: https://platform.openai.com/docs/api-reference/audit-logs
Runbook: |
  1. Validate the identity responsible for the API key activity. Confirm whether the user or service account is authorized to manage API keys and if the action aligns with an approved business process (e.g., onboarding, rotation).
  2. Review the key's scopes/permissions for elevated or unrestricted access. Check recent API usage, source IP address, geolocation, and TLS fingerprints for unusual client patterns or concurrent suspicious activities.
  3. If suspicious activity is confirmed, immediately revoke or rotate the affected API key. Review all API keys created by the same identity, investigate potential account compromise, and escalate for further investigation.
Reports:
  MITRE ATT&CK:
    - TA0001:T1078  # Valid Accounts
    - TA0003:T1098  # Account Manipulation
    - TA0005:T1550  # Use Alternate Authentication Material
    - TA0011:T1071  # Application Layer Protocol (for C2)
Severity: Medium
DedupPeriodMinutes: 60
Threshold: 1
LogTypes:
  - OpenAI.Audit
RuleID: "OpenAI.API.Key.Anomalous.Activity"

Stages and Predicates

Fires on OpenAI.Audit events when the condition below holds.

Condition

  • type is one of api_key.created, api_key.updated

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.

FieldKindValuesSearch
typein
  • api_key.created
  • api_key.updated
field:"type" kind:in

Output fields

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

FieldSource
event_typetype
event_idid
actor_emailactor.session.user.email
actor_idactor.session.user.id
source_ipactor.session.ip_address
user_agentactor.session.user_agent

Response runbook

1. Validate the identity responsible for the API key activity. Confirm whether the user or service account is authorized to manage API keys and if the action aligns with an approved business process (e.g., onboarding, rotation).

2. Review the key's scopes/permissions for elevated or unrestricted access. Check recent API usage, source IP address, geolocation, and TLS fingerprints for unusual client patterns or concurrent suspicious activities.

3. If suspicious activity is confirmed, immediately revoke or rotate the affected API key. Review all API keys created by the same identity, investigate potential account compromise, and escalate for further investigation.

Worked example

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

Sample Test Event
{
  "actor": {
    "session": {
      "ip_address": "192.0.2.100",
      "ip_address_details": {
        "city": "Bucharest",
        "country": "RO"
      },
      "user": {
        "email": "contractor@external.com",
        "id": "user-contractor789"
      },
      "user_agent": "curl/7.68.0"
    },
    "type": "session"
  },
  "api_key_created": {
    "data": {
      "name": "Full Access Key",
      "scopes": [
        "models:write",
        "organization:write",
        "all"
      ]
    },
    "id": "key-suspicious001"
  },
  "effective_at": 1702857600,
  "id": "audit_log-test002",
  "object": "organization.audit_log",
  "type": "api_key.created"
}

OpenAI Brute Force Login Success

#
Severity
high
Time window
30h
Match by
p_any_emails
Reference
platform.openai.com
Source
github.com/panther-labs/panther-analysis

Detects successful credential stuffing or brute force attacks against OpenAI accounts. This rule identifies when a user account experiences 5 or more failed login attempts followed by a successful login within 30 minutes. This pattern indicates: - Successful credential stuffing attack - Successful brute force attack - Compromised user credentials - Automated attack tools successfully gaining access The correlation is performed by matching on the user email address to track attempts against the same account across multiple failed attempts and the eventual success.

MITRE ATT&CK coverage

Rule specification

AnalysisType: correlation_rule
RuleID: "OpenAI.BruteForce.Login.Success.Group"
DisplayName: "OpenAI Brute Force Login Success"
Enabled: false
Severity: High
Description: |
  Detects successful credential stuffing or brute force attacks against OpenAI accounts.
  This rule identifies when a user account experiences 5 or more failed login attempts
  followed by a successful login within 30 minutes. This pattern indicates:
  - Successful credential stuffing attack
  - Successful brute force attack
  - Compromised user credentials
  - Automated attack tools successfully gaining access
  The correlation is performed by matching on the user email address to track attempts
  against the same account across multiple failed attempts and the eventual success.
Reference: https://platform.openai.com/docs/api-reference/audit-logs
Runbook: |
  1. Verify if the successful login was legitimate by contacting the user via a trusted out-of-band channel to confirm they logged in. Review the failed login attempts including source IPs, geolocations, user agents, and timing patterns for anomalies.
  2. If the login is confirmed as unauthorized, immediately disable the compromised account, force password reset, revoke all active sessions and API keys, and review all actions taken by the account since the successful login.
  3. Investigate potential breach scope by checking if the same IP targeted multiple accounts, reviewing API key modifications, auditing role assignments and permission changes, and checking for data exfiltration or suspicious API usage.
Reports:
  MITRE ATT&CK:
    - TA0001:T1110  # Brute Force
    - TA0006:T1110.001  # Password Guessing
    - TA0006:T1110.003  # Password Spraying
    - TA0006:T1110.004  # Credential Stuffing
Detection:
  - Group:
      - ID: Multiple Failed Logins
        RuleID: OpenAI.Login.Failed
        MinMatchCount: 5
      - ID: Successful Login
        RuleID: OpenAI.Login.Success
    MatchCriteria:
      field_name:
        - GroupID: Multiple Failed Logins
          Match: p_any_emails
        - GroupID: Successful Login
          Match: p_any_emails
    Schedule:
      RateMinutes: 1440
      TimeoutMinutes: 5
    LookbackWindowMinutes: 1800

Stages and Predicates

Fires when the steps below all occur within 30h, correlated by p_any_emails. Each step needs one match unless a higher minimum is shown.

Stage 1: step Multiple Failed Logins

References detection OpenAI Failed Login (Base Rule) (min 5 matches).

Stage 2: step Successful Login

References detection OpenAI Successful Login (Base Rule).

Response runbook

1. Verify if the successful login was legitimate by contacting the user via a trusted out-of-band channel to confirm they logged in. Review the failed login attempts including source IPs, geolocations, user agents, and timing patterns for anomalies.

2. If the login is confirmed as unauthorized, immediately disable the compromised account, force password reset, revoke all active sessions and API keys, and review all actions taken by the account since the successful login.

3. Investigate potential breach scope by checking if the same IP targeted multiple accounts, reviewing API key modifications, auditing role assignments and permission changes, and checking for data exfiltration or suspicious API usage.

OpenAI Credential Stuffing

#
Severity
medium
Group by
actor.session.user.email
Log types
OpenAI.Audit
Tags
OpenAI, Credential Access
Reference
platform.openai.com
Source
github.com/panther-labs/panther-analysis

Detects credential stuffing attacks against OpenAI accounts by tracking the number of distinct source IP addresses submitting failed login attempts against the same email address within a short timeframe. Unlike brute force from a single IP, credential stuffing distributes attempts across many IPs to evade rate limiting. This rule complements OpenAI.BruteForce.Login.Success.Group, which confirms account compromise once a successful login follows the failures.

MITRE ATT&CK coverage

TacticTechniques
Credential Access

Detection logic

def rule(event):
    return event.get("type") == "login.failed"


def unique(event):
    return event.deep_get("actor", "session", "ip_address", default="UNKNOWN_IP")


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


def title(event):
    email = event.deep_get("actor", "session", "user", "email", default="UNKNOWN_EMAIL")
    return f"[OpenAI] Credential stuffing detected against account [{email}]"


def alert_context(event):
    return {
        "email": event.deep_get("actor", "session", "user", "email", default="UNKNOWN_EMAIL"),
        "ip_address": event.deep_get("actor", "session", "ip_address", default="UNKNOWN_IP"),
        "error_code": event.deep_get("login_failed", "error_code", default="UNKNOWN"),
    }

Rule specification

AnalysisType: rule
RuleID: "OpenAI.CredentialStuffing"
DisplayName: "OpenAI Credential Stuffing"
Filename: openai_credential_stuffing.py
Enabled: true
LogTypes:
  - OpenAI.Audit
Severity: Medium
Threshold: 5
DedupPeriodMinutes: 30
Description: >
  Detects credential stuffing attacks against OpenAI accounts by tracking the number of
  distinct source IP addresses submitting failed login attempts against the same email
  address within a short timeframe. Unlike brute force from a single IP, credential
  stuffing distributes attempts across many IPs to evade rate limiting. This rule
  complements OpenAI.BruteForce.Login.Success.Group, which confirms account compromise
  once a successful login follows the failures.
Runbook: |
  1. Review the distinct source IPs from the failed login attempts and check if they belong to known anonymization infrastructure (VPNs, Tor exit nodes, residential proxies) using threat intelligence
  2. Query OpenAI audit logs for all login.succeeded and login.failed events against the targeted email address within the past 30 minutes to identify all contributing source IPs and determine if any attempt succeeded — the alert context shows only the IP of the final triggering event, not all contributing IPs
  3. Check for other alerts involving the targeted email address in the past 7 days and look for login.succeeded events from unfamiliar locations or devices to assess whether account compromise has already occurred
Reference: https://platform.openai.com/docs/api-reference/audit-logs
Tags:
  - OpenAI
  - Credential Access
Reports:
  MITRE ATT&CK:
    - TA0006:T1110.004 # Credential Access: Credential Stuffing

Stages and Predicates

Fires on OpenAI.Audit events when the condition below holds.

Condition

  • type is login.failed
Alert cadence
alerts after 5 matches within 30m

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
typeeq
  • login.failed
field:"type" kind:eq value:"login.failed"

Output fields

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

FieldSource
emailactor.session.user.email
ip_addressactor.session.ip_address
error_codelogin_failed.error_code

Response runbook

1. Review the distinct source IPs from the failed login attempts and check if they belong to known anonymization infrastructure (VPNs, Tor exit nodes, residential proxies) using threat intelligence

2. Query OpenAI audit logs for all login.succeeded and login.failed events against the targeted email address within the past 30 minutes to identify all contributing source IPs and determine if any attempt succeeded — the alert context shows only the IP of the final triggering event, not all contributing IPs

3. Check for other alerts involving the targeted email address in the past 7 days and look for login.succeeded events from unfamiliar locations or devices to assess whether account compromise has already occurred

Worked example

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

Sample Test Event
{
  "actor": {
    "session": {
      "ip_address": "203.0.113.100",
      "user": {
        "email": "user@company.com",
        "id": "user-123"
      },
      "user_agent": "Mozilla/5.0"
    },
    "type": "session"
  },
  "effective_at": 1702857600,
  "id": "audit_log-failed001",
  "login_failed": {
    "error_code": "invalid_credentials",
    "error_message": "Invalid username or password"
  },
  "object": "organization.audit_log",
  "type": "login.failed"
}

OpenAI Failed Login (Base Rule)

#
Severity
informational
Log types
OpenAI.Audit
Source
github.com/panther-labs/panther-analysis

Base rule for detecting OpenAI failed login attempts. This rule is used primarily as a building block for correlation rules and does not generate alerts on its own.

Detection logic

def rule(event):
    return event.get("type") == "login.failed"

Rule specification

AnalysisType: rule
Description: |
  Base rule for detecting OpenAI failed login attempts. This rule is used primarily
  as a building block for correlation rules and does not generate alerts on its own.
DisplayName: "OpenAI Failed Login (Base Rule)"
Enabled: true
Filename: openai_login_failed.py
CreateAlert: false
Severity: Info
DedupPeriodMinutes: 60
Threshold: 999999
LogTypes:
  - OpenAI.Audit
RuleID: "OpenAI.Login.Failed"

Stages and Predicates

Fires on OpenAI.Audit events when the condition below holds.

Condition

  • type is login.failed
Alert cadence
alerts after 999999 matches within 1h

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
typeeq
  • login.failed
field:"type" kind:eq value:"login.failed"

Worked example

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

Sample Test Event
{
  "actor": {
    "session": {
      "ip_address": "203.0.113.100",
      "user": {
        "email": "user@company.com",
        "id": "user-123"
      },
      "user_agent": "Mozilla/5.0"
    },
    "type": "session"
  },
  "effective_at": 1702857600,
  "id": "audit_log-failed001",
  "login_failed": {
    "error_code": "invalid_credentials",
    "error_message": "Invalid username or password"
  },
  "object": "organization.audit_log",
  "type": "login.failed"
}

OpenAI IP Allowlist Configuration Changes

#
Severity
medium
Log types
OpenAI.Audit
Reference
platform.openai.com
Source
github.com/panther-labs/panther-analysis

Detects changes to OpenAI IP allowlist configurations including creation, updates, deletion, activation, and deactivation. IP allowlists restrict API and console access to specific IP addresses or CIDR ranges, providing network-level access control. Changes to IP allowlists can indicate: - Security control removal (deletion/deactivation) - CRITICAL - Addition of dangerous IPs like 0.0.0.0 (updates) - HIGH - Configuration changes for visibility (creation/activation) - MEDIUM Unauthorized modifications can expose the organization to unauthorized access, bypass network security controls, or indicate preparation for malicious activity.

MITRE ATT&CK coverage

Detection logic

IP_ALLOWLIST_EVENTS = [
    "ip_allowlist.created",
    "ip_allowlist.updated",
    "ip_allowlist.deleted",
    "ip_allowlist.config.activated",
    "ip_allowlist.config.deactivated",
]


def rule(event):
    return event.get("type") in IP_ALLOWLIST_EVENTS


def title(event):
    event_type = event.get("type", "")
    email = event.deep_get("actor", "session", "user", "email", default="<UNKNOWN_USER>")

    action_map = {
        "ip_allowlist.created": "Created",
        "ip_allowlist.updated": "Updated",
        "ip_allowlist.deleted": "Deleted",
        "ip_allowlist.config.activated": "Activated",
        "ip_allowlist.config.deactivated": "Deactivated",
    }

    action = action_map.get(event_type, "Modified")
    return f"OpenAI IP Allowlist {action} by [{email}]"


def severity(event):
    event_type = event.get("type")

    if event_type in ["ip_allowlist.deleted", "ip_allowlist.config.deactivated"]:
        return "CRITICAL"

    if event_type == "ip_allowlist.updated":
        return "HIGH"

    if event_type in ["ip_allowlist.created", "ip_allowlist.config.activated"]:
        return "LOW"

    return "DEFAULT"


def alert_context(event):
    event_type = event.get("type")

    context = {
        "event_type": event_type if event_type else "<UNKNOWN_EVENT_TYPE>",
        "event_id": event.get("id", "<UNKNOWN_EVENT_ID>"),
        "actor_email": event.deep_get(
            "actor", "session", "user", "email", default="<UNKNOWN_ACTOR_EMAIL>"
        ),
        "actor_id": event.deep_get("actor", "session", "user", "id", default="<UNKNOWN_ACTOR_ID>"),
        "source_ip": event.deep_get(
            "actor", "session", "ip_address", default="<UNKNOWN_SOURCE_IP>"
        ),
        "user_agent": event.deep_get(
            "actor", "session", "user_agent", default="<UNKNOWN_USER_AGENT>"
        ),
        "ip_details": event.deep_get("actor", "session", "ip_address_details", default={}),
    }

    # Add event-specific fields
    if event_type == "ip_allowlist.created":
        context["allowlist_id"] = event.deep_get(
            "ip_allowlist_created", "id", default="<UNKNOWN_ALLOWLIST_ID>"
        )
        context["allowlist_name"] = event.deep_get(
            "ip_allowlist_created", "name", default="<UNKNOWN_ALLOWLIST_NAME>"
        )
        context["allowed_ips"] = event.deep_get("ip_allowlist_created", "allowed_ips", default=[])
    elif event_type == "ip_allowlist.updated":
        context["allowlist_id"] = event.deep_get(
            "ip_allowlist_updated", "id", default="<UNKNOWN_ALLOWLIST_ID>"
        )
        context["allowed_ips"] = event.deep_get("ip_allowlist_updated", "allowed_ips", default=[])
    elif event_type == "ip_allowlist.deleted":
        context["allowlist_id"] = event.deep_get(
            "ip_allowlist_deleted", "id", default="<UNKNOWN_ALLOWLIST_ID>"
        )
        context["allowlist_name"] = event.deep_get(
            "ip_allowlist_deleted", "name", default="<UNKNOWN_ALLOWLIST_NAME>"
        )
        context["allowed_ips"] = event.deep_get("ip_allowlist_deleted", "allowed_ips", default=[])
    elif event_type == "ip_allowlist.config.activated":
        context["activated_configs"] = event.deep_get(
            "ip_allowlist_config_activated", "configs", default=[]
        )
    elif event_type == "ip_allowlist.config.deactivated":
        context["deactivated_configs"] = event.deep_get(
            "ip_allowlist_config_deactivated", "configs", default=[]
        )

    return context

Rule specification

AnalysisType: rule
Description: |
  Detects changes to OpenAI IP allowlist configurations including creation, updates,
  deletion, activation, and deactivation.
  IP allowlists restrict API and console access to specific IP addresses or CIDR ranges,
  providing network-level access control. Changes to IP allowlists can indicate:
  - Security control removal (deletion/deactivation) - CRITICAL
  - Addition of dangerous IPs like 0.0.0.0 (updates) - HIGH
  - Configuration changes for visibility (creation/activation) - MEDIUM
  Unauthorized modifications can expose the organization to unauthorized access,
  bypass network security controls, or indicate preparation for malicious activity.
DisplayName: "OpenAI IP Allowlist Configuration Changes"
Enabled: true
Filename: openai_ip_allowlist_changes.py
Reference: https://platform.openai.com/docs/api-reference/audit-logs
Runbook: |
  1. Verify the IP allowlist change was authorized and aligns with approved network security policies. Review the specific IPs added, removed, or modified, and confirm they match expected organizational infrastructure.
  2. For deletions or deactivations, immediately assess if the organization is now exposed to unauthorized access. For updates, check if dangerous IPs like 0.0.0.0 (allow all), unknown ranges, or suspicious geolocations were added.
  3. If the change is unauthorized or suspicious, immediately revert the configuration, review all access from non-allowlisted IPs since the change, audit role assignments and API key usage, and escalate for security review.
Reports:
  MITRE ATT&CK:
    - TA0005:T1562.001  # Impair Defenses: Disable or Modify Tools
    - TA0005:T1562.007  # Impair Defenses: Disable or Modify Cloud Firewall
Severity: Medium
DedupPeriodMinutes: 60
Threshold: 1
LogTypes:
  - OpenAI.Audit
RuleID: "OpenAI.IP.Allowlist.Changes"

Stages and Predicates

Fires on OpenAI.Audit events when the condition below holds.

Condition

  • type is one of ip_allowlist.created, ip_allowlist.updated, ip_allowlist.deleted, ip_allowlist.config.activated, ip_allowlist.config.deactivated

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
typein
  • ip_allowlist.config.activated
  • ip_allowlist.config.deactivated
  • ip_allowlist.created
  • ip_allowlist.deleted
  • ip_allowlist.updated
field:"type" kind:in

Output fields

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

FieldSource
event_idid
actor_emailactor.session.user.email
actor_idactor.session.user.id
source_ipactor.session.ip_address
user_agentactor.session.user_agent
ip_detailsactor.session.ip_address_details

Response runbook

1. Verify the IP allowlist change was authorized and aligns with approved network security policies. Review the specific IPs added, removed, or modified, and confirm they match expected organizational infrastructure.

2. For deletions or deactivations, immediately assess if the organization is now exposed to unauthorized access. For updates, check if dangerous IPs like 0.0.0.0 (allow all), unknown ranges, or suspicious geolocations were added.

3. If the change is unauthorized or suspicious, immediately revert the configuration, review all access from non-allowlisted IPs since the change, audit role assignments and API key usage, and escalate for security review.

Worked example

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

Sample Test Event
{
  "actor": {
    "session": {
      "ip_address": "203.0.113.100",
      "user": {
        "email": "admin@company.com",
        "id": "user-admin123"
      },
      "user_agent": "Mozilla/5.0"
    },
    "type": "session"
  },
  "effective_at": 1702857600,
  "id": "audit_log-ip001",
  "ip_allowlist_deleted": {
    "allowed_ips": [
      "203.0.113.0/24",
      "198.51.100.0/24"
    ],
    "id": "ipallow_org-abc123",
    "name": "Corporate VPN"
  },
  "object": "organization.audit_log",
  "type": "ip_allowlist.deleted"
}

OpenAI SCIM Configuration Change

#
Severity
medium
Log types
OpenAI.Audit
Reference
platform.openai.com
Source
github.com/panther-labs/panther-analysis

Detects when SCIM (System for Cross-domain Identity Management) is enabled or disabled in an OpenAI organization. SCIM provides automated user provisioning and deprovisioning from identity providers (IdP) to OpenAI. Disabling SCIM can: - Bypass identity governance and access control policies - Allow orphaned accounts to persist after employee offboarding - Indicate an attempt to maintain unauthorized access - Violate compliance requirements for automated access management Enabling SCIM should be monitored for visibility into identity integration changes.

MITRE ATT&CK coverage

TacticTechniques
Persistence
Defense Impairment

Detection logic

def rule(event):
    return event.get("type") in ["scim.enabled", "scim.disabled"]


def title(event):
    event_type = event.get("type", "")
    action = "Enabled" if event_type == "scim.enabled" else "Disabled"
    email = event.deep_get("actor", "session", "user", "email", default="<UNKNOWN_USER>")
    return f"OpenAI SCIM {action} by [{email}]"


def severity(event):
    if event.get("type") == "scim.disabled":
        return "HIGH"
    if event.get("type") == "scim.enabled":
        return "LOW"
    return "DEFAULT"


def alert_context(event):
    field = "scim_enabled" if event.get("type") == "scim.enabled" else "scim_disabled"
    return {
        "event_type": event.get("type", "<UNKNOWN_EVENT_TYPE>"),
        "event_id": event.get("id", "<UNKNOWN_EVENT_ID>"),
        "scim_resource_id": event.deep_get(field, "id", default="<UNKNOWN_SCIM_RESOURCE_ID>"),
        "actor_email": event.deep_get(
            "actor", "session", "user", "email", default="<UNKNOWN_ACTOR_EMAIL>"
        ),
        "actor_id": event.deep_get("actor", "session", "user", "id", default="<UNKNOWN_ACTOR_ID>"),
        "source_ip": event.deep_get(
            "actor", "session", "ip_address", default="<UNKNOWN_SOURCE_IP>"
        ),
        "user_agent": event.deep_get(
            "actor", "session", "user_agent", default="<UNKNOWN_USER_AGENT>"
        ),
        "ip_details": event.deep_get("actor", "session", "ip_address_details", default={}),
    }

Rule specification

AnalysisType: rule
Description: |
  Detects when SCIM (System for Cross-domain Identity Management) is enabled or disabled
  in an OpenAI organization.
  SCIM provides automated user provisioning and deprovisioning from identity providers (IdP)
  to OpenAI. Disabling SCIM can:
  - Bypass identity governance and access control policies
  - Allow orphaned accounts to persist after employee offboarding
  - Indicate an attempt to maintain unauthorized access
  - Violate compliance requirements for automated access management
  Enabling SCIM should be monitored for visibility into identity integration changes.
DisplayName: "OpenAI SCIM Configuration Change"
Enabled: true
Filename: openai_scim_configuration_change.py
Reference: https://platform.openai.com/docs/api-reference/audit-logs
Runbook: |
  1. Verify the SCIM configuration change was authorized. Confirm whether the change aligns
  with approved identity management policies or integration updates.
  2. If SCIM was disabled, immediately verify that user provisioning and deprovisioning
  workflows are not disrupted. Check if there are alternative mechanisms in place for
  user lifecycle management.
  3. Review the actor who made the change, including source IP, geolocation, and timing.
  Investigate if the change was made from an unusual location or during off-hours.
  4. If the change is unauthorized or suspicious, immediately re-enable SCIM (if disabled),
  review all user accounts for unauthorized access, audit recent role assignments, and
  escalate for security review.
Reports:
  MITRE ATT&CK:
    - TA0003:T1098  # Account Manipulation
    - TA0005:T1562.001  # Impair Defenses: Disable or Modify Tools
Severity: Medium
DedupPeriodMinutes: 60
Threshold: 1
LogTypes:
  - OpenAI.Audit
RuleID: "OpenAI.SCIM.Configuration.Change"

Stages and Predicates

Fires on OpenAI.Audit events when the condition below holds.

Condition

  • type is one of scim.enabled, scim.disabled

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
typein
  • scim.disabled
  • scim.enabled
field:"type" kind:in

Output fields

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

FieldSource
event_typetype
event_idid
actor_emailactor.session.user.email
actor_idactor.session.user.id
source_ipactor.session.ip_address
user_agentactor.session.user_agent
ip_detailsactor.session.ip_address_details

Response runbook

1. Verify the SCIM configuration change was authorized. Confirm whether the change aligns

with approved identity management policies or integration updates.

2. If SCIM was disabled, immediately verify that user provisioning and deprovisioning

workflows are not disrupted. Check if there are alternative mechanisms in place for

user lifecycle management.

3. Review the actor who made the change, including source IP, geolocation, and timing.

Investigate if the change was made from an unusual location or during off-hours.

4. If the change is unauthorized or suspicious, immediately re-enable SCIM (if disabled),

review all user accounts for unauthorized access, audit recent role assignments, and

escalate for security review.

Worked example

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

Sample Test Event
{
  "actor": {
    "session": {
      "ip_address": "203.0.113.100",
      "ip_address_details": {
        "city": "San Francisco",
        "country": "US",
        "region": "California"
      },
      "user": {
        "email": "admin@company.com",
        "id": "user-admin123"
      },
      "user_agent": "Mozilla/5.0"
    },
    "type": "session"
  },
  "effective_at": 1702857600,
  "id": "audit_log-scim001",
  "object": "organization.audit_log",
  "scim_enabled": {
    "id": "org-abc123"
  },
  "type": "scim.enabled"
}

OpenAI Successful Login (Base Rule)

#
Severity
informational
Log types
OpenAI.Audit
Source
github.com/panther-labs/panther-analysis

Base rule for detecting OpenAI successful login events. This rule is used primarily as a building block for correlation rules and does not generate alerts on its own.

Detection logic

def rule(event):
    return event.get("type") == "login.succeeded"

Rule specification

AnalysisType: rule
Description: |
  Base rule for detecting OpenAI successful login events. This rule is used primarily
  as a building block for correlation rules and does not generate alerts on its own.
DisplayName: "OpenAI Successful Login (Base Rule)"
Enabled: true
Filename: openai_login_success.py
CreateAlert: false
Severity: Info
DedupPeriodMinutes: 60
Threshold: 999999
LogTypes:
  - OpenAI.Audit
RuleID: "OpenAI.Login.Success"

Stages and Predicates

Fires on OpenAI.Audit events when the condition below holds.

Condition

  • type is login.succeeded
Alert cadence
alerts after 999999 matches within 1h

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
typeeq
  • login.succeeded
field:"type" kind:eq value:"login.succeeded"

Worked example

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

Sample Test Event
{
  "actor": {
    "session": {
      "ip_address": "203.0.113.100",
      "user": {
        "email": "user@company.com",
        "id": "user-123"
      },
      "user_agent": "Mozilla/5.0"
    },
    "type": "session"
  },
  "effective_at": 1702857600,
  "id": "audit_log-success001",
  "object": "organization.audit_log",
  "type": "login.succeeded"
}