GitHub API Sensitive Resource Enumeration

edit
IMPORTANT: This documentation is no longer updated. Refer to Elastic's version policy and the latest documentation.

GitHub API Sensitive Resource Enumeration

edit

Detects a burst of GitHub api.request events from a single non-bot actor and source IP against sensitive routes such as Actions/Dependabot/environment secrets, public keys, git trees and contents, zipball archives, organization repository and member lists, webhooks, installations, and personal access tokens. Grouping by actor and source IP and requiring a high request count spread across several distinct sensitive routes separates broad enumeration from a single high-volume endpoint. This pattern is commonly used for credential discovery and source collection after token theft. Requires GitHub Enterprise audit log streaming with API Request Events enabled.

Rule type: esql

Rule indices: None

Severity: medium

Risk score: 47

Runs every: 10m

Searches indices from: now-11m (Date Math format, see also Additional look-back time)

Maximum alerts per execution: 100

References:

Tags:

  • Domain: Cloud
  • Domain: SaaS
  • Platform: GitHub
  • Use Case: Threat Detection
  • Tactic: Discovery
  • Tactic: Credential Access
  • Tactic: Collection
  • Data Source: Github
  • Data Source: GitHub Audit Logs
  • Rule Type: ES|QL
  • Resources: Investigation Guide

Version: 1

Rule authors:

  • Elastic

Rule license: Elastic License v2

Investigation guide

edit

Triage and analysis

Disclaimer: This investigation guide was created using generative AI technology and has been reviewed to improve its accuracy and relevance. While every effort has been made to ensure its quality, we recommend validating the content and adapting it to suit your specific environment and operational needs.

Investigating GitHub API Sensitive Resource Enumeration

api.request is a streaming-only audit action (available with GitHub Enterprise audit log streaming and API Request Events enabled). Sensitive routes include Actions/Dependabot/environment secrets, public keys, git trees, contents, zipball archives, organization repository lists, members, webhooks, installations, and personal access tokens. The rule groups requests by user.name and github.actor_ip in a 10-minute window and fires on >= 15 requests spanning >= 3 distinct sensitive routes, which distinguishes broad enumeration from a single high-volume route. Automated actors (github.actor_is_bot: true) and requests without an actor IP are excluded. An adversary who has stolen a token commonly enumerates these routes to locate secrets and source code before pulling them, so this activity frequently precedes successful credential access or source collection.

Possible investigation steps

  • Review the distinct routes probed (Esql.github_route_values, Esql.github_route_unique_count) and the breadth across repositories and organizations (Esql.github_repo_unique_count, Esql.github_org_values) to gauge how targeted or broad the enumeration is.
  • Identify the token and access method from Esql.github_token_id_values and Esql.github_programmatic_access_type_values, and confirm whether the token belongs to known automation.
  • Review Esql.github_user_agent_values for the client; treat user agents containing tooling signatures such as trufflehog or nord-stream as credential-stealer tooling.
  • Pivot on github.actor_ip and user.name and correlate with git.clone, repo.download_zip, and secret-scanning alerts from the same actor or token, which indicate the enumeration led to collection.
  • Check for a later run of successful (2xx) requests on secrets, trees, or contents from the same actor or token, which indicates successful enumeration rather than failed probing.

False positive analysis

  • Internal inventory, backup, or secret-rotation automation and GitHub App servers that enumerate installations, repositories, and secrets. These typically use a known token and user agent; confirm and scope exceptions by actor, token, or user agent when expected.
  • Security tooling that legitimately scans repositories for exposed secrets from a known source.

Response and remediation

  • Revoke the token if the actor is not a known automation identity, and rotate any secrets exposed on the enumerated routes.
  • Review PAT and OAuth grants for the actor, and tighten organization PAT access and approval policies.
  • Block or monitor the source IP if the activity is clearly reconnaissance, and review whether any probed route later returned a success for the same actor.
  • Escalate to incident response if the enumeration is followed by secret access, repository cloning, or ZIP downloads from the same actor or token.

Setup

edit

Setup

Requires the Elastic GitHub integration (logs-github.audit-*), GitHub Enterprise audit log streaming, and Enable API Request Events on the enterprise. api.request is not available via the REST audit-log API.

Rule query

edit
from logs-github.audit-* metadata _id, _index, _version
| where
  data_stream.dataset == "github.audit" and event.action == "api.request" and
  github.actor_ip is not null and (github.actor_is_bot is not null and github.actor_is_bot == false) and
  (
    github.route like "*secrets*" or
    github.route like "*public-key*" or
    github.route like "*git/trees*" or
    github.route like "*contents*" or
    github.route like "*zipball*" or
    github.route like "*hooks*" or
    github.route like "*installations*" or
    github.route like "*members*" or
    github.route like "*outside_collaborators*" or
    github.route like "*personal-access-token*" or
    github.route == "/organizations/:organization_id/repos"
  )
| stats
  Esql.event_count = COUNT(*),
  Esql.github_route_unique_count = COUNT_DISTINCT(github.route),
  Esql.github_repo_unique_count = COUNT_DISTINCT(github.repo),
  Esql.github_route_values = VALUES(github.route),
  Esql.github_org_values = VALUES(github.org),
  Esql.github_programmatic_access_type_values = VALUES(github.programmatic_access_type),
  Esql.github_user_agent_values = VALUES(github.user_agent),
  Esql.agent_id_values = VALUES(agent.id),
  Esql.data_stream_dataset_values = VALUES(data_stream.dataset),
  Esql.data_stream_namespace_values = VALUES(data_stream.namespace),
  Esql.github_token_id_values = VALUES(github.token_id)
  by user.name, github.actor_ip, Esql.time_window = DATE_TRUNC(10 minutes, @timestamp)
| keep user.name, github.actor_ip, Esql.*
| where Esql.event_count >= 15 and Esql.github_route_unique_count >= 3

Framework: MITRE ATT&CKTM