开源工具
dev.to ·
@Biya Paul
仓库被入侵两个半月:一次供应链攻击的复盘
一名独立开发者复盘了其工作区遭木马入侵的经历:2026年6月22日首次注入,9月6日被杀毒软件发现,潜伏77天,波及3个 GitHub 组织下的18个仓库,并扩散到一名协作者的机器。恶意代码伪装成 32KB 的 fa-solid-400.woff2 字体文件,通过 .vscode/tasks.json 声明隐藏任务、配合 settings.json 开启自动任务,在 VS Code 打开文件夹时用 Node.js 执行;另一变体将代码追加在 tailwind.config.js、vite.config.js、eslint.config.mjs 尾部,以便在构建、CI 流水线和 Docker 镜像中运行。攻击者还利用受害者的 SSH key 强推提交,并接触了 SSH 密钥和13个项目的 .env 文件。
AI推荐理由 这是一篇开发者亲历的供应链攻击复盘,提供了具体可验证的技术细节:伪装成 fa-solid-400.woff2 的 32KB 木马、通过 .vscode/tasks.json 隐藏任务和自动任务设置在打开文件夹时执行、以及在 tailwind.config.js/vite.config.js/eslint.config.mjs 尾部追加代码以在构建和 Docker 镜像中运行。文中给出时间线(6月22日首次注入、9月6日检测、77天潜伏、18个仓库受影响、3个 GitHub 组织)和攻击者使用受害者 SSH key 强推提交的痕迹,对开发者安全实践有直接参考价值。局限在于这是单一来源的个人叙述,未提供独立验证或恶意样本分析,部分结论(如凭据是否已被滥用)未给出证据。
72
正文
On 6 September 2026, an antivirus quarantined a 32 KB "font file" in one of our projects. That file was a trojan. It had been running on our workstation since 22 June, had rewritten commits in eighteen repositories, and had spread to a collaborator's machine. Here is what happened, what we did, and what we are keeping from it.
An ordinary morning, two details that did not add up
It started with two observations that, taken separately, would have worried nobody. On opening the machine, every one of our repositories showed a pending pull we could not account for. And every one of them had triggered an automatic deployment on GitHub overnight, which had failed.
An hour later, the antivirus reported that it had quarantined a file named fa-solid-400.woff2 in the public/fonts folder of a marketing site. A Font Awesome font. Except that a font by that name weighs more than 100 KB and starts with the binary signature wOF2. This one weighed 32,874 bytes and started with several hundred spaces, followed by unreadable JavaScript.
A real font starts with "wOF2". The fake one starts with spaces, then code.
What we found when we dug
We spent the afternoon examining, read-only, our whole workspace: about sixty repositories. The picture that came out was far wider than the one flagged file.
Eighteen repositories affected, across three GitHub organisations, including client projects.
A first injection on 22 June, in a marketing site. Seventy-seven days before detection.
A signature in the git history: in every affected repository, the last commit had been copied verbatim, same message, same author date, but with a truncated committer name and a -0700 timezone, the US west coast. Those copies had been force-pushed with our own SSH key.
A collaborator's workstation carried the same signature from late August: they had cloned an already infected repository.
A last malicious push the day before, at 15:21. The malware was still active while we were looking for it.
Seventy-seven silent days, then fifteen projects hit in ten days.
The mechanism itself fitted in three files. A .vscode/tasks.json declared a hidden task that ran the fake font with Node.js as soon as the folder was opened in VS Code. A .vscode/settings.json enabled automatic tasks so the editor would not ask. And in one variant, the same code was appended after a long run of spaces at the end of tailwind.config.js, vite.config.js or eslint.config.mjs, to run on every build, including in our pipelines and Docker images. We go through all of it in a second, more technical article.
What the malware had within reach
A trojan of this kind is not trying to break anything. It is after credentials. For two and a half months it had access to everything on the workstation of an independent developer working for several clients:
SSH keys, without a passphrase, that opened both GitHub and the production server;
the .env files of thirteen projects, with their payment, AI, email, storage and database keys;
passwords saved in the browser and open sessions;
deployment secrets, exposed in the pipelines its own pushes had triggered.
None of that was encrypted by anything other than the Windows session. That is the hardest lesson of this story: cleaning the machine is only the beginning. The real remediation is making everything that was copied worthless.
What we did, in order
1. Cut off access
Before anything else, from a phone rather than the affected workstation: delete every SSH key on the GitHub account, change the password, sign out all sessions, revoke access tokens and authorised applications. Without a key, the malware cannot push anything, wherever it is.
2. Clean the workstation
We chose an in-place clean-up over a reinstall, for lack of a backup drive that day, and accepting the residual risk. Quarantine of the files dropped in the temp folder, removal of stored keys and credentials, deletion of the malware's files and decoys across the eighteen repositories, purge of forty gigabytes of installed dependencies, hardening of VS Code, then a full re-test: no marker left, no process, no persistence, no suspicious connection.
3. Regenerate every secret
This is the longest job. We inventoried the variable names of twenty-seven .env files and the secrets of nineteen pipelines, then applied the same rule to each: create the new one at the provider, put it in place, verify, and only then revoke the old one. In that order, otherwise the application goes down while everything is put back.
4. Audit the server
SSH login logs since June, authorised keys, users, scheduled jobs, services, Docker images built during the period. A new deployment key, separate from the personal key. Rebuild of every image from the cleaned repositories.
5. Clean the repositories and tell people
Removal of the malware from the branches still infected on GitHub, force-push protection, mandatory signed commits, a guard workflow that fails if a signature reappears. Then a message to every collaborator and every client whose secrets had been on our workstation. Saying it quickly protects the relationship; staying silent exposes it.
What we are keeping
The antivirus did not save us. A failed push and an unexplained pull did.
VS Code runs code when you open a folder. The task.allowAutomaticTasks setting exists for that, and the malware set it to on in every project. We now force it to off in our user settings, with workspace trust enabled. Opening an unknown repository is not a harmless gesture.
One key for everything is one leak for everything. The same SSH key, without a passphrase, opened GitHub and production. Today: a personal key with a passphrase for GitHub, one deployment key per server that lives only in the pipeline's secrets, and never one in place of the other.
npm install scripts are a way in. ignore-scripts=true in ~/.npmrc costs a few pnpm rebuild a year and closes an entire class of attacks.
Git history is a free audit log. A single command, run weekly, would have revealed the rewritten commits on 22 June. We now run it every Monday:
git log --all --format='%h|%ad|%cd|%an|%cn|%s' --date=iso \
| awk -F'|' '$3 ~ /-0700/ || $4 != $5' | grep -v GitHub
Enter fullscreen mode
Exit fullscreen mode
An incident is handled with a plan, not with adrenaline. We wrote ours down as four procedures: detect and remove on a workstation, on a server, in the repositories, then the actions to take after an infection. They were useful the very next day, to walk the affected collaborator through the same steps.
If you build with Node.js and VS Code
Do these four things this week. They take less than an hour.
In VS Code: task.allowAutomaticTasks set to off, security.workspace.trust.enabled checked.
In ~/.npmrc: ignore-scripts=true.
A passphrase on your SSH keys, and a deployment key separate from your personal key.
Search your projects for a fa-solid-400.woff2 file of 32,874 bytes, and a tasks.json containing folderOpen. If you find one, do not open it: read the technical analysis and start by cutting off your access.
This campaign targets developers, often through fake job interviews or fake freelance assignments that come with a repository "to run". We have not yet identified our own entry point. If you have been through the same thing, write to us: comparing traces helps everyone.