Как вредоносный код переписал мой Git-коммит и заразил десятки проектов и несколько рабочих машин

от автора

Эта история началась с обычного git push.

Я работал над проектом, не буду называть его, потому что статья не про конкретную компанию или проект и не про поиск виноватых. Она про то, как вредоносное ПО может попасть в привычный рабочий процесс разработчика, замаскироваться под нормальные Git-изменения и постепенно заражать другие проекты и машины.

Я хочу рассказать это на реальном примере, потому что такие истории обычно кажутся чем-то далеким: «ну это где-то в крупных компаниях», «ну это у тех, кто скачивает странные файлы», «ну я-то опытный разработчик, я замечу».

Как оказалось, заметить можно. Но не сразу. И не всегда спокойно.


Обычный рабочий день

В тот день я работал как обычно. К обеду у меня накопились локальные коммиты, и я решил запушить изменения на GitHub.

Но push не прошел. Git сказал, что в удаленной ветке уже есть изменения и сначала нужно сделать pull.

В командной работе это обычная ситуация. Но здесь был важный нюанс: на этом проекте я был практически единственным активным разработчиком. Да, доступ был еще у нескольких людей, но они пушили туда очень редко.

Поэтому сообщение «сначала подтяни изменения» выглядело странно.

Я пошел смотреть, что именно появилось в удаленной ветке.


Мой коммит был уже не совсем моим

Когда я открыл историю, меня сильно смутило, что измененным оказался мой же предыдущий коммит.

Не новый коммит от другого человека. Не обычный merge. Не pull request.

А мой коммит, который я делал день или два назад.

Но внутри были файлы, которые я не трогал. Насколько помню, среди них был .gitignore и JavaScript-конфиг, вроде babel. config. js.

Подозрительные изменения в знакомом коммите

Подозрительные изменения в знакомом коммите

Скриншот: в знакомом коммите рядом с обычными изменениями появился подозрительный babel.config.js.

В этом JS-файле были странные обфусцированные строки. То есть код был специально записан так, чтобы человек не мог быстро понять, что он делает.

Обфусцированный JavaScript в babel.config.js

Обфусцированный JavaScript в babel.config.js

Скриншот: длинный обфусцированный JavaScript-payload был добавлен прямо в babel.config.js.

Вот в этот момент стало понятно: это не обычная Git-ситуация. Что-то явно пошло не так.


Первая мысль: взломали мой компьютер

Сначала я решил, что скомпрометирован мой компьютер или мой GitHub.

Честно говоря, состояние в такие моменты неприятное. Ты вроде бы опытный разработчик, понимаешь Git, SSH, токены, доступы, окружения. Но в голове все равно появляется мысль: «Меня взломали, а я даже не понял когда».

Я сообщил коллегам, что, скорее всего, мой аккаунт в GitHub взломан. На тот момент никто из нас еще не понимал масштаба проблемы.

Я начал проверять свой Mac: приложения удаленного доступа, расширения VS Code, плагины RubyMine, системные логи macOS, Docker-логи, development-логи проектов. Искал все, что могло объяснить, как кто-то получил доступ к моему GitHub.

Параллельно я удалял все, что казалось подозрительным: плагины, расширения, приложения. Это была не самая методичная фаза расследования. Скорее стрессовая попытка быстро убрать все потенциально опасное.


Мы поменяли ключи, пароли и платежные секреты

Я сообщил руководителю проекта, что ситуация похожа на компрометацию.

Мы решили действовать так, будто утекло все, что могло утечь: SSH-ключи, GitHub-доступ, токены, секреты, пароли, платежные креды.

Мы поменяли SSH-ключи, пароль GitHub, платежные токены и секреты, связанные с сервисами Stripe и PayPal.

Это было логично. Если злоумышленник получил доступ к машине разработчика или GitHub, нельзя исключать, что он мог видеть .env, SSH-ключи, deploy keys, API-токены и другие чувствительные данные.

После этого мне казалось, что самое страшное позади — ведь пару дней был полный штиль.

Но через несколько дней я решил снова проверить проект и последний коммит.

И снова увидел вредоносный код…


Второй удар: проблема не исчезла

Это был очень неприятный момент.

Мы уже поменяли ключи, пароли и секреты. Я уже почистил машину. Но вредоносный скрипт появился снова…в GitHub.

В голове сразу возникла мысль: значит, кто-то все еще имеет доступ. Либо к моей машине, либо к GitHub, либо к какой-то другой части цепочки, которую мы еще не нашли.

Я снова начал смотреть логи: локальные, серверные, Docker, system logs, development logs. Проверял серверы, с которыми работал. Но картина не складывалась.

Если все ключи поменяли, почему атака продолжается?

