Detection rules › Panther
Panther rules: openai
| Rule | Severity |
|---|---|
| OpenAI Admin Role Assignment | high |
| OpenAI Anomalous API Key Activity | medium |
| OpenAI Brute Force Login Success | high |
| OpenAI Credential Stuffing | medium |
| OpenAI Failed Login (Base Rule) | informational |
| OpenAI IP Allowlist Configuration Changes | medium |
| OpenAI SCIM Configuration Change | medium |
| OpenAI Successful Login (Base Rule) | informational |
OpenAI Admin Role Assignment
#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
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation | |
| Stealth |
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
typeisrole.assignment.createdrole_assignment_created.idis 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.
| Field | Kind | Values | Search |
|---|---|---|---|
role_assignment_created.id | is_not_null | field:"role_assignment_created.id" kind:is_not_null | |
type | eq |
| field:"type" kind:eq value:"role.assignment.created" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
event_type | type |
event_id | id |
assignment_id | role_assignment_created.id |
principal_id | role_assignment_created.principal_id |
principal_type | role_assignment_created.principal_type |
resource_id | role_assignment_created.resource_id |
resource_type | role_assignment_created.resource_type |
actor_email | actor.session.user.email |
actor_id | actor.session.user.id |
source_ip | actor.session.ip_address |
user_agent | actor.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
#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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Stealth | |
| Lateral Movement | |
| Command & Control |
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
typeis one ofapi_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.
| Field | Kind | Values | Search |
|---|---|---|---|
type | in |
| field:"type" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
event_type | type |
event_id | id |
actor_email | actor.session.user.email |
actor_id | actor.session.user.id |
source_ip | actor.session.ip_address |
user_agent | actor.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
#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
| Tactic | Techniques |
|---|---|
| Credential Access |
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
#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
| Tactic | Techniques |
|---|---|
| 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
typeislogin.failed
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
type | eq |
| field:"type" kind:eq value:"login.failed" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | actor.session.user.email |
ip_address | actor.session.ip_address |
error_code | login_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)
#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
typeislogin.failed
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
type | eq |
| 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
#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
| Tactic | Techniques |
|---|---|
| Defense Impairment |
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
typeis one ofip_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.
| Field | Kind | Values | Search |
|---|---|---|---|
type | in |
| field:"type" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
event_id | id |
actor_email | actor.session.user.email |
actor_id | actor.session.user.id |
source_ip | actor.session.ip_address |
user_agent | actor.session.user_agent |
ip_details | actor.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
#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
| Tactic | Techniques |
|---|---|
| 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
typeis one ofscim.enabled,scim.disabled
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
type | in |
| field:"type" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
event_type | type |
event_id | id |
actor_email | actor.session.user.email |
actor_id | actor.session.user.id |
source_ip | actor.session.ip_address |
user_agent | actor.session.user_agent |
ip_details | actor.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)
#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
typeislogin.succeeded
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
type | eq |
| 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"
}