Supply chain attack alert: .github/setup.js

Our org GitHub just got compromised massively by a supply-chain attack. Vectors are

* Claude hooks

* Gemini hooks

* Cursor setup

* VScode tasks

It adds all of the above to execute node .github/setup.js, an obfuscated file.

Check infected: `rg --hidden --no-ignore 'node .github/setup.js`

It spreads by adding mimic'd skip-ci commits to open PRs which then get merged.

Payload is obfuscated, available on request.

If this is already a known one in the world, apologies, it hit us at around 10PM BST last night, the damage would have been incredible.

Still trying to identify the original source.

24 points | by antihero 2 days ago

6 comments

  • icflorescu 23 hours ago
    OSS maintainer, @icflorescu on GitHub, owner of a few affected repos here.

    My full analysis and IOCs: https://dev.to/icflorescu/the-bot-that-never-was-2mfp (source repo on codeberg: https://codeberg.org/icflorescu/miasma-github-incident)

  • gionn 2 days ago
    Made a quick script to find affected repos/branches and optionally wipe the commits which contains malware: https://github.com/gionn/malware-cleanup/blob/master/scanner...
    • prathje 1 day ago
      Thanks a lot, Gionn, for also scanning and raising issues on this matter!
      • gionn 1 day ago
        always glad to help!
  • christeamrs 1 day ago
    FYI we released a discovery and mitigation tool today: https://github.com/Team-Rockstars-Security/antimiasma
  • antihero 2 days ago
    Attack is called "Hades - The End for the Damned", it exfiltrates secrets including ALL ORG GITHUB ACTIONS SECRETS via creating compromised actions, through GitHub public repos with encrypted payloads.
    • nhecker 2 days ago
      If you're saying it only impacts public repos, I don't think that's quite right. It appears to impact private ones as well. Source: first-hand experience. If you're claiming that the only export vector is via public repos then I can't refute that. But just trying to clarify here.

      And after a quick glance I'm not seeing any correlation between "Hades - The End for the Dammed" and this worm; would also love a source for this claim.

      • antihero 2 days ago
        Nope, the public repos are what the on-machine payload creates. Sorry, I worded that wrong, I meant it exfiltrates to.

        The main attack is using compromised repo keys to:

        * Create malicious actions to JSON dump and exfiltrate all GitHub org secrets.

        * Commit the payload delivering hooks/scripts to any repo/PR it has access to.

        * Mimics previous commits/timestamps, however you can see the key that did it by seeing the push in activity/audit logs.

      • thejaybird 2 days ago
        Take the JS file and decode it!

        Decoded execution chain ----------------------- 1. Outer layer: - Starts with try{eval(function(s,n){...})([large numeric array].map(...).join(""),1)) - Converts numeric character codes into a string. - Applies a Caesar +1 shift to alphabetic characters. - eval() executes the decoded layer.

        2. First decoded layer: - Imports node:crypto. - Defines an AES-128-GCM decryptor. - Decrypts two embedded payloads: a) _b: small Bun bootstrap/loader b) _p: large obfuscated payload (~686 KB) - Writes _p to a temporary JS file under /tmp/p<random>.js. - If Bun is available, runs: bun run "<temp file>" - If Bun is unavailable, downloads Bun from GitHub releases and then runs the payload.

        3. Small loader payload: - Downloads Bun v1.3.13 from: https://github.com/oven-sh/bun/releases/download/bun-v1.3.13... - Uses curl and unzip. - Creates temporary directories under /tmp/b-* - Runs the large payload using Bun.

        4. Large payload: - Obfuscated JavaScript with a string-table decoder and a second custom encrypted string layer. - After decoding strings, the payload clearly contains credential and secret collection logic.

        Observed behaviour / capabilities --------------------------------- The payload appears to collect or search for: - GitHub tokens / PATs / GitHub Actions OIDC tokens - npm tokens and npm OIDC package exchange tokens - RubyGems API keys - AWS credentials, STS metadata credentials and Secrets Manager secrets - Azure credentials, service principals and Key Vault secrets - GCP service account tokens and Secret Manager secrets - Vault tokens and Vault secrets - Kubernetes service account tokens and kubeconfig - Docker credentials - SSH keys and config - Git credentials - .env files and common project secrets - Claude configuration and project files - OnePassword items via the op CLI - Slack, Discord, Signal, Telegram and Element local data - Crypto wallet files such as Exodus, Ledger Live, Ethereum keystores and Monero data - Shell and database history files

        Exfiltration / persistence style -------------------------------- The payload contains logic to: - Create or use GitHub repositories through GitHub API endpoints. - Commit collected data/content into repository files. - Add/update files such as: - .vscode/tasks.json - .claude/index.js - .claude/settings.json - .claude/setup.mjs - .vscode/setup.mjs - Use commit messages such as: - chore: update dependencies - create del-commit: - Create a GitHub repo with description: - Hades - The End for the Damned - Use api.anthropic.com with path v1/api as an apparent outbound endpoint. - Use a token/string: - IfYouYankThisTokenItWillNukeTheComputerOfTheOwnerFully

        Notable URLs / endpoints ------------------------ - https://api.github.com - https://api.github.com/graphql - https://github.com/ - https://github.com/actions/runner - https://github.com/oven-sh/bun/releases/download/bun-v1.3.13... - https://registry.npmjs.org/ - https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/pack... - https://registry.npmjs.org/-/npm/v1/tokens - https://registry.npmjs.org/-/whoami - https://rubygems.org/api/v1/api_key.json - https://rubygems.org/api/v1/gems - https://cloudresourcemanager.googleapis.com/v1 - https://secretmanager.googleapis.com/v1 - https://graph.microsoft.com/v1.0/me - https://login.microsoftonline.com/ - https://vault.azure.net/.default - http://169.254.169.254/latest/api/token - http://169.254.169.254/latest/meta-data/iam/security-credent... - http://169.254.170.2 - http://127.0.0.1:8200 - api.anthropic.com / v1/api

        Files/globs targeted -------------------- - */.env - */.env.local - */.env.production - */.git/config - */config/database.yml - */wp-config.php - .env - .git-credentials - .npmrc - ~/.npmrc - ~/.pypirc - ~/.yarnrc - ~/.aws/config - ~/.aws/credentials - ~/.azure/accessTokens.json - ~/.azure/msal_token_cache.* - ~/.config/gcloud/access_tokens.db - ~/.config/gcloud/application_default_credentials.json - ~/.config/gcloud/credentials.db - ~/.docker/config.json - ~/.docker//config.json - /root/.docker/config.json - ~/.kube/config - /etc/rancher/k3s/k3s.yaml - /var/run/secrets/kubernetes.io/serviceaccount/token - ~/.terraform.d/credentials.tfrc.json - ~/.ssh/id - ~/.ssh/id_rsa - ~/.ssh/id_ed25519 - ~/.ssh/id_ecdsa - ~/.ssh/config - ~/.ssh/known_hosts - ~/.gitconfig - ~/.config/git/credentials - ~/.netrc - ~/.bash_history - ~/.zsh_history - ~/.python_history - ~/.psql_history - ~/.mysql_history - ~/.claude.json - ~/.claude/* - ~/.claude/projects/* - ~/.claude/mcp.json - %USERPROFILE%\.claude.json - %USERPROFILE%\.claude* - ~/.config/Slack/Cookies - ~/.config/discord/Local Storage/leveldb/* - ~/.config/Signal/* - ~/.local/share/TelegramDesktop/tdata/* - ~/.config/Element/Local Storage/* - ~/.config/Exodus/exodus.wallet/* - ~/.config/Ledger Live/* - ~/.ethereum/keystore/* - ~/.monero/* - ~/.local/share/keyrings/.keyring - ~/.kde/share/apps/kwallet/.kwl - ~/.config/filezilla/sitemanager.xml - ~/.config/filezilla/recentservers.xml

        Environment variables checked ----------------------------- - GITHUB_ACTIONS - GITHUB_REPOSITORY - GITHUB_REF - ACTIONS_ID_TOKEN_REQUEST_URL - ACTIONS_ID_TOKEN_REQUEST_TOKEN - TARGET_REPOS - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN - AWS_PROFILE - AWS_REGION - AWS_DEFAULT_REGION - AWS_WEB_IDENTITY_TOKEN_FILE - AWS_CONTAINER_CREDENTIALS_RELATIVE_URI - AWS_CONTAINER_CREDENTIALS_FULL_URI - AWS_CONTAINER_AUTHORIZATION_TOKEN - AZURE_TENANT_ID - AZURE_CLIENT_ID - AZURE_CLIENT_SECRET - AZURE_FEDERATED_TOKEN_FILE - AZURE_KEY_VAULT_NAME - GOOGLE_APPLICATION_CREDENTIALS - GOOGLE_CLOUD_PROJECT - GCP_PROJECT - GCLOUD_PROJECT - KUBECONFIG - VAULT_ADDR - VAULT_API_TOKEN - VAULT_TOKEN_FILE - VAULT_TOKEN_PATH - VAULT_AWS_ROLE - VAULT_ROLE - HOME - USERPROFILE - LANG / LANGUAGE / LC_ALL / LC_MESSAGES (edited)

    • nhecker 2 days ago
      https://safedep.io/miasma-worm-ai-coding-agent-config-inject... seems to describe the same symptoms, for what it's worth.
  • thejaybird 2 days ago
    For me i feel the attack vector is

    Public repo > infect by merge > github runner picks up and gets infected > and github action (from a repo) that then runs on runner getw effected

  • nikita2206 2 days ago
    Did you dig anything on where it came from for you? Some NPM package?
    • antihero 2 days ago
      We're trying to figure this out, we've nailed it to the developer machine but not sure how that machine got infected. Possibly through GHA.

Data from the public Hacker News API

UI based on nuxt/hackernews under the MIT License. Copyright © Yuxi (Evan) You & Nuxt core team