Ответ оказался в том, что проблема была не только у меня.


Email-уведомление вывело меня на товарища

В какой-то момент я вспомнил важную деталь.

Как то утром мне приходили email-уведомления от GitHub о пушах. Время тех уведомлений было около 4:58 утра.

Я точно знал, что в это время спал.

Email-уведомление GitHub о push ночью

Email-уведомление GitHub о push ночью

Скриншот: GitHub-уведомление о push пришло в 04:58, когда я точно ничего не пушил.

Но самое важное было не только во времени. В уведомлении был подсвечен ник моего товарища в GitHub. То есть снаружи это выглядело так, будто push был связан с его аккаунтом. Именно поэтому я пошел к нему.

Не потому что подозревал его лично, а потому что email-нотификация дала конкретный след: ник, активность, время, push.

Дальше стало еще страннее. Когда я открыл сами коммиты в GitHub, часть изменений выглядела так, будто они подписаны уже моим именем.

Получалась путаница: уведомление вело к одному человеку, Git-история показывала другого, а реальная точка заражения могла быть вообще третьей.

И это важный момент. Такие атаки опасны не только вредоносным кодом, но и тем, что они запутывают следы. Разработчик видит знакомые имена, знакомые коммиты, привычные файлы, и мозг пытается объяснить это как обычную рабочую ситуацию.

Но это была не обычная ситуация.


Расследование вместе с коллегой

Я пошел к товарищу и объяснил ситуацию.

Сначала мы не думали, что его компьютер или GitHub тоже скомпрометированы. Скорее проверяли токены, доступы, GitHub Apps, OAuth Apps, SSH-ключи, все возможные способы, через которые мог происходить push.

Но через пару часов стало понятно: похожие симптомы есть и у него.

Потом выяснилось, что похожие признаки есть еще у нескольких разработчиков в других проектах.

Мы начали восстанавливать цепочку по push, коммитам и GitHub-истории. В итоге нашли первого разработчика, у которого заражение появилось раньше всех.

Если у меня первые признаки были примерно 28 марта, то у него похожие изменения появились еще 1 или 2 марта.

Так стало понятно: это не одиночный взлом моего компьютера. Это цепочка заражения.


##«А ты уверен?»

Когда мы вышли на первого разработчика в цепочке, я написал ему, что, судя по истории, именно с его окружения все началось.

Первая реакция была понятной: «А ты уверен?»

Я отправил скриншоты, коммиты, доказательства.

Через какое-то он взял время на перепровекру — и когда вернулся, сказал примерно следующее: «Я помню как видел, что у меня почему-то меняется .gitignore и .env и появляются странные изменения, но не придал этому значения».

И вот это один из главных уроков всей истории.

Если у вас в проекте «сам по себе» меняется .gitignore, package. json, babel. config. js, postcss. config. js, tailwind. config. js, .env или любой другой конфиг, это не мелочь.

Это повод остановиться и разобраться.


Как распространялось заражение

Общая схема выглядела примерно так.

Тот самый разработчик на Windows установил что-то вредоносное: возможно, npm-пакет, dev-tool, расширение или инсталлер. После этого в его проектах появился обфусцированный JavaScript-код, который он успешно запушил в GitHub.

Затем мой колега получил задачу развернуть тот самый проект и помочь выполнить ряд задач. Он сделал обычные действия: клонировал репозиторий, установил зависимости через yarn install или похожую команду, запустил и полностью развернул проект.

Демонстрация развернутого React-проекта

Демонстрация развернутого React-проекта

GIF: пример того, как зараженный проект снаружи может выглядеть как обычное рабочее приложение.

Смотреть MP4-версию в лучшем качестве

Если вредоносный код выполняется на этапе install/build или через dev-tooling, этого может быть достаточно, чтобы скомпрометировать локальное окружение.

Дальше злоумышленники получают доступ к SSH ключам, GitHub-контексту разработчика и начинают заражать уже другие доступные ему репозитории.

Так цепочка идет дальше: один разработчик, второй, третий, несколько проектов, несколько веток.

Это и есть supply chain attack. Заражается не только конечная машина, а сама цепочка разработки и доставки кода.


Что такое PolinRider

Позже я начал искать информацию и вышел на материалы про PolinRider.

По данным OpenSourceMalware, PolinRider — это кампания, связанная с компрометацией GitHub-репозиториев через вредоносные npm-пакеты, dev-tooling и обфусцированные JavaScript-payloads.

PolinRider: DPRK GitHub Attack

PolinRider: DPRK GitHub Attack

Иллюстрация из материала OpenSourceMalware о кампании PolinRider.

Исследователи связывают эту активность с DPRK-linked threat actor, то есть с группами, которые ассоциируют с Северной Кореей.

