Skip to main content
Th1nkDev
Two and a half months with an intruder in our repositories
Back to Academy

Article

Two and a half months with an intruder in our repositories

BIYA PaulBIYA Paul
September 9, 20269 min readIntermediate
#Sécurité #Retour d'expérience #Git #VS Code #Node.js
In a nutshell

A 32 KB "font file" quarantined by the antivirus turned out to be a trojan that had been rewriting our commits since June. What happened, what we did, and what we changed.

On this page

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.

Comparison between a real WOFF2 font and the fake 32,874-byte file containing 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.
Timeline from 22 June to 6 September 2026: first injection, acceleration in late August, detection on 6 September
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:

shell
git log --all --format='%h|%ad|%cd|%an|%cn|%s' --date=iso \
  | awk -F'|' '$3 ~ /-0700/ || $4 != $5' | grep -v GitHub

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.

  1. In VS Code: task.allowAutomaticTasks set to off, security.workspace.trust.enabled checked.
  2. In ~/.npmrc: ignore-scripts=true.
  3. A passphrase on your SSH keys, and a deployment key separate from your personal key.
  4. 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.

Found this useful?

Let us know what you think.

BIYA Paul

About the author

BIYA Paul · Founder & CEO at Think Dev SARL

Senior Software engineer, Designer and Product Manager based in Douala, Cameroon. Building digital products that solve real problems in Africa.

Ready to get started?

Your next digital product
Start here.

Let's talk about your project. No commitment required — just an honest conversation about what's possible.