{"slug": "your-deploy-box-is-the-target-supply-chain-defenses-after-this-week-s-attacks", "title": "Your deploy box is the target: supply chain defenses after this week's attacks", "summary": "A developer, responding to two supply chain attacks reported in November, audited their own CI configuration and found a single build job holding a registry token, deploy key and cloud credential at once. The attacks included a stolen developer key that pushed a dropper into @ethersphere/bee-js 13.3.0 via the normal publish workflow, and a fresh GitHub account that opened CI exfiltration PRs, added an AGENTS.md note telling reviewers not to flag the uploads, and shipped a Rust crate whose build.rs harvested credentials. The developer's proposed fix splits builds from deploys so dependency-installing jobs carry no deploy credentials.", "body_md": "I read the OpenSourceMalware report on Tuesday and then did something simple. I opened my own CI config and listed every secret a single build job could see. The attack it describes starts with a PR from a brand new account labeled \"CI diagnostics\" that sends environment variables to a remote URL.\n\nThe answer was uncomfortable. A build job on my side holds a registry token, a deploy key and a cloud credential, all in one shell. That is exactly the shopping list attackers wrote down this week.\n\n| What happened | Date | What it means for you | \n|---|---|---|\n| A stolen developer key pushed a dropper into `@ethersphere/bee-js` 13.3.0, and the publish workflow shipped it to npm and GitHub | Nov 9 | Your release pipeline will publish whatever a valid key commits | \n| A brand new GitHub account sent CI exfiltration PRs, a prompt injection for reviewers and a Rust crate with a `build.rs` stealer | Nov 10 | Build scripts and PR checks run with your secrets in reach | \n| Socket's hardening guide says most 2025 and 2026 CI compromises came through `npm install` , not workflow bugs | Nov 3 | Install time is the weak spot, so lock it down first | \n\nOX Security reported on November 9 that attackers likely used a stolen developer access key to commit malicious code to the official bee-js and swarm-desktop repositories. The commit triggered the normal publish workflow, which created the npm release and the GitHub release. No one broke into the registry. The pipeline did its job with a bad commit.\n\nThe dropper then asked a command server for a payload matched to the victim's operating system. It uploaded the local Swarm node's encrypted key and config, then installed a backdoor. On Linux and macOS the backdoor looks up its server address from an Ethereum contract, so the attacker can move it whenever they like. OX counts about 16,800 monthly downloads of exposure. That is small. The pattern is not.\n\nThe second attack, written up by OpenSourceMalware on November 10, came from a GitHub account created about seven hours before the report. It ran four moves in a row. Two PRs tried to send environment variables and signing material out of CI. A third PR added a paragraph to `AGENTS.md` telling reviewers, including automated ones, not to flag those uploads. Another PR added a Rust crate, `lwd-uri-util`, whose `build.rs` gathers `.git-credentials`, `.aws/credentials` and `.npmrc`. Finally, a fake security disclosure sent two maintainers to a cloned sign-in page.\n\nRead that list again if you deploy code. The target is not your app. The target is the box where your app gets built.\n\nA typical CI job carries a registry push token, a cloud credential and a deploy SSH key. It also runs arbitrary install scripts from hundreds of transitive packages. One malicious `postinstall` or `build.rs` inherits all of it.\n\nThe OpenSourceMalware report lists what the Rust stealer wants: GitHub, AWS, Cargo, npm, PyPI, Android signing keys and CI secrets. Its advice for anyone who built the affected branches is to revoke and reissue every secret present in that build environment. That is a long afternoon. The cheaper move is to make sure the build never holds most of them.\n\nHere is the rule I now apply. A job that installs dependencies gets no deploy credentials. A job that deploys installs nothing.\n\n```\nname: release\non:\n  push:\n    tags: [\"v*\"]\n\npermissions: {}\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n    steps:\n      - uses: actions/checkout@<full-40-char-sha>\n        with:\n          persist-credentials: false\n      - run: npm ci --ignore-scripts\n      - run: npm run build\n      - uses: actions/upload-artifact@<full-40-char-sha>\n        with:\n          name: dist\n          path: dist/\n\n  deploy:\n    needs: build\n    runs-on: ubuntu-latest\n    environment: production   # holds the deploy secret, needs approval\n    steps:\n      - uses: actions/download-artifact@<full-40-char-sha>\n        with:\n          name: dist\n      - run: ./scripts/push-release.sh\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n```\n\nThe build job sees no secret at all. The deploy job installs nothing, so a poisoned dependency never runs next to the token. The `production` environment can require a human approval before the second job starts.\n\nSocket's guide makes a point I keep repeating to people. Version tags are movable pointers. In the `tj-actions/changed-files` attack, pinning by commit SHA kept every repository safe. In the March 2026 `trivy-action` incident, Socket reports 75 of 76 version tags were force-pushed.\n\nA SHA proves the content cannot change. It does not prove where the commit came from, because impostor commits from forks can still resolve. Pin, and read the diff when you bump.\n\nImages work the same way. A tag like `node:22` is a pointer. A digest is content.\n\n```\n# resolve once, on a machine you trust\ndocker buildx imagetools inspect node:22-slim | grep Digest\n# Digest: sha256:<64 hex chars>\n\n# then pin it in the Dockerfile\n# FROM node:22-slim@sha256:<64 hex chars>\n```\n\nDo the same for the images you run, not only the ones you build. In a compose file:\n\n```\nservices:\n  web:\n    image: registry.example.com/team/web@sha256:<64 hex chars>\n```\n\nI also keep to upstream-only namespaces. Pull `library/postgres` or the vendor's own namespace, not a lookalike from a stranger. Even a real namespace can be hijacked, so digests matter more than names. Names only remove the cheapest attacks.\n\nSocket cites a Wiz estimate that a 7 to 14 day cooldown catches 80 to 90 percent of supply chain attacks, and notes Dependabot applies a three day cooldown by default on github.com. Ethersphere reverted the bad npm version only after it shipped. If your lockfile refuses anything published in the last week, you skip most of that window.\n\n```\n# .npmrc\nignore-scripts=true\nmin-release-age=7\n```\n\nScripts then run only where you allow them. Check your npm version supports the release-age key before you rely on it, since Socket describes it as a setting for `.npmrc` and your update bot.\n\nSocket calls the write-scoped `GITHUB_TOKEN` the usual escalation step from a foothold to a real incident. Set `permissions: {}` at workflow level, as in the example above, and grant per job. Avoid `pull_request_target` with untrusted code. Pass PR titles through `env:` instead of inlining them in `run:` blocks. Run `zizmor` in CI to catch the rest.\n\nThen treat registry tokens the same way. A publish token that only works for one package, with a short life, turns a stolen key into a small problem. The Ethersphere case shows the other side: a long-lived developer key plus an automatic publish workflow equals a release anyone with the key can ship.\n\nThe last layer is the one most people skip. When the stealer cannot reach its server, it collects secrets and has nowhere to put them. The Rust crate uploads to a public webhook endpoint. The Ethersphere dropper called a fresh `.xyz` domain registered two days earlier. A deny-by-default outbound list blocks both.\n\nSocket's advice is to run an egress agent in audit mode first, build the allowlist from real traffic, then enforce. Socket also admits the limit: allowlisting cannot stop exfiltration to hosts you must allow, such as `github.com`. Egress is a way to cut damage, not a wall.\n\nOn the server that runs your apps, the same idea applies at runtime. If the web container only talks to your payment API, say so.\n\nWe built outbound allowlists and image pinning into Levelrail for the deploy side. Per the docs, Levelrail pins an image pulled from a registry to its digest at deploy time, `APP_IMAGE_VERIFY_MODE=enforce` refuses unsigned or unpinned images on promotion, and a service can declare an `egress` allowlist of host and port pairs. Two honest limits from the same docs: without the egress block the outbound traffic is open, and signature checks support cosign key pairs, not keyless signatures. Build-time secrets are opt in per secret for that reason.\n\nI did five things, in order of effort.\n\n`npm ci`.` ignore-scripts`.` permissions: {}` as the workflow default.\nNone of it takes more than a day. The attacks this week were small: about 16,800 monthly downloads and one wallet-focused crate. The same techniques against a package with millions of installs would look different.\n\nAttackers are skipping your code and going for the machine that builds and ships it. Split install from deploy, pin by digest, add a cooldown, shrink token scopes and limit outbound traffic. How many secrets does your slowest, most neglected CI job hold right now?", "url": "https://wpnews.pro/news/your-deploy-box-is-the-target-supply-chain-defenses-after-this-week-s-attacks", "canonical_source": "https://dev.to/thegdsks/your-deploy-box-is-the-target-supply-chain-defenses-after-this-weeks-attacks-1o3d", "published_at": "2026-11-12 17:41:00+00:00", "updated_at": "2026-11-12 17:52:40.061804+00:00", "lang": "en", "topics": ["ai-safety", "developer-tools", "ai-tools"], "entities": ["OpenSourceMalware", "OX Security", "Socket", "@ethersphere/bee-js", "GitHub", "npm", "lwd-uri-util"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/your-deploy-box-is-the-target-supply-chain-defenses-after-this-week-s-attacks", "markdown": "https://wpnews.pro/news/your-deploy-box-is-the-target-supply-chain-defenses-after-this-week-s-attacks.md", "text": "https://wpnews.pro/news/your-deploy-box-is-the-target-supply-chain-defenses-after-this-week-s-attacks.txt", "jsonld": "https://wpnews.pro/news/your-deploy-box-is-the-target-supply-chain-defenses-after-this-week-s-attacks.jsonld"}}