Для обычного разработчика практический вывод не в геополитике. Практический вывод в том, что атака бьет по привычному developer workflow:

  • установить пакет;

  • открыть проект;

  • запустить yarn install;

  • сделать build;

  • запушить изменения.

Именно поэтому это настолько опасно. Атака не выглядит как «я скачал вирус». Она выглядит как обычная работа разработчика.


Где прячется вредоносный код

По данным исследователей, PolinRider может добавлять обфусцированный JavaScript в обычные файлы проекта. Например:

  • postcss.config.mjs

  • tailwind.config.js

  • eslint.config.mjs

  • next.config.mjs

  • babel.config.js

  • App.js

  • app.js

Это хитрый выбор.

Разработчики редко внимательно читают конец config-файла, особенно если проект большой и «вроде работает». А если изменение еще и спрятано в уже существующий коммит, заметить его становится еще сложнее.

В моем случае это тоже выглядело не как отдельный очевидный вирусный файл, а как странные изменения внутри привычного JS-конфига.


Подмена последнего коммита

Самая неприятная часть — работа с Git-историей.

Исследователи OpenSourceMalware описывают Windows-артефакт temp_auto_push.bat, который берет последний коммит, сохраняет автора, email, сообщение и timestamp, затем делает git commit --amend и force-push.

Batch-файлы спрятаны через .gitignore

Batch-файлы спрятаны через .gitignore

Скриншот: batch-файлы добавлены в .gitignore, то есть они одновременно используются и прячутся от обычного git status.

На практике это означает: злоумышленник может добавить вредоносный код в последний коммит так, чтобы он выглядел почти как прежний.

Для разработчика это психологически опасно.

Ты открываешь историю и видишь вроде бы знакомый коммит. Автор — ты. Сообщение — твое. Время — похожее. Но внутри уже есть чужие изменения.

Подозрительный коммит выглядит как знакомый

Подозрительный коммит выглядит как знакомый

Скриншот: коммит выглядит подписанным знакомым автором, но внутри уже огромный подозрительный diff.

Поэтому в таких случаях нельзя доверять только ощущению «коммит вроде мой». Нужно смотреть diff, GitHub events, force-push, измененные файлы, активность по веткам и email-уведомления.


Почему это опасно не только для разработчиков

Может показаться, что это проблема только программистов.

Но это не так.

Если вредоносный код попадает в репозиторий, он может дальше попасть:

  • в CI/CD;

  • на staging;

  • на production;

  • в npm-пакет;

  • в open-source библиотеку;

  • в проект клиента;

  • на сервер, где есть платежные ключи, API-токены, доступы к базам и другим сервисам.

Если у разработчика на машине есть SSH-ключи, GitHub-токены, .env, доступы к облакам, браузерные пароли или криптокошельки, ущерб может выйти далеко за рамки одного репозитория.

Цель подобных атак часто не «сломать сайт». Цель — украсть доступы, секреты, криптовалюту и получить возможность распространяться дальше.


Какие признаки должны насторожить

После этой истории я бы очень серьезно относился к таким сигналам:

  • Git просит pull, хотя вы точно один работали в ветке.

  • Последний коммит изменился, но вы его не меняли.

  • В .gitignore появились странные новые строки.

  • В JS-конфигах появился непонятный обфусцированный код.

  • В .gitignore появился .bat-файл или другой скрипт, которого вы не добавляли.

  • В репозитории появились .bat, .cmd, .sh, .woff2 или другие неожиданные файлы.

  • GitHub присылает уведомления о push в то время, когда вы точно ничего не пушили.

  • В email-уведомлении подсвечен один GitHub-ник, а коммиты выглядят подписанными другим именем.

  • После смены паролей и ключей симптомы появляются снова.

  • Заражены только недавно активные ветки, а старые ветки не тронуты.

Подозрительная строка в .gitignore

Подозрительная строка в .gitignore

Скриншот: появление config.bat в .gitignore — отдельный красный флаг, даже если остальной diff кажется обычным.

Последний пункт тоже интересный. По нашим наблюдениям, злоумышленники трогали не все ветки подряд, а в основном те, где была недавняя активность. Например, если ветка не обновлялась полгода или год, ее могли не трогать. А вот активные ветки заражались.


Что делать, если вы нашли похожие симптомы

