URGENT: Targeted malware in our repositories altering tailwind.config.js and .gitignore via force-push. Has anyone seen this? #197873
Replies: 8 comments 3 replies
|
The fact that the exact same payload reappeared after you force pushed clean files strongly suggests that the repository itself is not the root cause. I would investigate in roughly this order:
Regarding malware identification, the most reliable indicator is not the obfuscated code itself but the account or process creating the commit. If you can identify exactly which credential performed the push, you’ll often find the persistence mechanism much faster than by reverse engineering the payload first. Since .env handling was modified, treating all previously exposed secrets as compromised and rotating them is the correct response. |
|
Great summary from Luke-Corwin. I want to add the most likely root cause and The re-injection is almost certainly an npm postinstall script. The most overlooked vector in attacks like this: a dependency in your Verify this first: Find all postinstall/prepare scripts in your entire dependency treenpm ls --json | grep -E "postinstall|prepare|preinstall" Or more thoroughly:find node_modules -name "package.json" -exec grep -l "postinstall|prepare" {} ; Look specifically for any recently-added or oddly-named packages. The payload Why .gitignore was modified matters — it's not incidental. Removing .env from .gitignore is the real goal. The attacker wants your .env file Specific hunting checklist (in priority order):
Check for packages published or updated in the last 30 days that touch yourconfigs
Go to: Org Settings → Audit Log → filter by git.push around the attack
Go to: Org Settings → GitHub Apps — look for anything with Contents: write
Red flag: any workflow with this + a checkout step + a commit steppermissions: Permanent prevention (beyond what's already been suggested):
TL;DR for the original poster: Your cleanup isn't sticking because the injector |
|
We got hit by what looks like the same campaign and open-sourced a cleanup toolkit that may help others here: https://github.com/Pariola-droid/build-config-loader-cleanup A few things that matched, plus some detail that might add to the picture: Same IOCs: obfuscated loader appended to build configs ( C2 via blockchain dead-drop (EtherHiding): the decoded payload resolves its C2 from attacker wallets via Delivery: in our case an unsigned commit was force-pushed / amended into existing commits, with the git author spoofed to look Re-injection: confirming what others noted: with a malicious Cleanup approach (in the toolkit): for every branch, restore each poisoned config file to its most recent clean version in |
|
You need every member of your team that has access to rotate all credentials associated with github.. |
|
We investigated this issue and it is not a GitHub platform bug. The behavior strongly indicates an active supply-chain compromise with persistent write access to our repositories. The malicious changes being repeatedly re-injected (even after force-push cleanup) confirm that the attacker still controls at least one of the following: A compromised developer machine with stored Git credentials or Git hooks The modifications found are consistent with supply-chain malware targeting frontend build tools (e.g., vite.config.js / tailwind.config.js) and git configuration manipulation (.gitignore changes such as removing .env and adding config.bat). This pattern is commonly used to enable secret exfiltration and persistent execution during build or install steps. Root cause investigation steps: This is not a one-time repository cleanup issue. Until the compromised identity or system is identified and removed, the attacker will continue to reintroduce the payload through legitimate write access. |
This comment was marked as off-topic.
This comment was marked as off-topic.
|
🕒 Discussion Activity Reminder 🕒 This Discussion has been labeled as dormant by an automated system for having no activity in the last 60 days. Please consider one the following actions: 1️⃣ Close as Out of Date: If the topic is no longer relevant, close the Discussion as 2️⃣ Provide More Information: Share additional details or context — or let the community know if you've found a solution on your own. 3️⃣ Mark a Reply as Answer: If your question has been answered by a reply, mark the most helpful reply as the solution. Note: This dormant notification will only apply to Discussions with the Thank you for helping bring this Discussion to a resolution! 💬 |
|
Same campaign here, different variant tag — and I want to add one detail I have not seen written down anywhere, because it is the reason "we cleaned it and it came back" happens. Variant: yours is 1. Why cleaning doesn't stick — the commit message liesThe commits that inject the loader carry messages like: So the commit claiming to have removed the malware is the one carrying it. I "cleaned" this repeatedly and genuinely believed it was re-appearing on its own. The tell is a twin commit pair: Same parent, same message, one second apart. The attacker takes your cleanup commit, rewrites it into an infected twin, and force-pushes the branch onto it. Check whether your "clean" HEAD has a twin — 2. Finding the source — the author name is forged, and that is provableMy local Local git cannot produce that; it only ever uses Worth checking, since password rotation does not touch these:
3. Detection that works (the diff view will not help you)The payload sits after ~280 spaces on the same line as gh api "repos/OWNER/REPO/contents/postcss.config.mjs?ref=BRANCH" --jq .size
# real config: 80-200 bytes. infected: 30 KB+Signatures worth grepping: In my most recent wave, 8 repos were force-pushed in 4 minutes in alphabetical order — which is what convinced me it was scripted rather than a person. Still unsolved, and I would take any pointerBoth audited machines are clean: full signature sweeps found zero infected files, no And on a free plan there is nothing I can do about it: no audit log to see which credential pushed, and both branch protection and rulesets 403 with "Upgrade to GitHub Pro" on private repos — so a force-push cannot be blocked at all. Detection is the only control available. If anyone got past this and actually identified the persistence path after their machines came back clean, please post what it turned out to be. |
Uh oh!
There was an error while loading. Please reload this page.
🏷️ Discussion Type
Question
💬 Feature/Topic Area
Supply chain security
Discussion Details
Our engineering team is dealing with a highly suspicious supply chain/repository attack across all 3 of our GitHub organizations, and I am trying to figure out how they are maintaining persistence.
What we are seeing:
Several of our repositories suddenly showed as "updated 2 days ago." When we checked the commit history on the default branch, no files appeared to be changed on that date.
However, when we created a PR from the default branch to another branch, the diff revealed that two specific files were modified:
.gitignore: The attacker removed
.envand addedconfig.bat.tailwind.config.js (and in some repos, vite.config.js): The attacker injected a massive block of obfuscated JavaScript at the very end of the file.
The injected code starts like this:
The Problem:
We immediately identified this as malicious, revoked credentials, and force-pushed clean versions of the config files to remove the obfuscated code. However, we have been attacked twice. Even after cleaning the repository, the exact same malicious code was re-injected and pushed again today.
My Questions:
Any guidance on threat hunting this specific payload would be massively appreciated. We are currently treating all local .env secrets as compromised.
All reactions