{
  "generatedAt": "2026-10-12T16:01:05.493Z",
  "sourceTypeLabels": {
    "postmortem": "Postmortem: the victim's own account of the incident",
    "research": "Research: explains the primitive, generalizes beyond one victim",
    "tracker": "Tracker: live campaign feed, recheck before publishing",
    "advisory": "Advisory: vendor/CVE record with affected version ranges"
  },
  "scenarios": [
    {
      "id": "user-dependabot-impersonation",
      "plane": "user",
      "title": "Dependabot impersonation",
      "dateLabel": "July 2023",
      "sortDate": "2023-07",
      "narrative": "Attackers used stolen personal access tokens to push commits faked to look like automated Dependabot contributions across hundreds of public and private repositories. They added a hook.yml workflow file that shipped GitHub secrets to an external server on every push, and appended obfuscated JS to every .js file in the project to harvest form passwords.",
      "permissionsAbused": [
        "contents:write",
        "workflow scope (classic PAT)"
      ],
      "attribution": "Checkmarx noted that PAT activity logging was only visible to enterprise accounts at the time",
      "sources": [
        {
          "type": "research",
          "title": "Surprise: When Dependabot Contributes Malicious Code",
          "url": "https://checkmarx.com/blog/surprise-when-dependabot-contributes-malicious-code/"
        }
      ]
    },
    {
      "id": "user-mercedes-benz-token-leak",
      "plane": "user",
      "title": "Mercedes-Benz source code exposure",
      "dateLabel": "January 2024",
      "sortDate": "2024-01",
      "narrative": "An employee committed a token to a public repo. It gave unrestricted and unmonitored access to the entire source code on the internal GitHub Enterprise Server, exposing database connection strings, cloud access keys, blueprints, design documents, SSO passwords, and API keys. The first token granted full ownership of the account; inside one of its private repos sat a second token to the GHE instance, a classic chained-credential blast radius that ran from the user plane straight into the enterprise plane.",
      "permissionsAbused": [
        "Classic PAT (full account access)",
        "Chained token → GitHub Enterprise Server access"
      ],
      "sources": [
        {
          "type": "research",
          "title": "Mercedes-Benz source code at risk: GitHub token mishap",
          "url": "https://redhuntlabs.com/blog/mercedes-benz-source-code-at-risk-github-token-mishap-sparks-major-security-concerns/"
        },
        {
          "type": "tracker",
          "title": "How a mistakenly published password exposed Mercedes-Benz source code",
          "url": "https://techcrunch.com/2024/01/26/mercedez-benz-token-exposed-source-code-github"
        },
        {
          "type": "tracker",
          "title": "A mishandled GitHub token exposed Mercedes-Benz source code",
          "url": "https://www.bleepingcomputer.com/news/security/a-mishandled-github-token-exposed-mercedes-benz-source-code/"
        },
        {
          "type": "tracker",
          "title": "Leaked GitHub token exposed Mercedes source code",
          "url": "https://www.securityweek.com/leaked-github-token-exposed-mercedes-source-code/"
        }
      ]
    },
    {
      "id": "user-oauth-consent-phishing",
      "plane": "user",
      "title": "OAuth consent phishing at scale",
      "dateLabel": "2025–2026",
      "sortDate": "2025-01",
      "narrative": "A campaign hit roughly 12,000 repositories with fake \"Security Alert\" issues, tricking developers into authorizing a malicious OAuth app that then held full control of their accounts and code. Because the link lands on the real GitHub authorization screen, it passes SPF/DKIM, bypasses MFA, and grants a persistent token without ever touching a password. Device-code phishing against GitHub follows the same shape: research reports success rates above 90% in authorized engagements, and a broadly scoped phished token yields private repos, Actions secrets, and more.",
      "permissionsAbused": [
        "OAuth App authorization (broad scope)",
        "Device-code phishing"
      ],
      "sources": [
        {
          "type": "tracker",
          "title": "Fake 'Security Alert' issues use OAuth app to hijack accounts",
          "url": "https://www.bleepingcomputer.com/news/security/fake-security-alert-issues-on-github-use-oauth-app-to-hijack-accounts/"
        },
        {
          "type": "research",
          "title": "Device code phishing explained: how attackers abuse OAuth across SaaS",
          "url": "https://appomni.com/blog/device-code-phishing-saas/"
        }
      ],
      "steps": [
        {
          "title": "A fake \"Security Alert\" issue is filed",
          "detail": "The campaign opened issues on roughly 12,000 repositories, styled to look like an automated GitHub security notification about the repository itself."
        },
        {
          "title": "The link leads to a genuine GitHub authorization screen",
          "detail": "The issue's call-to-action pointed to GitHub's own, real OAuth authorization page for a malicious OAuth App — not a spoofed login page — so it passed SPF/DKIM checks and looked legitimate under every check a developer could reasonably make."
        },
        {
          "title": "One click grants a persistent token",
          "detail": "Approving the prompt handed the attacker's app a persistent OAuth token. Because the developer's own already-authenticated session did the authorizing, MFA was never re-challenged and never came into play."
        },
        {
          "title": "The token grants full account and code control",
          "detail": "With that token, the attacker held exactly the access the developer had just authorized: full control over the developer's repositories and account activity, with no password ever touched."
        },
        {
          "title": "Device-code phishing achieves the same outcome without an app",
          "detail": "A related technique skips installing an OAuth App entirely: research reports success rates above 90% against users in authorized testing engagements using device-code phishing alone."
        }
      ],
      "mitigations": [
        {
          "action": "Require security/IT approval before any new GitHub App or OAuth App installation"
        },
        {
          "action": "Review every currently installed GitHub App / OAuth App and its granted scope on a recurring basis"
        },
        {
          "action": "Train developers to treat any \"authorize this app\" prompt reached via an issue, email, or chat link as suspicious",
          "why": "it can resolve to a genuine github.com domain and still be malicious"
        },
        {
          "action": "Restrict which OAuth Apps can access organization resources",
          "why": "via OAuth App access restrictions"
        },
        {
          "action": "Monitor and alert on new OAuth App authorizations in the audit log",
          "why": "rather than only reviewing them periodically"
        }
      ]
    },
    {
      "id": "user-circleci-session-theft",
      "plane": "user",
      "title": "CircleCI session-cookie theft",
      "dateLabel": "December 2022 – January 2023",
      "sortDate": "2022-12",
      "narrative": "Malware on an engineer's laptop stole a 2FA-backed session cookie, letting the attacker impersonate an employee who had rights to generate production tokens. Customer environment variables, tokens, and SSH keys were exfiltrated, and encryption keys made at-rest encryption moot. CircleCI rotated every customer GitHub OAuth token on their behalf. Worth noting for tiering purposes: a session cookie sat above MFA in the trust chain.",
      "permissionsAbused": [
        "2FA-backed session cookie (bypasses PAT/SSH controls)"
      ],
      "attribution": "CircleCI incident report",
      "sources": [
        {
          "type": "postmortem",
          "title": "CircleCI: rotating secrets for the January 4th incident",
          "url": "https://support.circleci.com/hc/en-us/articles/11816211460891-Rotating-Secrets-for-January-4th-Incident"
        }
      ]
    },
    {
      "id": "user-nx-console-github-breach",
      "plane": "user",
      "title": "GitHub's internal repositories taken through a VS Code extension",
      "dateLabel": "May 2026",
      "sortDate": "2026-05",
      "narrative": "On 18 May 2026 GitHub detected and contained the compromise of an employee's device through a poisoned VS Code extension from a third party. Aikido reports that GitHub confirmed the extension as Nx Console, which has more than 2.2 million installs and a verified publisher; the bad release was pulled from the VS Code Marketplace within about 18 minutes. That was enough. The attacker claimed about 3,800 repositories, and GitHub said the claim was directionally consistent with its own investigation. Only GitHub's internal repositories were affected, but GitHub rotated the GitHub Enterprise Server signing key as a precaution, and every GHES customer had to rotate the GPG public key used to verify upgrades. Nobody needed a GitHub permission to start this: the extension ran with the developer's own access.",
      "permissionsAbused": [
        "Everything the developer's machine could reach",
        "Developer tokens and cloned code"
      ],
      "attribution": "GitHub",
      "sources": [
        {
          "type": "postmortem",
          "title": "Investigating unauthorized access to GitHub's internal repositories",
          "url": "https://github.blog/security/investigating-unauthorized-access-to-githubs-internal-repositories/"
        },
        {
          "type": "research",
          "title": "GitHub breached through a VS Code extension",
          "url": "https://www.aikido.dev/blog/github-breached-vs-code-extension"
        },
        {
          "type": "tracker",
          "title": "5 supply chain attacks in 48 hours",
          "url": "https://www.stepsecurity.io/blog/5-supply-chain-attacks-in-48-hours-why-securing-one-layer-is-not-enough"
        }
      ],
      "steps": [
        {
          "title": "A trusted extension ships a bad release",
          "detail": "A malicious version of a widely installed VS Code extension was published under its real, verified publisher. Extensions update automatically, so it reached installed copies before anyone reviewed it."
        },
        {
          "title": "It runs on an employee's machine",
          "detail": "The extension executed with the developer's own rights: their tokens, their SSH keys and the repositories they had cloned or could clone."
        },
        {
          "title": "Internal repositories are copied out",
          "detail": "The attacker took roughly 3,800 of GitHub's internal repositories before GitHub contained the device on the same day."
        },
        {
          "title": "Downstream keys are rotated",
          "detail": "GitHub rotated the GHES signing key, so every Enterprise Server customer had to update the key their upgrades are verified against."
        }
      ],
      "mitigations": [
        {
          "action": "Allow-list IDE extensions centrally",
          "why": "a verified publisher was not enough to keep a bad release out"
        },
        {
          "action": "Delay extension updates by a few days",
          "why": "the malicious release was live for minutes; a cooldown would have skipped it"
        },
        {
          "action": "Keep long-lived tokens off developer laptops",
          "why": "whatever the machine holds, the extension holds"
        },
        {
          "action": "Enable an IP allow list on the organization",
          "why": "tokens taken off the machine stop working elsewhere"
        },
        {
          "action": "Develop sensitive repositories in Codespaces or dev containers",
          "why": "keeps the code and credentials off the endpoint"
        }
      ]
    },
    {
      "id": "user-glassworm",
      "plane": "user",
      "title": "GlassWorm: editor extensions that steal GitHub tokens and force-push",
      "dateLabel": "October 2025 – March 2026",
      "sortDate": "2025-10",
      "narrative": "GlassWorm started on 17 October 2025 as malicious VS Code extensions on Open VSX and the VS Code Marketplace. The loader is written in invisible Unicode characters, so it shows as blank space in editors, terminals and GitHub's diff view. Once installed, it takes GitHub, Git, npm and Open VSX credentials from the developer's machine, uses them to infect more extensions, and turns the machine into a proxy with remote access. From late 2025 the stolen GitHub tokens were also used against repositories. In March 2026 the same actor took the latest real commit in hundreds of Python repositories, added an obfuscated payload to files such as setup.py, and force-pushed it to the default branch. The author and commit message were kept, so only the committer date gives it away. Aikido found at least 151 affected repositories through code search in 3 to 9 March, and StepSecurity found hundreds more across hundreds of accounts, which it tracks as ForceMemo and ties to GlassWorm through the same command wallet. A force-push like this only works where the branch allows it.",
      "permissionsAbused": [
        "Tokens stored on the developer's machine",
        "contents:write (force-push to the default branch)"
      ],
      "attribution": "Aikido, StepSecurity",
      "sources": [
        {
          "type": "research",
          "title": "Glassworm returns: invisible Unicode malware found in 150+ GitHub repositories",
          "url": "https://www.aikido.dev/blog/glassworm-returns-unicode-attack-github-npm-vscode"
        },
        {
          "type": "research",
          "title": "ForceMemo: hundreds of GitHub Python repos compromised via account takeover and force-push",
          "url": "https://www.stepsecurity.io/blog/forcememo-hundreds-of-github-python-repos-compromised-via-account-takeover-and-force-push"
        },
        {
          "type": "tracker",
          "title": "GlassWorm attack uses stolen GitHub tokens to force-push malware into Python repos",
          "url": "https://thehackernews.com/2026/03/glassworm-attack-uses-stolen-github.html"
        }
      ],
      "steps": [
        {
          "title": "A malicious extension is installed",
          "detail": "Extensions on Open VSX and the VS Code Marketplace carried a loader hidden in invisible Unicode characters. Reviewing the source shows nothing."
        },
        {
          "title": "Credentials are taken from the machine",
          "detail": "The payload reads GitHub tokens from git credential fill, VS Code's extension storage, ~/.git-credentials and environment variables, along with npm and Open VSX tokens."
        },
        {
          "title": "The worm publishes itself",
          "detail": "Stolen registry tokens are used to push infected versions of other extensions the victim maintains."
        },
        {
          "title": "Repositories are rewritten with a force-push",
          "detail": "The attacker rebases the latest commit with the payload added and force-pushes it to the default branch. The original author and message stay, so the history looks unchanged at a glance."
        }
      ],
      "mitigations": [
        {
          "action": "Block force pushes on default branches with a ruleset",
          "why": "the GitHub side of the campaign was nothing but force-pushes"
        },
        {
          "action": "Require signed commits",
          "why": "a rewritten commit loses the original signature"
        },
        {
          "action": "Allow-list IDE extensions centrally",
          "why": "the way in was an extension the developer chose to install"
        },
        {
          "action": "Keep long-lived tokens off developer laptops and set a maximum token lifetime",
          "why": "every token on the machine was taken"
        },
        {
          "action": "Alert on force pushes in the audit log",
          "why": "the committer date is the only visible trace"
        }
      ]
    },
    {
      "id": "app-heroku-travisci-oauth-theft",
      "plane": "application",
      "title": "Heroku / Travis CI OAuth token theft",
      "dateLabel": "April 2022",
      "sortDate": "2022-04",
      "narrative": "The canonical application-scope incident. Stolen OAuth user tokens issued to Heroku and Travis CI were used to authenticate to the GitHub API and download private repositories from dozens of organizations, including npm; GitHub assessed the actor was likely mining those repos for secrets to pivot into other infrastructure. Detection started when GitHub found unauthorized access to npm production infrastructure using an AWS key that had been lifted from a private npm repo downloaded with one of those tokens. Five specific app IDs were implicated (three Heroku Dashboard variants, Heroku Dashboard Classic, and Travis CI). The tokens were never compromised at GitHub. The app vendor was the weak point.",
      "permissionsAbused": [
        "OAuth App token (private repo read)"
      ],
      "sources": [
        {
          "type": "postmortem",
          "title": "GitHub security alert: stolen OAuth user tokens",
          "url": "https://github.blog/news-insights/company-news/security-alert-stolen-oauth-user-tokens/"
        }
      ]
    },
    {
      "id": "app-tj-actions-changed-files",
      "plane": "application",
      "title": "tj-actions/changed-files supply-chain compromise (CVE-2025-30066)",
      "dateLabel": "March 2025",
      "sortDate": "2025-03",
      "narrative": "The attacker used a previously obtained GitHub token with write permissions to push a commit disguised as renovate[bot], slipped into a legitimate auto-merged PR, then retroactively repointed multiple version tags at the malicious commit, affecting over 23,000 repositories. The payload dumped runner memory to logs, exposing access keys, PATs, npm tokens, and private RSA keys. Upstream, reviewdog/action-setup (CVE-2025-30154) was backdoored first, and researchers traced the original target to Coinbase's agentkit repo. The permission abused, contents:write on an Action repo, implies tag-write, which implies control of every consumer pinning by tag.",
      "permissionsAbused": [
        "contents:write on an Action repo (tag rewrite)"
      ],
      "attribution": "Unit42",
      "sources": [
        {
          "type": "research",
          "title": "GitHub Actions supply chain attack: Coinbase to tj-actions",
          "url": "https://unit42.paloaltonetworks.com/github-actions-supply-chain-attack/"
        },
        {
          "type": "advisory",
          "title": "CISA alert: tj-actions/changed-files and reviewdog/action-setup",
          "url": "https://www.cisa.gov/news-events/alerts/2025/03/18/supply-chain-compromise-third-party-tj-actionschanged-files-cve-2025-30066-and-reviewdogaction"
        },
        {
          "type": "advisory",
          "title": "GHSA-mrrh-fwg8-r2c3 (CVE-2025-30066)",
          "url": "https://github.com/advisories/ghsa-mrrh-fwg8-r2c3"
        },
        {
          "type": "tracker",
          "title": "tj-actions/changed-files supply chain attack (Wiz)",
          "url": "https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066"
        }
      ],
      "steps": [
        {
          "title": "An upstream dependency is backdoored first",
          "detail": "Attackers compromised reviewdog/action-setup (CVE-2025-30154), a dependency of the tj-actions release process, obtaining a GitHub token with write permissions in the process."
        },
        {
          "title": "A commit is disguised as a routine bot contribution",
          "detail": "Using that write token, the attacker pushed a commit to tj-actions/changed-files crafted to look like an automated renovate[bot] update, and it was folded into a legitimate pull request that auto-merged."
        },
        {
          "title": "Release tags are retroactively repointed",
          "detail": "The attacker then force-moved multiple existing version tags to point at the malicious commit. Every consuming workflow pinning by tag — not by commit SHA — silently ran the tampered action on its very next run."
        },
        {
          "title": "The payload dumps runner memory into public logs",
          "detail": "The injected code printed the CI runner's process memory straight into the workflow's own log output, a public artifact on public repositories, exposing access keys, PATs, npm tokens, and private RSA keys."
        },
        {
          "title": "23,000+ repositories are exposed in one sweep",
          "detail": "Because tj-actions/changed-files was so widely consumed, the tag retarget alone reached every downstream repository's next workflow run; researchers traced the original intended target to Coinbase's agentkit repository."
        }
      ],
      "mitigations": [
        {
          "action": "Pin third-party Actions to a full commit SHA, never a mutable tag or branch",
          "why": "a tag can be retargeted with no warning"
        },
        {
          "action": "Restrict which Actions can run to GitHub-verified creators or an internal allow-list"
        },
        {
          "action": "Enable dependency review on pull requests",
          "why": "surfaces unexpected Action version bumps before merge"
        },
        {
          "action": "Apply tag-protection rulesets on your own releases",
          "why": "tags can't be silently force-moved after publication"
        },
        {
          "action": "Treat workflow log output as a secret-exposure surface",
          "why": "avoid any step that could dump environment variables or process memory into it"
        }
      ]
    },
    {
      "id": "app-artipacked",
      "plane": "application",
      "title": "ArtiPACKED artifact token leakage",
      "dateLabel": "August 2024",
      "sortDate": "2024-08",
      "narrative": "actions/checkout persists the GitHub token into .git/config by default; uploading the whole checkout directory as an artifact exposes it. Researchers observed leakage of both GITHUB_TOKEN and the undocumented ACTIONS_RUNTIME_TOKEN, which lives about six hours and can be used to swap an artifact for a malicious version. The token-theft path is a race condition: download the artifact mid-run and create a branch before expiry. It requires the workflow to hold contents: write.",
      "permissionsAbused": [
        "GITHUB_TOKEN persisted in .git/config",
        "ACTIONS_RUNTIME_TOKEN (~6h lifetime)"
      ],
      "attribution": "Unit42",
      "sources": [
        {
          "type": "research",
          "title": "ArtiPACKED: hacking giants through a race condition in Actions artifacts",
          "url": "https://unit42.paloaltonetworks.com/github-repo-artifacts-leak-tokens/"
        },
        {
          "type": "tracker",
          "title": "GitHub Actions artifacts found leaking auth tokens in popular projects",
          "url": "https://www.bleepingcomputer.com/news/security/github-actions-artifacts-found-leaking-auth-tokens-in-popular-projects/"
        },
        {
          "type": "research",
          "title": "GitHub Actions security: a guide to common risks",
          "url": "https://orca.security/resources/blog/github-actions-security-risks/"
        },
        {
          "type": "research",
          "title": "GitHub Actions hardening",
          "url": "https://orca.security/resources/blog/github-actions-hardening/"
        }
      ]
    },
    {
      "id": "app-agentic-mcp-tokens",
      "plane": "application",
      "title": "GitHub MCP / agentic token breadth",
      "dateLabel": "May 2025 – ongoing",
      "sortDate": "2025-05",
      "narrative": "Invariant Labs showed a malicious public issue can coerce an agent into pulling private repo data into context and leaking it via an autonomously created public PR, arguing this can't be fixed server-side and needs architectural controls. The pattern kept spreading: Orca's RoguePilot used a hidden prompt in an issue to make Copilot leak a repository's privileged token, and Noma's GitLost targeted agentic workflows handed a token with read access across an org's repos, private ones included. This is the clearest case where the token's scope breadth is the entire vulnerability. Nothing else needs to go wrong.",
      "permissionsAbused": [
        "Broad read-across-org token handed to an autonomous agent"
      ],
      "attribution": "Invariant Labs, Orca Security, Noma Security",
      "sources": [
        {
          "type": "research",
          "title": "GitHub MCP exploited: accessing private repositories via MCP",
          "url": "https://invariantlabs.ai/blog/mcp-github-vulnerability"
        },
        {
          "type": "research",
          "title": "github-mcp-server issue #844: token scope reproduction",
          "url": "https://github.com/github/github-mcp-server/issues/844"
        },
        {
          "type": "tracker",
          "title": "Public GitHub issue could trick agentic workflows into leaking private repo data",
          "url": "https://thehackernews.com/2026/07/public-github-issue-could-trick-github.html"
        },
        {
          "type": "research",
          "title": "MCP horror stories: the GitHub prompt injection data heist",
          "url": "https://www.docker.com/blog/mcp-horror-stories-github-prompt-injection/"
        },
        {
          "type": "research",
          "title": "AI GitHub agents: how one issue leaked private repos",
          "url": "https://www.upguard.com/blog/ai-github-agents-issue-leaked-private-repos"
        }
      ]
    },
    {
      "id": "app-installation-token-abuse",
      "plane": "application",
      "title": "GitHub App installation token abuse",
      "dateLabel": "Ongoing",
      "sortDate": "2022-01",
      "narrative": "An attacker holding the App private key, or landing on a runner that carries the installation, mints fresh short-lived tokens with scopes like contents:write and pull_requests:write, then reads private repos, pushes code, or opens malicious PRs. GHES also had an incorrect-authorization bug, fixed in 2022 (versions 3.3.17/3.4.12/3.5.9/3.6.5), where a scoped user-to-server token could escalate to full admin/owner.",
      "permissionsAbused": [
        "App private key theft",
        "contents:write",
        "pull_requests:write"
      ],
      "sources": [
        {
          "type": "research",
          "title": "What is GitHub App token abuse?",
          "url": "https://securelayer7.net/learn/credential-access/github-app-token-abuse"
        },
        {
          "type": "advisory",
          "title": "HackerOne #1732595: scoped user-to-server token escalates to org admin/owner",
          "url": "https://hackerone.com/reports/1732595"
        }
      ]
    },
    {
      "id": "org-pwn-requests",
      "plane": "organization",
      "title": "\"Pwn requests\": pull_request_target & workflow_run",
      "dateLabel": "Ongoing",
      "sortDate": "2021-04",
      "narrative": "The structural flaw behind most of the incidents on this page. pull_request deliberately withholds write permissions and secrets; pull_request_target was created to restore them, and becomes hazardous the moment maintainers treat it as a normal CI trigger. workflow_run carries the same risk and gets far less attention: the triggered workflow can access secrets and write tokens even when the workflow that triggered it could not. One industry scan found 38% of organizations have a workflow vulnerable to script injection or dangerous triggers. Since 8 December 2025, pull_request_target always runs the workflow file from the default branch and checks out the default branch, whatever branch the pull request targets. That closes attacks through an old, vulnerable workflow left on another branch. It does not help when the default branch workflow itself checks out and runs the pull request's code.",
      "permissionsAbused": [
        "pull_request_target write + secrets access",
        "workflow_run inherited privilege"
      ],
      "attribution": "Datadog",
      "sources": [
        {
          "type": "research",
          "title": "The case for GitHub Actions security after recent supply chain attacks",
          "url": "https://securitylabs.datadoghq.com/articles/case-for-github-actions-security/"
        },
        {
          "type": "research",
          "title": "Vulnerable GitHub Actions workflows: privilege escalation",
          "url": "https://www.legitsecurity.com/blog/github-privilege-escalation-vulnerability"
        },
        {
          "type": "advisory",
          "title": "Actions pull_request_target and environment branch protections changes",
          "url": "https://github.blog/changelog/2025-11-07-actions-pull_request_target-and-environment-branch-protections-changes/"
        },
        {
          "type": "research",
          "title": "Keeping your GitHub Actions and workflows secure: preventing pwn requests",
          "url": "https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/"
        }
      ]
    },
    {
      "id": "org-ultralytics",
      "plane": "organization",
      "title": "Ultralytics build cache poisoning",
      "dateLabel": "December 2024",
      "sortDate": "2024-12",
      "narrative": "Template injection via a crafted branch name against a format.yml workflow using pull_request_target. Secrets including secrets._GITHUB_TOKEN were exfiltrated and the build cache poisoned to inject a cryptominer into v8.3.41 and v8.3.42. The read from researchers was that the attackers had write access but preferred extracting CI/CD secrets, then used them to poison the cache and ship malicious releases.",
      "permissionsAbused": [
        "pull_request_target template injection",
        "secrets._GITHUB_TOKEN"
      ],
      "attribution": "GitGuardian",
      "sources": [
        {
          "type": "research",
          "title": "zizmor would have caught the Ultralytics workflow vulnerability",
          "url": "https://blog.yossarian.net/2024/12/06/zizmor-ultralytics-injection"
        },
        {
          "type": "research",
          "title": "Ultralytics AI pwn request supply chain attack",
          "url": "https://snyk.io/blog/ultralytics-ai-pwn-request-supply-chain-attack/"
        },
        {
          "type": "research",
          "title": "The Ultralytics supply chain attack: how it happened, how to prevent",
          "url": "https://www.legitsecurity.com/blog/the-ultralytics-supply-chain-attack-how-it-happened-how-to-prevent"
        },
        {
          "type": "tracker",
          "title": "Ultralytics AI library hacked via GitHub for cryptomining",
          "url": "https://www.wiz.io/blog/ultralytics-ai-library-hacked-via-github-for-cryptomining"
        },
        {
          "type": "research",
          "title": "The Ultralytics supply chain attack: connecting the dots",
          "url": "https://blog.gitguardian.com/the-ultralytics-supply-chain-attack-connecting-the-dots-with-gitguardians-public-monitoring-data/"
        }
      ]
    },
    {
      "id": "org-nx-s1ngularity",
      "plane": "organization",
      "title": "Nx \"s1ngularity\"",
      "dateLabel": "August 2025",
      "sortDate": "2025-08",
      "narrative": "The injected script extracted a read/write GitHub token, a consequence of the workflow permissions setting, then created a branch replacing the CI script, triggered the publish workflow against that branch via the API using workflow_dispatch, and deleted branches and workflow runs to clean up. When publish ran, it had NPM_TOKEN in scope and exfiltrated it. The PR targeted an outdated branch that still carried the vulnerable workflow after it had been reverted on master. In phase two, the stolen GitHub tokens were used to flip thousands of private repositories to public. Downstream, one threat-intel group rode the consequences to full AWS administrator access at a victim org in under 72 hours, via OIDC trust chains and a CloudFormation IAM capability chain. No zero-days were involved.",
      "permissionsAbused": [
        "Read/write GITHUB_TOKEN (permissive workflow default)",
        "actions:write (workflow_dispatch)",
        "NPM_TOKEN"
      ],
      "attribution": "Mandiant (UNC6426, downstream follow-on)",
      "sources": [
        {
          "type": "postmortem",
          "title": "s1ngularity: what happened, how we responded, what we learned",
          "url": "https://nx.dev/blog/s1ngularity-postmortem"
        },
        {
          "type": "research",
          "title": "Learnings from recent npm compromises",
          "url": "https://securitylabs.datadoghq.com/articles/learnings-from-recent-npm-compromises/"
        },
        {
          "type": "research",
          "title": "OIDC trust chain abuse: cloud takeover",
          "url": "https://labs.cloudsecurityalliance.org/research/csa-research-note-oidc-trust-chain-abuse-cloud-takeover-2026/"
        }
      ],
      "steps": [
        {
          "title": "An injected script extracts a read/write token",
          "detail": "A malicious script running in CI took advantage of a permissive workflow-level default and pulled out a GITHUB_TOKEN scoped for both read and write."
        },
        {
          "title": "The CI script itself is replaced on a new branch",
          "detail": "Using that token, the attacker created a new branch that swapped in a tampered version of the legitimate CI script."
        },
        {
          "title": "The publish workflow is triggered directly via the API",
          "detail": "Rather than waiting for a normal push, the attacker invoked workflow_dispatch through the GitHub API to run the publish workflow against their tampered branch — one that still carried a vulnerable workflow definition already reverted on master."
        },
        {
          "title": "NPM_TOKEN is exfiltrated during the tampered publish",
          "detail": "Because the publish workflow had NPM_TOKEN in scope, running it against the attacker's branch exfiltrated that token directly. The attacker then deleted the branch and workflow run history to reduce the trail."
        },
        {
          "title": "Phase two: stolen tokens flip repos public",
          "detail": "With the stolen GitHub tokens, the attacker flipped thousands of private repositories to public within a short window."
        },
        {
          "title": "Downstream: an OIDC trust chain reaches full AWS admin",
          "detail": "A separate threat-intel group rode the exposure to full AWS administrator access at a victim org in under 72 hours, chaining OIDC trust relationships with a CloudFormation IAM capability — no zero-day involved."
        }
      ],
      "mitigations": [
        {
          "action": "Set the default GITHUB_TOKEN permission to read-only at the org level",
          "why": "a compromised workflow can't write branches or trigger further jobs on its own"
        },
        {
          "action": "Require CODEOWNERS review on CI/CD script changes",
          "why": "a replaced build script can't merge unseen"
        },
        {
          "action": "Restrict who can trigger workflow_dispatch on publish/release workflows",
          "why": "limit it to a minimal, protected set of actors"
        },
        {
          "action": "Prevent branch and workflow-run deletion on protected branches",
          "why": "cleanup steps can't erase the trail"
        },
        {
          "action": "Migrate cloud credentials from static keys to OIDC federation scoped tightly to repo + branch",
          "why": "closes off the trust-chain pivot into cloud infrastructure"
        }
      ]
    },
    {
      "id": "org-self-hosted-runner-attacks",
      "plane": "organization",
      "title": "Self-hosted runner takeovers (PyTorch and others)",
      "dateLabel": "2023–2024",
      "sortDate": "2023-01",
      "narrative": "A fork PR makes you a \"previous contributor,\" after which workflows run on the self-hosted runner without approval; default runner setup is non-ephemeral, so access persists. On PyTorch, researchers confirmed root on the runners and pulled GITHUB_TOKENs with extensive write access out of .git/config during active workflows. Against GitHub's own actions/runner-images, the access reached internal macOS private cloud vCenter and Azure credentials, and could have poisoned every hosted runner base image. TensorFlow's fix was requiring approval for all fork PRs and setting GITHUB_TOKEN to read-only on self-hosted runners.",
      "permissionsAbused": [
        "Fork PR access to a non-ephemeral self-hosted runner",
        "GITHUB_TOKEN with write scope"
      ],
      "attribution": "Adnan Khan / John Stawinski",
      "sources": [
        {
          "type": "research",
          "title": "One supply chain attack to rule them all",
          "url": "https://adnanthekhan.com/2023/12/20/one-supply-chain-attack-to-rule-them-all/"
        },
        {
          "type": "research",
          "title": "TensorFlow supply chain compromise via self-hosted runner attack",
          "url": "https://www.praetorian.com/blog/tensorflow-supply-chain-compromise-via-self-hosted-runner-attack/"
        },
        {
          "type": "research",
          "title": "PyTorch supply chain compromise",
          "url": "https://www.stepsecurity.io/blog/pytorch-supply-chain-compromise"
        },
        {
          "type": "research",
          "title": "GitHub, PyTorch and more orgs vulnerable to self-hosted runner attacks",
          "url": "https://www.legitsecurity.com/blog/github-pytorch-and-more-organizations-found-vulnerable-to-self-hosted-runner-attacks"
        }
      ]
    },
    {
      "id": "org-ghostaction",
      "plane": "organization",
      "title": "GhostAction",
      "dateLabel": "September 2025, returned 2026",
      "sortDate": "2025-09",
      "narrative": "327 compromised GitHub user accounts were used to inject workflows disguised as \"Add GitHub Actions Security workflow\" across 817 repositories, stealing 3,325 secrets. The actors enumerated secret names from legitimate workflow files first, then hardcoded those names into their own workflow, harvesting PyPI, npm, DockerHub, GitHub, Cloudflare, and AWS credentials. It came back in 2026. GitGuardian found the malicious workflow in 772 public repositories belonging to 373 users and organizations between 31 August and 30 September 2026, aimed at 2,577 secrets; GitHub held most of the runs for approval, and by 5 October only 16% of the repositories had removed the workflow. From 7 October Socket saw more than 500 accounts committing it to tens of thousands of repositories.",
      "permissionsAbused": [
        "Injected Actions workflow",
        "Harvested repo/CI secrets by name"
      ],
      "sources": [
        {
          "type": "research",
          "title": "The GhostAction campaign: 3,325 secrets stolen",
          "url": "https://blog.gitguardian.com/ghostaction-campaign-3-325-secrets-stolen/"
        },
        {
          "type": "research",
          "title": "GhostAction returns: GitHub Actions supply chain attack",
          "url": "https://blog.gitguardian.com/ghostaction-github-actions-supply-chain-attack-returns/"
        },
        {
          "type": "tracker",
          "title": "GhostAction: cloud credentials targeted across tens of thousands of repositories",
          "url": "https://socket.dev/blog/ghostaction-cloud-credentials"
        },
        {
          "type": "tracker",
          "title": "GhostAction returns",
          "url": "https://www.stepsecurity.io/blog/ghostaction-returns"
        }
      ],
      "steps": [
        {
          "title": "327 GitHub accounts are compromised",
          "detail": "The campaign started from 327 already-compromised GitHub accounts, most likely via credential stuffing or reused leaked passwords rather than any GitHub platform vulnerability."
        },
        {
          "title": "Existing workflows are read to learn real secret names",
          "detail": "Before writing anything malicious, the actors read each target repository's own workflow files to learn exactly which secret names it referenced — PyPI, npm, DockerHub, GitHub, Cloudflare, AWS — instead of guessing."
        },
        {
          "title": "A convincingly-named workflow is injected",
          "detail": "Using the compromised accounts' write access, they committed a new workflow disguised as \"Add GitHub Actions Security workflow\" — a name designed to look like a routine hardening change — into 817 repositories."
        },
        {
          "title": "Secrets are harvested by their real names",
          "detail": "Because the injected workflow referenced the exact secret names it had just enumerated, every one of them resolved and was exfiltrated: 3,325 secrets in total, across six credential types."
        }
      ],
      "mitigations": [
        {
          "action": "Enforce MFA on every account, including bots/service accounts",
          "why": "compromised accounts, not a code vulnerability, were the entry point"
        },
        {
          "action": "Require CODEOWNERS review on any change under .github/workflows/",
          "why": "a new workflow file can't land unreviewed"
        },
        {
          "action": "Enable secret scanning and push protection",
          "why": "flags exfiltration attempts in an injected workflow"
        },
        {
          "action": "Inventory every Actions secret and its scope on a recurring basis",
          "why": "the campaign relied on being able to correctly guess names from existing workflows"
        },
        {
          "action": "Scope secrets to the narrowest repo/environment that actually needs them",
          "why": "instead of one org-wide secret reused everywhere"
        }
      ]
    },
    {
      "id": "org-toptal",
      "plane": "organization",
      "title": "Toptal repo-visibility flip and wiper",
      "dateLabel": "July 2025",
      "sortDate": "2025-07",
      "narrative": "73 repositories in the org went public within a five-minute window; at least 10 contained code to exfiltrate GitHub auth tokens and destroy victim systems. The payloads sat in preinstall/postinstall hooks, sending the token to a webhook endpoint then recursively deleting files on Windows and Linux. About 5,000 downloads occurred before removal. This was an org-owner compromise, and the very first action taken was flipping repo visibility.",
      "permissionsAbused": [
        "Org owner (bulk repo visibility change)",
        "npm pre/postinstall token exfiltration"
      ],
      "sources": [
        {
          "type": "research",
          "title": "Toptal's GitHub organization hijacked",
          "url": "https://socket.dev/blog/toptal-s-github-organization-hijacked-10-malicious-packages-published"
        },
        {
          "type": "tracker",
          "title": "Hackers breach Toptal GitHub account, publish malicious npm packages",
          "url": "https://www.bleepingcomputer.com/news/security/hackers-breach-toptal-github-account-publish-malicious-npm-packages/"
        }
      ]
    },
    {
      "id": "org-salesloft-drift",
      "plane": "organization",
      "title": "Salesloft Drift GitHub → AWS pivot",
      "dateLabel": "March – June 2025",
      "sortDate": "2025-03",
      "narrative": "Investigators found the threat actor UNC6395 in the Salesloft GitHub account from March through June 2025, downloading content from multiple repositories, adding a guest user, and establishing workflows. They then moved to Drift's AWS environment, took OAuth tokens for customer integrations, and used those to reach customer data. Downstream victims included Zscaler, Palo Alto Networks, Cloudflare, PagerDuty, and others. The interesting detail for the tier model: \"add a guest user\" and \"establish workflows\" are both persistence primitives that org admin grants.",
      "permissionsAbused": [
        "Org admin: add guest collaborator",
        "Org admin: establish workflows",
        "OAuth tokens for downstream integrations"
      ],
      "attribution": "Mandiant (UNC6395)",
      "sources": [
        {
          "type": "research",
          "title": "Anatomy of the Salesloft breach",
          "url": "https://permiso.io/blog/anatomy-of-the-salesloft-breach"
        },
        {
          "type": "tracker",
          "title": "GitHub account compromise led to Salesloft Drift breach",
          "url": "https://thehackernews.com/2025/09/github-account-compromise-led-to.html"
        }
      ]
    },
    {
      "id": "org-shai-hulud",
      "plane": "organization",
      "title": "Shai-Hulud self-propagating worm (1.0 & 2.0)",
      "dateLabel": "September 2025 / November 2025 / May 2026",
      "sortDate": "2025-09",
      "narrative": "The worm made GitHub permissions the propagation mechanism. It validated available GitHub tokens and their permissions, then queried /user/repos?affiliation=owner,collaborator,organization_member to find repos where the compromised account had enough privilege. It planted shai-hulud-workflow.yml, exfiltrated ${{ toJSON(secrets) }} to a webhook, and made repos public, including any the victim merely held tokens for. The 2.0 variant registered the infected host as a self-hosted Actions runner named SHA1HULUD and planted an injection-prone workflow at .github/workflows/discussion.yaml, so opening a GitHub Discussion gave arbitrary command execution on the host. 2.0 compromised 796 npm packages, with credential exfiltration confirmed across more than 500 GitHub users spanning 150+ organizations. A smaller descendant, known as Mini Shai-Hulud, hit in May 2026: on 19 May one developer account takeover led to more than 630 malicious versions across 317 npm packages in about 20 minutes, including AntV packages. The TanStack compromise a week earlier came through a different door, Actions cache poisoning (see the cache poisoning incident).",
      "permissionsAbused": [
        "Enumerated token permissions via /user/repos",
        "contents:write (public flip, workflow injection)",
        "Self-hosted runner registration (2.0)"
      ],
      "sources": [
        {
          "type": "research",
          "title": "Inside Shai-Hulud's maw: how the npm worm exploits and propagates",
          "url": "https://checkmarx.com/zero-post/inside-shai-huluds-maw-how-the-npm-worm-exploits-and-propagates/"
        },
        {
          "type": "research",
          "title": "Shai-Hulud: self-replicating worm compromises 500+ npm packages",
          "url": "https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised"
        },
        {
          "type": "tracker",
          "title": "Shai-Hulud 2.0: host registered as SHA1HULUD runner",
          "url": "https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/"
        },
        {
          "type": "tracker",
          "title": "Shai-Hulud npm supply chain attack",
          "url": "https://unit42.paloaltonetworks.com/npm-supply-chain-attack/"
        },
        {
          "type": "tracker",
          "title": "npm supply chain attack: GitHub API exploitation",
          "url": "https://www.trendmicro.com/en_us/research/25/i/npm-supply-chain-attack.html"
        },
        {
          "type": "tracker",
          "title": "Hackers have compromised dozens of popular open source packages in an ongoing supply chain attack",
          "url": "https://techcrunch.com/2026/05/19/hackers-have-compromised-dozens-of-popular-open-source-packages-in-an-ongoing-supply-chain-attack/"
        }
      ],
      "steps": [
        {
          "title": "Available GitHub tokens are validated and scoped",
          "detail": "After the worm reached a host — typically via a compromised npm package — its payload validated whatever GitHub tokens were reachable there and checked exactly what each one could do."
        },
        {
          "title": "Repos are enumerated by privilege, not just ownership",
          "detail": "It queried /user/repos?affiliation=owner,collaborator,organization_member to find every repository where the compromised account held enough privilege to act — far beyond repos it directly owned."
        },
        {
          "title": "A malicious workflow is planted and secrets exfiltrated",
          "detail": "It planted shai-hulud-workflow.yml in each reachable repo and exfiltrated the full secrets context (${{ toJSON(secrets) }}) to an external webhook."
        },
        {
          "title": "Repositories are flipped public to maximize spread",
          "detail": "It made repositories public, including ones the compromised account merely held a token for rather than owned outright, maximizing both exposure and further propagation surface."
        },
        {
          "title": "2.0: the infected host registers itself as a runner",
          "detail": "The follow-up variant registered the infected machine as a self-hosted Actions runner named SHA1HULUD, turning a single compromised developer machine into infrastructure the attacker could keep using."
        },
        {
          "title": "2.0: opening a GitHub Discussion becomes remote code execution",
          "detail": "It planted an injection-prone workflow at .github/workflows/discussion.yaml, so simply opening a GitHub Discussion on an affected repo triggered arbitrary command execution on that self-hosted runner — ultimately compromising 796 npm packages, with credential exfiltration confirmed across 500+ GitHub users spanning 150+ organizations."
        }
      ],
      "mitigations": [
        {
          "action": "Disallow self-hosted runners on public repositories",
          "why": "the 2.0 variant's persistence relied entirely on registering one"
        },
        {
          "action": "Prevent branch and workflow-run deletion",
          "why": "a propagating worm's cleanup steps can't erase the evidence trail"
        },
        {
          "action": "Set the default GITHUB_TOKEN permission to read-only at the org level",
          "why": "limits what any single compromised token can enumerate and write"
        },
        {
          "action": "Enable secret scanning and push protection",
          "why": "catches exfiltration attempts in an injected workflow"
        },
        {
          "action": "Audit and revoke stale PATs/SSH keys on a recurring basis",
          "why": "compromise of one maintainer's npm-side token was enough to start the chain"
        }
      ]
    },
    {
      "id": "org-hackerbot-claw",
      "plane": "organization",
      "title": "hackerbot-claw autonomous agent RCE",
      "dateLabel": "February 2026",
      "sortDate": "2026-02",
      "narrative": "An autonomous agent doing this at scale. It abused unsafe pull_request_target, unsanitized inputs, dynamic shell execution, and overprivileged GITHUB_TOKEN to get RCE on GitHub-hosted runners across at least six repositories, exfiltrating write-capable tokens in some cases. Targets included Trivy, avelino/awesome-go, microsoft/ai-discovery-agent, DataDog/datadog-iac-scanner, project-akri/akri, RustPython, and ambient-code/platform, where the vector was prompt injection via CLAUDE.md, which was blocked. On Trivy it stole a PAT through a pull_request_target workflow and used it to take over the repository.",
      "permissionsAbused": [
        "Unsafe pull_request_target",
        "Overprivileged GITHUB_TOKEN",
        "PAT exfiltration"
      ],
      "sources": [
        {
          "type": "research",
          "title": "hackerbot-claw: AI-powered bot exploiting GitHub Actions",
          "url": "https://www.stepsecurity.io/blog/hackerbot-claw-github-actions-exploitation"
        },
        {
          "type": "tracker",
          "title": "HackerBot-Claw campaign timeline",
          "url": "https://orca.security/resources/blog/hackerbot-claw-github-actions-attack/"
        }
      ],
      "steps": [
        {
          "title": "Public repositories are scanned at scale",
          "detail": "Over a single week (February 21–28, 2026), an autonomous, AI-driven bot systematically scanned public GitHub repositories for exploitable CI/CD workflow patterns, rather than hand-picking one victim."
        },
        {
          "title": "Vulnerable patterns are matched automatically",
          "detail": "The bot classified targets by which of five exploitation techniques applied — unsafe pull_request_target, unsanitized inputs, dynamic shell execution, or an overprivileged GITHUB_TOKEN among them — and applied the matching attack per repository."
        },
        {
          "title": "A crafted pull request gets code execution on a hosted runner",
          "detail": "Where pull_request_target combined with unsanitized input reaching a shell, a single PR was enough to achieve remote code execution on a GitHub-hosted runner in that repository's privileged context."
        },
        {
          "title": "Write-capable tokens are exfiltrated",
          "detail": "Running with an overprivileged GITHUB_TOKEN, the bot exfiltrated write-capable tokens from at least six repositories, including Trivy, awesome-go, and projects at Microsoft, DataDog, and the CNCF."
        },
        {
          "title": "A stolen token is reused for outright takeover",
          "detail": "On Trivy specifically, the exfiltrated PAT was reused to take over the repository. On another target, a prompt-injection attempt via CLAUDE.md was blocked before it could do damage."
        }
      ],
      "mitigations": [
        {
          "action": "Require approval for first-time/external contributor workflow runs on forks",
          "why": "a single PR can't auto-execute"
        },
        {
          "action": "Audit every use of pull_request_target",
          "why": "remove or restructure any that also check out and run the PR's own code"
        },
        {
          "action": "Set the default GITHUB_TOKEN permission to read-only at the org level"
        },
        {
          "action": "Sanitize any PR-controlled input (titles, branch names, file contents)",
          "why": "before it can reach a shell command"
        },
        {
          "action": "Treat repository files an autonomous coding agent might read (CLAUDE.md, AGENTS.md, etc.) as untrusted input",
          "why": "if such an agent runs against the repo"
        }
      ]
    },
    {
      "id": "org-teampcp",
      "plane": "organization",
      "title": "TeamPCP tag force-overwrite campaign",
      "dateLabel": "March – April 2026",
      "sortDate": "2026-03",
      "narrative": "The most complete demonstration of tag-write as a permission tier. On 19 March 2026 (CVE-2026-33634), continuing an attack that began in late February, the actors used credentials with tag-write privileges to publish Trivy v0.69.4 and force-overwrite 76 of 77 tags in aquasecurity/trivy-action and all seven in aquasecurity/setup-trivy, redirecting trusted versions to malicious commits with no visible change in release metadata. Checkmarx's KICS GitHub Action followed on 23 March, when all 35 of its tags were moved through a compromised service account, and on 24 March LiteLLM versions 1.82.7 and 1.82.8 were uploaded straight to PyPI with credentials taken from LiteLLM's own CI, where Trivy ran. Researchers traced at least seven waves through to @bitwarden/cli hijacked with stolen KICS npm tokens, and elementary-data via Actions script injection. Each compromised environment yielded the credentials for the next.",
      "permissionsAbused": [
        "Tag-write privilege (mass tag rewrite)",
        "Stolen PATs/npm tokens chained across projects"
      ],
      "attribution": "Trend Micro",
      "sources": [
        {
          "type": "advisory",
          "title": "CVE-2026-33634 (Trivy, trivy-action and setup-trivy)",
          "url": "https://nvd.nist.gov/vuln/detail/cve-2026-33634"
        },
        {
          "type": "postmortem",
          "title": "LiteLLM security update, March 2026",
          "url": "https://docs.litellm.ai/blog/security-update-march-2026"
        },
        {
          "type": "tracker",
          "title": "Detecting, investigating, defending against Trivy supply chain compromise",
          "url": "https://www.microsoft.com/en-us/security/blog/2026/03/24/detecting-investigating-defending-against-trivy-supply-chain-compromise/"
        },
        {
          "type": "tracker",
          "title": "TeamPCP: seven waves of credential chaining",
          "url": "https://www.trendmicro.com/en_us/research/26/e/analyzing-teampcp-supply-chain-attacks.html"
        },
        {
          "type": "tracker",
          "title": "TeamPCP isn't done",
          "url": "https://www.endorlabs.com/learn/teampcp-isnt-done"
        },
        {
          "type": "tracker",
          "title": "KICS GitHub Action compromised",
          "url": "https://www.wiz.io/blog/teampcp-attack-kics-github-action"
        },
        {
          "type": "tracker",
          "title": "TeamPCP campaign overview",
          "url": "https://arcticwolf.com/resources/blog/teampcp-supply-chain-attack-campaign-targets-trivy-checkmarx-kics-and-litellm-potential-downstream-impact-to-additional-projects/"
        }
      ],
      "steps": [
        {
          "title": "Tag-write privilege force-overwrites 76 of 77 trivy-action tags",
          "detail": "Using credentials with tag-write access, the actors force-overwrote almost every release tag in aquasecurity/trivy-action (76 of 77) and all seven tags in aquasecurity/setup-trivy, redirecting every consumer pinning by tag to malicious commits — with no visible change in release metadata."
        },
        {
          "title": "Checkmarx's KICS Action is compromised via stolen PATs",
          "detail": "Days later, on March 23, the actors used stolen personal access tokens to compromise Checkmarx's KICS GitHub Action the same way, alongside OpenVSX extensions and LiteLLM on PyPI."
        },
        {
          "title": "Stolen npm tokens chain into the next project",
          "detail": "Credentials harvested from the KICS compromise — specifically npm tokens — were reused to hijack @bitwarden/cli."
        },
        {
          "title": "A separate wave uses plain script injection",
          "detail": "elementary-data was compromised through ordinary Actions script injection, a different technique from the tag-overwrite chain used elsewhere in the campaign."
        },
        {
          "title": "Each compromised project funds the next wave",
          "detail": "Researchers traced at least seven distinct waves, with each compromise's stolen credentials chaining directly into the next target rather than the campaign relying on a single initial foothold."
        }
      ],
      "mitigations": [
        {
          "action": "Apply tag-protection rulesets",
          "why": "release tags can't be force-moved after publication"
        },
        {
          "action": "Pin third-party Actions to a full commit SHA rather than a tag",
          "why": "a retargeted upstream tag can't silently change what your workflow runs"
        },
        {
          "action": "Set an organization-wide maximum PAT lifetime and rotate on schedule",
          "why": "stolen long-lived PATs were the mechanism chaining each wave to the next"
        },
        {
          "action": "Audit and revoke stale PATs and npm tokens on a recurring basis"
        },
        {
          "action": "Write and rehearse a credential-rotation runbook",
          "why": "this class of campaign rewards whoever rotates slowest"
        }
      ]
    },
    {
      "id": "org-cache-poisoning",
      "plane": "organization",
      "title": "Actions cache poisoning (Angular, TanStack)",
      "dateLabel": "Ongoing",
      "sortDate": "2026-05",
      "narrative": "Actions caches are shared between PR workflows and release workflows. One researcher's angular/dev-infra chain was script injection via ${{ github.head_ref }} in a pull_request_target workflow, then cache filling with a purpose-built tool. TanStack saw 84 malicious versions across 42 @tanstack/* packages from the same class of chain on 11 May 2026: a pull_request_target workflow ran a fork's code, which poisoned the pnpm store in the shared cache, and the release workflow restored it and leaked its npm OIDC token. There is no CVE for any of this; the cache is behaving exactly as designed.",
      "permissionsAbused": [
        "Fork PR write into a cache shared with release workflows"
      ],
      "attribution": "Adnan Khan",
      "sources": [
        {
          "type": "postmortem",
          "title": "TanStack npm supply chain compromise postmortem",
          "url": "https://tanstack.com/blog/npm-supply-chain-compromise-postmortem"
        },
        {
          "type": "research",
          "title": "Cache poisoning through pull_request_target: the TanStack incident",
          "url": "https://safedep.io/tanstack-github-actions-cache-poisoning/"
        },
        {
          "type": "research",
          "title": "The cache that bites back: GitHub Actions cache poisoning attacks",
          "url": "https://hivesecurity.gitlab.io/blog/github-actions-cache-poisoning-supply-chain/"
        }
      ],
      "steps": [
        {
          "title": "A pull request opens the door",
          "detail": "An attacker opens an ordinary-looking PR against a workflow triggered on pull_request_target that checks out the PR's own head commit. That trigger runs with the base repository's secrets and cache access, even though the code being executed is the attacker's."
        },
        {
          "title": "The shared build cache gets poisoned",
          "detail": "During that run, the attacker's code writes a tampered dependency store into GitHub Actions' cache under the same key a legitimate release build would use — cache keys are derived from the lockfile hash and scoped to the whole repository, not to the branch or workflow that wrote them."
        },
        {
          "title": "A real release restores the poisoned entry",
          "detail": "Days or weeks later, a maintainer pushes to main and the publish workflow runs. It restores the cache entry with the matching key — the poisoned one — and installs from it, with no indication anything is wrong."
        },
        {
          "title": "The publishing token is extracted from memory",
          "detail": "The tampered install code executes inside the trusted release job and dumps runtime memory, recovering the OIDC token the workflow had just obtained to authenticate to the npm registry."
        },
        {
          "title": "84 malicious versions ship before detection",
          "detail": "Using the stolen token, the attacker publishes 84 tampered versions across 42 @tanstack/* packages — including Query, Router, and Form, each with millions of weekly downloads — before the versions are caught and yanked."
        }
      ],
      "mitigations": [
        {
          "action": "Audit every pull_request_target workflow",
          "why": "remove or isolate any that also check out and run the PR's own code"
        },
        {
          "action": "Pin actions/cache, actions/checkout, and every third-party action to a full commit SHA",
          "why": "not a mutable tag"
        },
        {
          "action": "Separate cache scopes for PR workflows and release workflows",
          "why": "distinct key prefixes (e.g. pr- vs release-) so a fork PR can never write into a key a release job will later restore"
        },
        {
          "action": "Restrict which workflows can request an OIDC publishing token",
          "why": "using environment protection rules"
        },
        {
          "action": "Disable caching entirely in release/publish workflows and install fresh every time",
          "why": "the cold-install cost is negligible next to the exposure"
        }
      ]
    },
    {
      "id": "org-prt-scan",
      "plane": "organization",
      "title": "prt-scan: automated pull_request_target attacks",
      "dateLabel": "March – April 2026",
      "sortDate": "2026-03",
      "narrative": "The second AI-assisted campaign of 2026 against pull_request_target, after hackerbot-claw. Wiz traced more than 500 malicious pull requests in six waves from six accounts, starting on 11 March 2026. The largest burst came from one account that opened more than 475 pull requests in 26 hours from 2 April. Early waves used crude shell scripts; later ones used AI-generated payloads written for each repository's language, including a stage that tried to get past label-gated workflows. Fewer than one in ten attempts worked, but at least two npm packages were published with the stolen credentials. The attacker never needed access to a target repository, only a fork and a workflow that ran its code with secrets.",
      "permissionsAbused": [
        "pull_request_target write token + secrets",
        "Fork pull request (no access to the target needed)"
      ],
      "attribution": "Wiz Research",
      "sources": [
        {
          "type": "research",
          "title": "Six accounts, one actor: inside the prt-scan supply chain campaign",
          "url": "https://www.wiz.io/blog/six-accounts-one-actor-inside-the-prt-scan-supply-chain-campaign"
        },
        {
          "type": "research",
          "title": "prt-scan: GitHub Actions exfiltration campaign",
          "url": "https://safedep.io/prt-scan-github-actions-exfiltration-campaign/"
        },
        {
          "type": "tracker",
          "title": "CSA research note: GitHub Actions prt-scan supply chain campaign",
          "url": "https://labs.cloudsecurityalliance.org/research/csa-research-note-github-actions-prt-scan-supply-chain-2026/"
        },
        {
          "type": "tracker",
          "title": "AI-assisted supply chain attack targets GitHub",
          "url": "https://darkreading.com/application-security/ai-assisted-supply-chain-attack-targets-github"
        }
      ],
      "steps": [
        {
          "title": "Vulnerable workflows are found by scanning",
          "detail": "The attacker searched public GitHub for workflows triggered on pull_request_target that check out or run code from the pull request."
        },
        {
          "title": "Pull requests are opened in bulk",
          "detail": "Each pull request came from a branch named prt-scan- plus 12 hex characters, titled \"ci: update build configuration\". One account opened more than 475 of them in about a day."
        },
        {
          "title": "The payload adapts to the target",
          "detail": "Later waves generated payloads in the repository's own language and tried to get around label-gated workflows, so a common piece of hardening was no longer enough."
        },
        {
          "title": "Secrets go out, packages get published",
          "detail": "Where the workflow ran the code, the payload took the workflow's secrets. At least two npm packages were then published with them."
        }
      ],
      "mitigations": [
        {
          "action": "Audit every pull_request_target and workflow_run workflow",
          "why": "the campaign only worked where these ran code from the pull request"
        },
        {
          "action": "Set the default GITHUB_TOKEN to read-only",
          "why": "limits what a successful run can change"
        },
        {
          "action": "Require approval for workflow runs from forks and new contributors",
          "why": "the accounts were new and had never contributed"
        },
        {
          "action": "Search your repositories for prt-scan- branches and the PR title",
          "why": "the indicators are specific and easy to find"
        }
      ]
    },
    {
      "id": "org-megalodon",
      "plane": "organization",
      "title": "Megalodon: mass backdooring of CI workflows",
      "dateLabel": "May 2026",
      "sortDate": "2026-05",
      "narrative": "On 18 May 2026, in about six hours, throwaway accounts pushed 5,718 commits into 5,561 public repositories, straight to the default branch and without pull requests. The commits claimed to come from bots such as build-bot and ci-bot, but no real account was linked to those names. A mass variant added a workflow that ran on push and pull_request_target and sent secrets out. A targeted variant replaced existing workflows with one that only runs on workflow_dispatch, a dormant backdoor the attacker can fire later through the API. At least one project, Tiledesk, then published npm versions built from the poisoned source; the npm account itself was never touched. How the attacker got push access to so many repositories is not established in the primary write-ups.",
      "permissionsAbused": [
        "Push to the default branch without review",
        "workflows: write (new and replaced workflows)"
      ],
      "attribution": "SafeDep, StepSecurity",
      "sources": [
        {
          "type": "research",
          "title": "Megalodon: mass GitHub repo backdooring via CI workflows",
          "url": "https://safedep.io/megalodon-mass-github-repo-backdooring-ci-workflows/"
        },
        {
          "type": "research",
          "title": "Megalodon: mass GitHub Actions secret exfiltration across 5,500 public repositories",
          "url": "https://stepsecurity.io/blog/megalodon-mass-github-actions-secret-exfiltration-across-5-500-public-repositories"
        }
      ],
      "steps": [
        {
          "title": "Push access is obtained (how is unknown)",
          "detail": "The commits were pushed directly to default branches. A stolen token or deploy key is the likely explanation, but neither write-up confirms it."
        },
        {
          "title": "Commits pose as CI bots",
          "detail": "The author name was set to things like build-bot, auto-ci or pipeline-bot. Git lets anyone write any author name; without signed commits nothing tells them apart from a real bot."
        },
        {
          "title": "A workflow steals secrets on the next run",
          "detail": "The mass variant added a workflow triggered on push and pull_request_target that exfiltrated the repository's secrets."
        },
        {
          "title": "A dormant workflow is left for later",
          "detail": "The targeted variant replaced existing workflows with one that only runs on workflow_dispatch, so it does nothing until the attacker triggers it."
        },
        {
          "title": "A package ships from the poisoned source",
          "detail": "Tiledesk's maintainer published several npm versions from the backdoored repository before it was noticed."
        }
      ],
      "mitigations": [
        {
          "action": "Protect the default branch with a ruleset that requires a pull request",
          "why": "every commit went straight to the default branch"
        },
        {
          "action": "Require code owner review for .github/workflows",
          "why": "a new or replaced workflow can't land on its own"
        },
        {
          "action": "Require signed commits",
          "why": "forged bot author names stand out"
        },
        {
          "action": "Alert on new workflows and unexpected workflow_dispatch runs",
          "why": "the dormant variant only shows itself when fired"
        },
        {
          "action": "Restrict classic PATs and set a maximum token lifetime",
          "why": "limits what one stolen token can push to"
        }
      ]
    },
    {
      "id": "org-chaindrop",
      "plane": "organization",
      "title": "ChainDrop: a hijacked maintainer account publishes through trusted publishing",
      "dateLabel": "August 2026",
      "sortDate": "2026-08",
      "narrative": "On 4 August 2026 an attacker used the GitHub account of the maintainer of keyv and cacheable, packages with well over 100 million weekly npm downloads together. The commits went straight to main, unsigned, which StepSecurity reads as a stolen token or session used through the API; how the account was taken is not known. The attacker then cut releases, and the projects' own release workflows published the malware through npm trusted publishing, with valid provenance. No npm token was stolen for this part. The payload, a descendant of Shai-Hulud 2.0, steals npm and GitHub tokens, cloud keys and SSH keys from developer machines and CI runners, publishes itself to every package the victim can publish, and in victims' repositories adds a workflow that dumps the secrets context, then deletes the branch. It also writes .vscode/tasks.json and .claude/settings.json into repositories to run again later. StepSecurity counted 2,212 versions of 444 packages within about four hours. Provenance showed which commit was built, not whether anyone should have pushed it.",
      "permissionsAbused": [
        "contents:write (push to main, create release tags)",
        "workflows:write (secrets-dumping workflow in victims' repositories)",
        "The project's own trusted publishing workflow"
      ],
      "attribution": "StepSecurity",
      "sources": [
        {
          "type": "research",
          "title": "ChainDrop npm worm: Bun-loaded CI/CD credential harvester with Ethereum dead-drop C2",
          "url": "https://www.stepsecurity.io/blog/chaindrop-npm-worm"
        },
        {
          "type": "tracker",
          "title": "ChainDrop: the Keyv and Cacheable npm supply chain attack",
          "url": "https://insights.integrity360.com/threat-advisories/chaindrop-the-keyv-and-cacheable-npm-supply-chain-attack"
        }
      ],
      "steps": [
        {
          "title": "The maintainer's GitHub account is used",
          "detail": "Unsigned commits appear on main of keyv, cacheable and related repositories under the maintainer's name. How the attacker got the account or token is not established."
        },
        {
          "title": "A release runs the real publish workflow",
          "detail": "The attacker creates releases, and each project's release workflow publishes to npm through trusted publishing. The packages carry valid provenance because the workflow really did build them."
        },
        {
          "title": "Installs steal credentials",
          "detail": "A preinstall script downloads the Bun runtime and runs the payload, which collects npm and GitHub tokens, cloud credentials and SSH keys from the machine or runner."
        },
        {
          "title": "The worm spreads and digs in",
          "detail": "With the stolen tokens it publishes infected versions of the victim's own packages, adds a workflow that writes the secrets context to an artifact and then removes the branch, and plants editor and agent config files that run it again."
        }
      ],
      "mitigations": [
        {
          "action": "Require a pull request on the default branch, with no bypass for maintainers",
          "why": "every malicious commit went straight to main"
        },
        {
          "action": "Restrict who can create release tags with a ruleset",
          "why": "a release was all it took to start the publish workflow"
        },
        {
          "action": "Put the publish job behind an environment with required reviewers",
          "why": "trusted publishing ran for whoever triggered it"
        },
        {
          "action": "Require signed commits",
          "why": "the attacker's commits were unsigned"
        },
        {
          "action": "Use security keys or passkeys for maintainer accounts",
          "why": "the maintainer's account was the single point of failure"
        }
      ]
    },
    {
      "id": "ent-cve-2024-4985",
      "plane": "enterprise",
      "title": "CVE-2024-4985: SAML auth bypass",
      "dateLabel": "Fixed 2024",
      "sortDate": "2024-05",
      "narrative": "Rated CVSS 10.0. With SAML SSO and encrypted assertions enabled, an attacker could forge a SAML response to provision or access a site administrator account, with no prior authentication required. All GHES versions before 3.13.0 were affected; fixed in 3.9.15, 3.10.12, 3.11.10, and 3.12.4. Site admin means every repository, secret, and CI/CD pipeline on the instance. A single forged assertion reaches Tier 0 with zero credentials.",
      "permissionsAbused": [
        "Forged SAML assertion (pre-authentication)"
      ],
      "sources": [
        {
          "type": "advisory",
          "title": "GHSA-5pw9-f9r4-mv2r: CVE-2024-4985, SAML forge to site admin",
          "url": "https://github.com/advisories/GHSA-5pw9-f9r4-mv2r"
        },
        {
          "type": "tracker",
          "title": "GitHub fixes maximum severity GHES auth bypass",
          "url": "https://www.helpnetsecurity.com/2024/05/23/cve-2024-4985/"
        }
      ]
    },
    {
      "id": "ent-cve-2025-11578",
      "plane": "enterprise",
      "title": "CVE-2025-11578: pre-receive hook symlink escape",
      "dateLabel": "Fixed 2025",
      "sortDate": "2025-10",
      "narrative": "An authenticated enterprise admin could escape via symlink in pre-receive hook environments, replace system binaries during hook cleanup, and add an SSH key to root's authorized_keys, turning enterprise-admin access into root on the appliance itself.",
      "permissionsAbused": [
        "Enterprise admin → root via symlink escape"
      ],
      "sources": [
        {
          "type": "advisory",
          "title": "CVE-2025-11578: enterprise admin to root via pre-receive symlink escape",
          "url": "https://github.com/advisories?query=CVE-2025-11578"
        }
      ]
    },
    {
      "id": "ent-cve-2025-3509",
      "plane": "enterprise",
      "title": "CVE-2025-3509: pre-receive hook RCE via port reuse",
      "dateLabel": "Fixed 2025",
      "sortDate": "2025-04",
      "narrative": "Pre-receive hook RCE via dynamically allocated ports available during operations such as hot-patch upgrades, requiring either site admin or a user able to modify repos with hooks already enabled.",
      "permissionsAbused": [
        "Site admin, or repo-hook-modify access, during a hot-patch window"
      ],
      "sources": [
        {
          "type": "advisory",
          "title": "GHSA-g3x6-r9w9-mcxx: CVE-2025-3509, pre-receive hook RCE",
          "url": "https://github.com/advisories/GHSA-g3x6-r9w9-mcxx"
        }
      ]
    },
    {
      "id": "ent-cve-2026-3854",
      "plane": "enterprise",
      "title": "CVE-2026-3854: push-option metadata RCE",
      "dateLabel": "Fixed 2026",
      "sortDate": "2026-03",
      "narrative": "Rated CVSS 8.7. A low-privileged user with push access to any repository, including one they created themselves, could inject metadata fields through crafted git push option values and achieve RCE on the instance. Notable because the starting privilege required is minimal: ordinary repo push, not admin.",
      "permissionsAbused": [
        "Repo push access (any repo, including self-created)"
      ],
      "sources": [
        {
          "type": "advisory",
          "title": "GHSA-64fw-jx9p-5j24: CVE-2026-3854, push option injection to RCE",
          "url": "https://github.com/advisories/GHSA-64fw-jx9p-5j24"
        },
        {
          "type": "tracker",
          "title": "GHES vulnerability: find impacted assets",
          "url": "https://www.runzero.com/blog/github-enterprise-server/"
        }
      ]
    },
    {
      "id": "ent-cve-2024-1374",
      "plane": "enterprise",
      "title": "CVE-2024-1374: Management Console template injection",
      "dateLabel": "Fixed 2024",
      "sortDate": "2024-02",
      "narrative": "A Management Console editor reached root SSH on the appliance via Nomad template injection in the audit log forwarding configuration, a mid-tier admin console role escalating straight to host-level control.",
      "permissionsAbused": [
        "Management Console editor role → root"
      ],
      "sources": [
        {
          "type": "advisory",
          "title": "HackerOne #2332623: editor to root SSH via nomad template injection (CVE-2024-1374)",
          "url": "https://hackerone.com/reports/2332623"
        }
      ]
    },
    {
      "id": "ent-cve-2022-23732",
      "plane": "enterprise",
      "title": "CVE-2022-23732: Management Console path traversal / CSRF bypass",
      "dateLabel": "Fixed 2022",
      "sortDate": "2022-01",
      "narrative": "A Management Console path traversal vulnerability bypassed CSRF protections, leading to privilege escalation on the appliance.",
      "permissionsAbused": [
        "Management Console path traversal bypassing CSRF"
      ],
      "sources": [
        {
          "type": "advisory",
          "title": "CVE-2022-23732: Management Console path traversal, CSRF bypass",
          "url": "https://github.com/advisories?query=CVE-2022-23732"
        }
      ]
    }
  ],
  "escalationPrimitives": [
    {
      "primitive": "Force-repoint version tags",
      "permission": "contents:write on an Action repo",
      "seenIn": "tj-actions, TeamPCP"
    },
    {
      "primitive": "Inject a workflow file",
      "permission": "workflow scope / contents:write",
      "seenIn": "Dependabot spoof, GhostAction, Shai-Hulud, Salesloft"
    },
    {
      "primitive": "Flip repo visibility to public",
      "permission": "org owner / repo admin",
      "seenIn": "Toptal, Nx phase 2, Shai-Hulud"
    },
    {
      "primitive": "Steal GITHUB_TOKEN from runner memory or .git/config",
      "permission": "any RCE on a runner",
      "seenIn": "tj-actions, PyTorch, ArtiPACKED"
    },
    {
      "primitive": "workflow_dispatch a privileged publish job",
      "permission": "actions:write",
      "seenIn": "Nx"
    },
    {
      "primitive": "Register a self-hosted runner",
      "permission": "admin:org / repo admin",
      "seenIn": "Shai-Hulud 2.0"
    },
    {
      "primitive": "Non-ephemeral runner persistence",
      "permission": "runner config default",
      "seenIn": "PyTorch, TensorFlow"
    },
    {
      "primitive": "Poison the shared Actions cache",
      "permission": "fork PR write into shared cache",
      "seenIn": "Ultralytics, Angular, TanStack"
    },
    {
      "primitive": "Add a guest/outside collaborator",
      "permission": "org owner",
      "seenIn": "Salesloft"
    },
    {
      "primitive": "Forge a SAML assertion",
      "permission": "none (pre-auth)",
      "seenIn": "CVE-2024-4985"
    }
  ]
}