Я не специалист по incident response, поэтому это не универсальная инструкция. Но по опыту этой истории я бы действовал так:

  1. Остановить обычную работу с проектом.

  2. Не запускать npm install, yarn install, build или dev server в подозрительном репозитории.

  3. Зафиксировать доказательства: скриншоты, commit hash, diff, время, email-уведомления.

  4. Проверить последние коммиты и force-push события.

  5. Проверить все активные ветки, а не только main.

  6. Проверить JS-конфиги, .gitignore, .vscode/tasks.json, подозрительные .bat, .cmd, .sh.

  7. Считать секреты скомпрометированными, если они были доступны на машине или в окружении.

  8. Ротировать GitHub tokens, SSH-ключи, deploy keys, cloud/API/payment secrets.

  9. Проверить GitHub Apps, OAuth Apps, personal access tokens и SSH keys.

  10. Проверить машины всех разработчиков, которые работали с зараженным проектом.

  11. После очистки пересканировать репозитории еще раз через день или два.

Главное — не лечить только один репозиторий. Если заражение шло через машину разработчика или GitHub-контекст, проблема может быть сразу в нескольких проектах.


Два скрипта для первичной проверки

К статье я хочу приложить два скрипта.

Они не являются антивирусом и не дают стопроцентной гарантии безопасности. Это инструменты первичной диагностики, которые помогают быстро найти явные признаки проблемы.

Я выложил их вместе с примерами в отдельный репозиторий: amir-budaychiev/polinrider-detection-examples. Там есть короткий README с командами для macOS, Ubuntu и Windows, а также скриншоты и материалы из этой статьи.

Первый скрипт нужен для локальной проверки проектов.

Он проходит по локальным репозиториям и ищет:

  • признаки обфусцированного JavaScript-кода;

  • подозрительные вставки в JS-конфигах;

  • наличие .bat-файлов;

  • случаи, когда .bat-файл указан в .gitignore;

  • подозрительные изменения в типичных config-файлах.

Это важно, потому что в подобных атаках злоумышленники могут добавлять служебные batch-файлы и одновременно прятать их от Git через .gitignore.

Второй скрипт нужен для проверки логов.

Его можно запускать локально или на сервере. Он помогает искать следы подозрительной активности:

  • неожиданные команды;

  • обращения к подозрительным файлам;

  • следы запуска install/build-скриптов;

  • обращения к Git;

  • возможные следы работы вредоносного payload.

Еще раз: если скрипт ничего не нашел, это не значит, что система точно чистая. Но если он что-то нашел, это повод остановиться, сохранить артефакты и провести полноценное расследование.


Почему опыт не спасает автоматически

Эта история неприятна еще и потому, что она бьет по профессиональному самолюбию.

Когда ты давно в разработке, кажется, что базовые вещи ты точно заметишь. Но современные атаки устроены так, чтобы использовать не глупость, а рутину.

Разработчик каждый день устанавливает зависимости. Каждый день переключается между ветками. Каждый день видит конфиги. Каждый день доверяет GitHub, npm, IDE и локальным токенам.

Атака маскируется под эту нормальность.

Именно поэтому защита должна быть не только на уровне «я внимательный», а на уровне процессов:

  • минимальные доступы;

  • отдельные SSH-ключи;

  • GitHub audit logs;

  • branch protection;

  • запрет force-push в важных ветках;

  • review изменений в config-файлах;

  • периодические сканы;

  • ротация токенов;

  • осторожность с npm/yarn install в незнакомых проектах.


Что я вынес из этой истории

Мои главные выводы:

  • Странный Git-diff — это не мелочь.

  • .gitignore и JS-конфиги нужно проверять так же внимательно, как бизнес-логику.

  • Если заражен один разработчик, возможно, заражен уже не один.

  • Простая смена пароля не помогает, если источник заражения остался активным.

  • Нельзя доверять только автору коммита: нужно смотреть, кто пушил, когда и что именно изменилось.

  • Email-уведомления GitHub могут дать важный след.

  • Supply chain attacks опасны тем, что используют нормальные инструменты разработки.

  • После инцидента нужно ротировать не только GitHub-пароль, но и все секреты, которые могли быть доступны в окружении.


Вместо вывода

Эта история началась с обычного git push.

Не с алерта антивируса. Не с падения сервера. Не с письма от GitHub Security.

Просто Git сказал: «сначала сделай pull».

И именно эта маленькая странность помогла заметить большую проблему.

Если вы разработчик, тимлид, владелец небольшого проекта или человек, у которого на компьютере есть доступы к рабочим системам, относитесь к таким сигналам серьезно.

В 2026 году вредоносное ПО может жить не где-то «снаружи», а прямо внутри привычного developer workflow: в npm-пакете, VS Code extension, config-файле, build script или последнем коммите, который выглядит почти как ваш.

Почти…

P.S. — Если статья получит достаточно лайков или комментариев, я выпущу вторую часть — уже с техническим разбором самого обфусцированного скрипта: как он устроен, какие приемы использует и как постепенно раскручивать такой код до понятной логики.


Источники

ссылка на оригинал статьи https://habr.com/ru/articles/1040200/