Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/morwn/github-threat-detector/llms.txt

Use this file to discover all available pages before exploring further.

Push events are the most fundamental unit of repository activity, and anomalies in them are reliable indicators of several different attack classes. A force push to main can silently erase commit history, overwrite a legitimate release tag, or inject backdated commits without a pull request audit trail. Rapid sequential pushes in a short window often indicate scripted automation rather than human activity. Branch deletion targeting well-known branch names is a common step in pivot attacks that disable branch protection to enable direct pushes. An abnormal spike in fork activity can precede coordinated typosquatting or dependency confusion attacks. The four rules in this category cover each of these patterns.

force-push-default

Severity: high
Required data: Events only

What it detects

A PushEvent where the forced field is true and the target ref is refs/heads/main or refs/heads/master. Force pushes to the default branch can rewrite history, remove commits from the audit trail, or replace a known-good tree with an attacker-controlled one.

How detection works

The analyzer queries push events filtered on both the forced flag and the ref value. No actor allowlisting is applied — even known maintainers should not be force-pushing to the default branch under normal circumstances.
SELECT id, repo_name, actor_login, created_at, payload
FROM github_events
WHERE event_type = 'PushEvent'
  AND (payload->>'forced')::boolean IS TRUE
  AND payload->>'ref' IN ('refs/heads/main', 'refs/heads/master')

Finding description

Force push to default branch by <actor_login>

Evidence fields

FieldDescription
refTarget branch ref (e.g. refs/heads/main)
headThe new HEAD commit SHA after the force push
beforeThe previous HEAD commit SHA that was overwritten
created_atTimestamp of the push
Compare before and head to understand exactly which commits were rewritten. If before does not appear in the current history, those commits have been permanently removed from the default branch.Remediation: Enable the “Require a pull request before merging” and “Do not allow bypassing the above settings” branch protection rules. These prevent force pushes even by administrators.

rapid-sequential-pushes

Severity: medium
Required data: Events only

What it detects

A non-bot actor who makes 5 or more push events from the same repository within 60 seconds. This pattern is a strong indicator of scripted automation — either a CI pipeline running as a human account, or an attacker using automated tooling to push multiple payloads in rapid succession.

Configurable thresholds

Both thresholds are defined in config.py and can be adjusted to match your repository’s normal CI patterns:
Config variableDefaultDescription
RAPID_PUSH_WINDOW_SECONDS60Maximum elapsed time (seconds) between first and last push in the burst
RAPID_PUSH_MIN_COUNT5Minimum number of pushes required to trigger

How detection works

The analyzer groups push events by actor, repository, and minute bucket. It filters for groups where the raw count meets RAPID_PUSH_MIN_COUNT and the elapsed time between the first and last event in the group falls within RAPID_PUSH_WINDOW_SECONDS. Bot accounts are excluded from consideration.
SELECT actor_login, repo_name,
       COUNT(*) AS cnt,
       MIN(created_at) AS first_at,
       MAX(created_at) AS last_at
FROM github_events
WHERE event_type = 'PushEvent'
  AND actor_login NOT LIKE '%-bot'
  AND actor_login NOT LIKE '%[bot]'
GROUP BY actor_login, repo_name, date_trunc('minute', created_at)
HAVING COUNT(*) >= 5
   AND extract(epoch from (MAX(created_at) - MIN(created_at))) <= 60

Finding description

Actor <actor_login> made <count> pushes within 60s

Evidence fields

FieldDescription
countNumber of pushes in the burst
first_atTimestamp of the first push
last_atTimestamp of the last push
If your repository uses a legitimate service account for automated commits (e.g. a changelog bot that pushes multiple times after a release), make sure that account’s login ends with -bot or [bot] so it is excluded from this check.

delete-branch-protection

Severity: high
Required data: Events only

What it detects

A DeleteEvent of type branch where the deleted branch is one of the well-known protected branch names: main, master, release, develop, or production. Deleting these branches bypasses branch protection rules entirely (the rules are attached to the branch name and are removed when the branch is deleted), enabling direct pushes on any subsequently recreated branch with the same name.

How detection works

The analyzer queries delete events filtered on ref_type = 'branch' and a fixed set of protected branch names. Bot accounts are excluded.
SELECT id, repo_name, actor_login, created_at, payload
FROM github_events
WHERE event_type = 'DeleteEvent'
  AND payload->>'ref_type' = 'branch'
  AND payload->>'ref' IN ('main', 'master', 'release', 'develop', 'production')
  AND actor_login NOT LIKE '%-bot'
  AND actor_login NOT LIKE '%[bot]'

Finding description

Actor <actor_login> deleted branch '<ref>' -- potential protection bypass

Evidence fields

FieldDescription
refName of the deleted branch
created_atTimestamp of the delete event
Deleting a protected branch name and recreating it is a known technique for bypassing branch protection rules on GitHub. After recreation the branch has no protection rules until an administrator reapplies them.Remediation: Treat any deletion of main or master as a critical incident. Restore the branch from the last known-good commit, immediately reapply branch protection rules, and audit all pushes that occurred to the branch between deletion and restoration.

fork-spike

Severity: medium
Required data: Events only

What it detects

A day in the past 7 days where the repository received more than 3× its historical daily average number of forks. Abnormal fork spikes can precede coordinated typosquatting campaigns, dependency confusion attacks, or intelligence gathering by a threat actor preparing a targeted supply chain attack.

Configurable thresholds

Config variableDefaultDescription
FORK_SPIKE_MULTIPLIER3.0Multiple of historical average that triggers the rule
FORK_SPIKE_WINDOW_DAYS7How many days back to look for spike days

How detection works

The analyzer computes a per-day fork count and then calculates the mean daily fork rate across all history. Days within the rolling window where the count exceeds avg × FORK_SPIKE_MULTIPLIER are emitted as findings.
WITH daily_forks AS (
    SELECT repo_name, date_trunc('day', created_at) AS day, COUNT(*) AS cnt
    FROM github_events
    WHERE event_type = 'ForkEvent'
    GROUP BY repo_name, day
),
stats AS (
    SELECT repo_name, AVG(cnt) AS avg_cnt
    FROM daily_forks
    GROUP BY repo_name
)
SELECT df.repo_name, df.day, df.cnt, s.avg_cnt
FROM daily_forks df
JOIN stats s ON df.repo_name = s.repo_name
WHERE df.day >= now() - INTERVAL '7 days'
  AND df.cnt > s.avg_cnt * 3.0
  AND s.avg_cnt > 0

Finding description

Fork spike on <date>: <count> forks vs avg <avg> (>3.0x baseline)

Evidence fields

FieldDescription
dayThe calendar day of the spike
countNumber of forks recorded that day
avgHistorical average daily fork count
Viral social media posts, security blog mentions, and conference talks can all cause legitimate fork spikes. Cross-reference the spike date with public activity before escalating. If no external explanation is apparent, investigate whether the forks belong to accounts with low activity, newly created profiles, or names resembling your package.

Build docs developers (and LLMs) love