{"id":4285,"date":"2026-11-14T10:57:27","date_gmt":"2026-11-14T18:57:27","guid":{"rendered":"https:\/\/cyber.mag212.com\/wfx-github-accounts-ci-secret-stealer-bee-js-npm\/"},"modified":"2026-11-14T10:57:27","modified_gmt":"2026-11-14T18:57:27","slug":"wfx-github-accounts-ci-secret-stealer-bee-js-npm","status":"publish","type":"post","link":"https:\/\/cyber.mag212.com\/fr\/wfx-github-accounts-ci-secret-stealer-bee-js-npm\/","title":{"rendered":"wfx: Hijacked GitHub Accounts Turn CI Into a Secret Stealer, and bee-js Shows Provenance Is Not Safety"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>From the <a href=\"https:\/\/cyber.mag212.com\/the-keys-were-already-inside-mag212-cyber-weekly-november-14-2026\/\">MAG212 CYBER Weekly, 14 November 2026<\/a>. Status labels: confirmed by vendor, reported by researchers, or unverified. Every command below is copied from official documentation and was <strong>not tested by MAG212<\/strong>; run it only on systems you are authorised to administer.<\/em><\/p>\n\n\n\n<p class=\"m2-status wp-block-paragraph\"><strong>Status:<\/strong> <strong>Reported by researchers<\/strong> (Endor Labs, published 12 Nov, updated 13 Nov) <sup><a href=\"#src-36\">[36]<\/a><\/sup>. BleepingComputer&#8217;s headline corroborates the scale, but MAG212 did not fetch the article <sup><a href=\"#src-37\">[37]<\/a><\/sup>. No official GitHub response was found. bee-js: maintainers&#8217; GitHub advisory GHSA-hh7j-5m3j-23pf exists, and the details come from a <strong>secondary aggregation<\/strong> of Socket, OX, SafeDep and StepSecurity research <sup><a href=\"#src-38\">[38]<\/a><\/sup>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What happened \u2014 wfx<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">From 10 November, attackers used hijacked GitHub accounts to slip a short-lived secret stealer into repositories. Endor Labs counts <sup><a href=\"#src-36\">[36]<\/a><\/sup>:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why this matters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">wfx did not need a new vulnerability. It used real maintainers&#x27; accounts and normal pull-request workflows to reach CI secrets, then created cloud keys for persistence. bee-js showed that a package with genuine provenance can still ship malware. Pipeline permissions and secret scope are now front-line controls. <em>(MAG212 analysis)<\/em><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/cyber.mag212.com\/wp-content\/uploads\/2026\/11\/wfx-github-accounts-ci-secret-stealer-bee-js-npm-diagram.png\"><img decoding=\"async\" src=\"https:\/\/cyber.mag212.com\/wp-content\/uploads\/2026\/11\/wfx-github-accounts-ci-secret-stealer-bee-js-npm-diagram.png\" alt=\"Swim-lane diagram of the wfx campaign: a hijacked GitHub account opens a probe pull request, commits and reverts a payload, steals CI secrets to exfiltration servers, and creates AWS keys and Azure federated credentials, with an inset on the bee-js npm compromise.\"\/><\/a><figcaption class=\"wp-element-caption\">Diagram: MAG212 CYBER. Solid red = observed or vendor-confirmed; dashed amber = reported or unverified; green = recommended control.<\/figcaption><\/figure>\n\n\n\n<ul class=\"wp-block-list\">\n<li>about <strong>61,764<\/strong> repos probed<\/li>\n\n<li>at least <strong>1,806<\/strong> with credentials exfiltrated<\/li>\n\n<li><strong>8,926<\/strong> with payload evidence<\/li>\n\n<li>more than 829 with npm <code>postinstall<\/code> hooks<\/li>\n\n<li>at least <strong>50<\/strong> hijacked accounts that publish to npm, Go, Packagist, PyPI or crates<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Victims include <code>nextcloud\/docker<\/code> and 20 <code>webdriverio<\/code> repositories. Malicious package versions were published to Packagist (<code>digitalelvis\/neuronai-studio<\/code> 3.14.3) and Go (<code>github.com\/onuragtas\/docker-env<\/code> v1.0.278 and v1.0.280) <sup><a href=\"#src-36\">[36]<\/a><\/sup>. BleepingComputer&#8217;s headline puts it at <strong>4,800 GitHub accounts<\/strong>, matching Endor&#8217;s count of about 4,837 developers&#8217; credentials stolen <sup><a href=\"#src-36\">[36]<\/a><\/sup><sup><a href=\"#src-37\">[37]<\/a><\/sup>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What happened \u2014 bee-js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On 9 November, <code>@ethersphere\/bee-js<\/code> 13.3.0 sat on npm <code>latest<\/code> for about <strong>75 minutes<\/strong> (00:09:50\u201301:24:58 UTC). The Swarm Desktop 0.56.2 auto-update was offered for about <strong>8.5 hours<\/strong>, with the fix in 0.56.3 at 12:52 UTC <sup><a href=\"#src-38\">[38]<\/a><\/sup>. The package <strong>carried valid npm provenance<\/strong>, because the attacker controlled the real release path: an unsigned commit by <code>bee-worker<\/code>, then a GitHub release that triggered publishing, through a workflow with no protected-branch check or required reviewers <sup><a href=\"#src-38\">[38]<\/a><\/sup>. Reach was about 10 bee-js downloads and 65 Desktop downloads by 08:27 UTC, out of roughly 16,800 monthly downloads across the package family (a reach figure, not infections). Socket&#8217;s attribution to SleepyDuck\/EtherDuck is <strong>unconfirmed<\/strong> <sup><a href=\"#src-38\">[38]<\/a><\/sup>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Who is affected<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>wfx:<\/strong> any organisation whose maintainers&#8217; GitHub accounts or tokens were hijacked, plus consumers of the affected Packagist and Go versions <sup><a href=\"#src-36\">[36]<\/a><\/sup>.<\/li>\n\n<li><strong>bee-js:<\/strong> anyone who installed <code>@ethersphere\/bee-js<\/code> 13.3.0 or Swarm Desktop 0.56.2. Pin bee-js 13.2.0 or move to Desktop 0.56.3 <sup><a href=\"#src-38\">[38]<\/a><\/sup>.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">How wfx works<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Per endor labs <sup><a href=\"#src-36\">[36]<\/a><\/sup><\/em><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Account takeover.<\/strong> One victim&#8217;s leaked GitHub CLI OAuth token was found, but the overall initial-access source is <strong>unconfirmed<\/strong>.<\/li>\n\n<li><strong>Probe PR.<\/strong> A pull request titled &#8220;docs: dependency audit notes&#8221; is opened from branch <code>wfx-docs<\/code>.<\/li>\n\n<li><strong>Payload commit.<\/strong> The payload is committed to the default branch between benign commits, then <strong>reverted within seconds<\/strong>. It comes in three variants:<ul><li>workflow injection: commit &#8220;ci: refresh build cache&#8221;, step &#8220;Restore cache j632&#8221;<\/li><li>install hook: commit &#8220;chore: add ci helper&#8221;, script <code>.ci-xxxxxx.sh<\/code><\/li><li>throwaway workflow: <code>.github\/workflows\/build-audit-xxxxxx.yml<\/code><\/li><\/ul><\/li>\n\n<li><strong>Theft.<\/strong> The stealer takes CI secrets, cloud and registry credentials, and AI-provider keys.<\/li>\n\n<li><strong>Persistence:<\/strong><ul><li>new IAM access keys<\/li><li>trust policies set to <code>\"AWS\": \"*\"<\/code><\/li><li>Azure federated credentials named <code>wfx-ci-*<\/code><\/li><\/ul><\/li>\n\n<li><strong>Cleanup.<\/strong> Workflow runs and logs are deleted.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How bee-js works<\/strong> <sup><a href=\"#src-38\">[38]<\/a><\/sup>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The <code>prepare<\/code> script is renamed to <code>postinstall<\/code>.<\/li>\n\n<li><code>scripts\/generate-version.cjs<\/code> becomes a loader that fetches JavaScript from <code>frameworkmetrics[.]xyz<\/code>, including the paths <code>\/v1\/analytics<\/code>, <code>\/v1\/electron<\/code>, <code>\/v1\/assets\/<\/code> and <code>\/v1\/swarm\/report<\/code>, with the header <code>X-Ethersphere: 1<\/code>.<\/li>\n\n<li>It steals the node&#8217;s <code>config.yaml<\/code> and <code>swarm.key<\/code>.<\/li>\n\n<li>It installs a backdoor:<ul><li><strong>Linux\/macOS:<\/strong> a Go backdoor under <code>~\/.solidity\/<\/code> (systemd user unit <code>solidity-langsupport-runner.service<\/code>, LaunchAgent <code>com.solidity.langsupport.runner<\/code>) talking to a hub at <code>193[.]57[.]9[.]155:4912<\/code>, with the hub address resolved from an Ethereum contract.<\/li><li><strong>Windows:<\/strong> a Defender exclusion plus a ScreenConnect client (<code>ScreenConnect Client (5cbe79c9c25524df)<\/code>) connecting to <code>185[.]232[.]84[.]169:8041<\/code>.<\/li><\/ul><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">What defenders should look for<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>wfx IOCs<\/strong> <sup><a href=\"#src-36\">[36]<\/a><\/sup>:<ul><li>exfiltration endpoints: <code>194.150.166[.]244:8080\/exfil<\/code>, <code>18.143.168[.]96:8080\/exfil<\/code>, <code>153[.]75[.]82[.]159:8080<\/code>, with a Telegram bot as fallback<\/li><li>stealer SHA-256: <code>89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf<\/code><\/li><li>branches: <code>wfx-docs<\/code>, <code>chore\/[a-z0-9]{8}<\/code>, <code>ci-check-[0-9]{4}<\/code><\/li><li><strong>gaps in workflow run numbers<\/strong>, which show deleted runs<\/li><\/ul><\/li>\n\n<li><strong>wfx cloud events:<\/strong><ul><li>AWS CloudTrail: <code>CreateAccessKey<\/code>, <code>UpdateAssumeRolePolicy<\/code>, <code>AssumeRoleWithWebIdentity<\/code><\/li><li>Entra ID: federated-credential additions named <code>wfx-ci-*<\/code><\/li><\/ul><\/li>\n\n<li><strong>bee-js:<\/strong><ul><li>loader SHA-256 <code>ee2f762e9ed3fd89f106e0b80c2e923bf1aed9a280277d3d998a5902c4d9ec0f<\/code><\/li><li>DNS lookups for <code>frameworkmetrics[.]xyz<\/code><\/li><li>the systemd\/LaunchAgent names above<\/li><\/ul><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>False positives:<\/strong> legitimate key rotation also produces <code>CreateAccessKey<\/code>, so check the identity making the call and when it happened.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to investigate<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Pull requests:<\/strong> search your organisation&#8217;s PRs for the title &#8220;docs: dependency audit notes&#8221; from 10 November onward. Endor supplies a <code>gh search prs<\/code> query, but its syntax <strong>was not verified<\/strong> against GitHub CLI docs.<\/li>\n\n<li><strong>Branches:<\/strong> check repos for attacker branches.<\/li>\n\n<li><strong>Commit history:<\/strong> review default-branch history for add-then-revert pairs.<\/li>\n\n<li><strong>Cloud logs:<\/strong> pull the CloudTrail and Entra events listed above for the same period.<\/li>\n\n<li><strong>bee-js:<\/strong> use NPM-1 to find the package anywhere in your dependency tree.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Contain and remediate (in order)<\/strong> <sup><a href=\"#src-36\">[36]<\/a><\/sup><sup><a href=\"#src-38\">[38]<\/a><\/sup>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Revoke GitHub sessions, tokens, SSH keys and OAuth grants for affected accounts.<\/li>\n\n<li>Require phishing-resistant MFA.<\/li>\n\n<li>Rotate <strong>every<\/strong> secret reachable from workflows.<\/li>\n\n<li>Delete attacker IAM keys and <code>\"AWS\": \"<em>\"<\/code> trust policies, and remove <code>wfx-ci-<\/em><\/code> federated credentials.<\/li>\n\n<li>For bee-js:<ul><li>pin 13.2.0 or Desktop 0.56.3<\/li><li>rotate secrets from a clean machine<\/li><li>move node funds to a new key<\/li><li>rebuild affected hosts<\/li><\/ul><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Verify recovery<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Rerun the PR and branch searches and get no hits. Confirm IAM and Entra show no unknown keys or federated credentials. Confirm <code>npm ls<\/code> shows no 13.3.0.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Prevention and awareness<\/strong> <sup><a href=\"#src-36\">[36]<\/a><\/sup>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Put workflow files under CODEOWNERS.<\/li>\n\n<li>Make <code>GITHUB_TOKEN<\/code> read-only by default.<\/li>\n\n<li>Use <code>npm ci --ignore-scripts<\/code> in CI where possible.<\/li>\n\n<li>Protect release branches and require reviewers on release workflows.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Lesson:<\/strong> provenance attestations prove <em>where<\/em> a package was built, not that the builder was honest <sup><a href=\"#src-38\">[38]<\/a><\/sup>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Command panel &#8211; npm (source: npm docs, npm-ls <sup><a href=\"#src-39\">[39]<\/a><\/sup>)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>NPM-1 \u2014 Find bee-js anywhere in the dependency tree<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>OS \/ shell \/ tool: any OS; the project directory; <code>npm<\/code> CLI<\/li>\n\n<li>Privileges: a normal user with read access to the project<\/li>\n\n<li>Purpose: show every installed copy of <code>@ethersphere\/bee-js<\/code>, including transitive ones. The docs confirm <code>npm ls &lt;package-spec&gt;<\/code>, and that <code>--all<\/code> shows the full tree rather than direct dependencies only <sup><a href=\"#src-39\">[39]<\/a><\/sup>.<\/li>\n\n<li>Read-only or change: <strong>Read-only<\/strong><\/li>\n\n<li>Command:<\/li>\n\n<li>How to interpret: any <code>13.3.0<\/code> in the output means the malicious version was installed. Treat that machine as compromised and follow the remediation steps. Run the command in each project and each CI cache.<\/li>\n\n<li>Not tested by MAG212.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>npm ls @ethersphere\/bee-js --all<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Related on MAG212 CYBER<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/cyber.mag212.com\/the-keys-were-already-inside-mag212-cyber-weekly-november-14-2026\/\">MAG212 CYBER Weekly, 14 November 2026: The keys were already inside<\/a> (full issue and PDF)<\/li>\n\n<li><a href=\"https:\/\/cyber.mag212.com\/subscribe-free\/\">Subscribe free to MAG212 CYBER<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Sources<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><span id=\"src-36\">[36]<\/span> Endor Labs, &#8220;GitHub Actions supply chain attack targeting CI secrets at scale&#8221;, 12 Nov, updated 13 Nov 2026. <a href=\"https:\/\/www.endorlabs.com\/learn\/github-actions-supply-chain-attack-targeting-ci-secrets-at-scale\" rel=\"noopener\" target=\"_blank\">link<\/a><\/li>\n\n<li><span id=\"src-37\">[37]<\/span> BleepingComputer, &#8220;Hackers compromise 4,800 GitHub accounts to steal cloud credentials (not fetched)&#8221;, Nov 2026. <a href=\"https:\/\/www.bleepingcomputer.com\/news\/security\/hackers-compromise-4-800-github-accounts-to-steal-cloud-credentials\/\" rel=\"noopener\" target=\"_blank\">link<\/a><\/li>\n\n<li><span id=\"src-38\">[38]<\/span> Safeguard.sh, &#8220;@ethersphere\/bee-js and Swarm Desktop compromise: npm loaders without install scripts&#8221;, 13 Nov 2026. <a href=\"https:\/\/safeguard.sh\/resources\/blog\/ethersphere-bee-js-swarm-desktop-compromise-npm-loaders-without-install-scripts\" rel=\"noopener\" target=\"_blank\">link<\/a><\/li>\n\n<li><span id=\"src-39\">[39]<\/span> npm Docs, &#8220;npm-ls (CLI v12)&#8221;, Current. <a href=\"https:\/\/docs.npmjs.com\/cli\/v12\/commands\/npm-ls\/\" rel=\"noopener\" target=\"_blank\">link<\/a><\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Written and edited by El Mostafa Ouchen for MAG212 CYBER. Facts checked against the sources above as of 14 November 2026, 10:05 AM PST. Diagrams are original MAG212 CYBER illustrations.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hijacked GitHub accounts opened probe pull requests that stole CI secrets and planted cloud persistence, while a genuine-provenance bee-js release shipped malware.<\/p>","protected":false},"author":1,"featured_media":4267,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13,15],"tags":[372,269,371,285,56],"class_list":["post-4285","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cyber-incidents","category-threat-intelligence","tag-ci-cd","tag-github","tag-github-actions","tag-npm","tag-supply-chain-attack"],"_links":{"self":[{"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/posts\/4285","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/comments?post=4285"}],"version-history":[{"count":0,"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/posts\/4285\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/media\/4267"}],"wp:attachment":[{"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/media?parent=4285"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/categories?post=4285"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cyber.mag212.com\/fr\/wp-json\/wp\/v2\/tags?post=4285"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}