OpenAI Anomalous API Key Activity


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)

Query · python

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>"
        ),
    }

Analyst notes

  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.
Raw source OpenAI Anomalous API Key Activity · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
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"
Tests:
  # Test 1: Should NOT alert - API key created with read-only scopes
  - Name: "API key created with read-only scopes - No Alert"
    ExpectedResult: false
    Log:
      id: "audit_log-test001"
      type: "api_key.created"
      effective_at: 1702857600
      object: "organization.audit_log"
      actor:
        type: "session"
        session:
          user:
            id: "user-developer456"
            email: "developer@company.com"
          ip_address: "203.0.113.50"
          user_agent: "Mozilla/5.0"
          ip_address_details:
            country: "US"
            city: "San Francisco"
      api_key_created:
        id: "key-abc123"
        data:
          name: "Production API Key"
          scopes:
            - "models:read"
  # Test 2: Should ALERT HIGH - API key created with elevated permissions
  - Name: "API key with elevated permissions - Alert HIGH"
    ExpectedResult: true
    Log:
      id: "audit_log-test002"
      type: "api_key.created"
      effective_at: 1702857600
      object: "organization.audit_log"
      actor:
        type: "session"
        session:
          user:
            id: "user-contractor789"
            email: "contractor@external.com"
          ip_address: "192.0.2.100"
          user_agent: "curl/7.68.0"
          ip_address_details:
            country: "RO"
            city: "Bucharest"
      api_key_created:
        id: "key-suspicious001"
        data:
          name: "Full Access Key"
          scopes:
            - "models:write"
            - "organization:write"
            - "all"
  # Test 3: Should ALERT HIGH - API key updated with elevated permissions
  - Name: "API key updated with elevated permissions - Alert HIGH"
    ExpectedResult: true
    Log:
      id: "audit_log-test003"
      type: "api_key.updated"
      effective_at: 1702857600
      object: "organization.audit_log"
      actor:
        type: "session"
        session:
          user:
            id: "user-intern001"
            email: "intern@company.com"
          ip_address: "203.0.113.75"
          user_agent: "Mozilla/5.0"
      api_key_updated:
        id: "key-existing123"
        data:
          scopes:
            - "models:write"
            - "api_keys:write"
            - "admin"
  # Test 4: Should NOT alert - API key deleted
  - Name: "API key deleted - No Alert"
    ExpectedResult: false
    Log:
      id: "audit_log-test004"
      type: "api_key.deleted"
      effective_at: 1702857600
      object: "organization.audit_log"
      actor:
        type: "session"
        session:
          user:
            id: "user-unknown999"
            email: "unknown@suspicious.com"
          ip_address: "198.18.0.50"
          user_agent: "python-requests/2.28.0"
          ip_address_details:
            country: "CN"
            city: "Beijing"
      api_key_deleted:
        id: "key-production001"
  # Test 5: Should ALERT HIGH - API key created with 'all' scope
  - Name: "API key with 'all' scope - Alert HIGH"
    ExpectedResult: true
    Log:
      id: "audit_log-test005"
      type: "api_key.created"
      effective_at: 1702857600
      object: "organization.audit_log"
      actor:
        type: "api_key"
        api_key:
          type: "user"
          id: "key-parent001"
          user:
            id: "user-suspicious002"
            email: "suspicious@external.com"
      api_key_created:
        id: "key-child001"
        data:
          name: "Nested API Key"
          scopes:
            - "all"
  # Test 6: Should ALERT HIGH - Multiple elevated scopes
  - Name: "API key with multiple elevated scopes - Alert HIGH"
    ExpectedResult: true
    Log:
      id: "audit_log-test006"
      type: "api_key.created"
      effective_at: 1702857600
      object: "organization.audit_log"
      actor:
        type: "session"
        session:
          user:
            id: "user-compromised001"
            email: "compromised@company.com"
          ip_address: "192.0.2.200"
          user_agent: "PostmanRuntime/7.29.2"
          ja3: "771,4865-4866-4867,0-23-65281,29-23-24,0"
          ja4: "t13d1516h2_8daaf6152771_e5627efa2ab1"
      api_key_created:
        id: "key-malicious001"
        data:
          name: "Admin Override Key"
          scopes:
            - "organization:write"
            - "admin"
  # Test 7: Should NOT alert - API key updated without elevated scopes
  - Name: "API key updated without elevated scopes - No Alert"
    ExpectedResult: false
    Log:
      id: "audit_log-test007"
      type: "api_key.updated"
      effective_at: 1702857600
      object: "organization.audit_log"
      actor:
        type: "session"
        session:
          user:
            id: "user-admin001"
            email: "admin@company.com"
          ip_address: "203.0.113.100"
          user_agent: "Mozilla/5.0"
      api_key_updated:
        id: "key-regular001"
        data:
          name: "Updated Key Name"
          scopes:
            - "models:read"
  # Test 8: Should NOT alert - Non-API key event
  - Name: "Login event - No Alert"
    ExpectedResult: false
    Log:
      id: "audit_log-test008"
      type: "login.succeeded"
      effective_at: 1702857600
      object: "organization.audit_log"
      actor:
        type: "session"
        session:
          user:
            id: "user-normal001"
            email: "user@company.com"
          ip_address: "203.0.113.200"
          user_agent: "Mozilla/5.0"


# ------ paired body: openai_api_key_anomalous_activity.py ------

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>"
        ),
    }

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.