- BrainTools - https://www.braintools.ru -
TL;DR; Разбираем базовую, но от этого не менее болезненную тему: что такое секреты, почему разработчики с завидным упорством продолжают их коммитить, где таятся главные дыры и как один забытый токен может обернуться миллионными убытками для бизнеса.
Представьте: ваш коллега забыл один токен в CI-конфиге личного форка. Один токен с правами на публикацию пакетов. Злоумышленник находит его во время того, как раннер на выделенном сервере выполняет деплой (пока что у злоумышленника нет доступа к другим частям), перехватывает и внедряет малварь напрямую в собираемый npm-пакет или Docker-образ компании. Во время следующего автоматического релиза заражённая библиотека уходит в продакшен тысячи B2B-клиентов. В код вшивается бэкдор, превращающий каждое клиентское приложение в точку входа. В итоге один утекающий токен разработчика приводит не к локальному инциденту, а к масштабному скандалу уровня SolarWinds и отзыву лицензий у компании.
Всем привет! Я Натан, техлид модуля Secrets в CodeScoring. Мы разрабатываем on-premise-решение для безопасной работы с кодом и инфраструктурой. В этой статье я расскажу, что мы подразумеваем под секретами, почему они упорно продолжают утекать и к каким катастрофам это приводит на практике. Статья будет полезна тимлидам, разработчикам и DevSecOps-инженерам, которые хотят навести порядок в своих репозиториях.
И чтобы не заканчивать на страшном — в конце статьи чек-лист [1]: как за пару минут проверить репозиторий на забытые секреты и что делать, если что-то нашлось. Спойлер: «просто удалить из кода» не поможет.

Давайте представим, что вы автоматически публикуете Python-пакеты во время очередного релиза:
import os
import subprocess
PACKAGE_NAME = "my-awesome-lib"
VERSION = "1.0.4"
PYPI_API_TOKEN = "pypi-AgEIcHlwaS5vcmcCAWIAAAYgxbyLvb9egSCECeOdB3qW3h4oXEoNC6kJI0NtaFOQlUY"
def publish_package():
print(f"Публикация версии {VERSION}...")
cmd = [
"twine", "upload",
"--username", "__token__",
"--password", PYPI_API_TOKEN,
"dist/*"
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("Пакет успешно опубликован!")
else:
print(f"Ошибка публикации: {result.stderr}")
if __name__ == "__main__":
publish_package()
Теперь посмотрите на код внимательно. Нас интересует вот эта строка: PYPI_API_TOKEN = "pypi-AgEIcHlwaS5vcmcCAWIAAAYgxbyLvb9egSCECeOdB3qW3h4oXEoNC6kJI0NtaFOQlUY".
Это токен доступа к PyPI — реестру пакетов Python. Грубо говоря, это ключ, который подтверждает: «Да, этот пакет публикует именно наша компания, а не кто-то другой».
В чём проблема? Токен написан прямо в коде, открытым текстом. Он не берётся, например, из хранилища. Он просто лежит в файле как обычная строка. А значит, любой, кто видит этот файл, видит и токен.
Если злоумышленник получит доступ к исходнику этого скрипта, то сможет опубликовать свой зловредный пакет от имени компании.
Или где-то в конфигурации пайплайна CI/CD всплывёт:
name: Publish Python Package to PyPI
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build and publish
env:
# ОШИБКА: Захардкоженный секрет вместо ${{ secrets.PYPI_TOKEN }}
TWINE_USERNAME: __token__
TWINE_PASSWORD: pypi-AgEIcHlwaS5vcmcCAWIAAAYgxbyLvb9egSCECeOdB3qW3h4oXEoNC6kJI0NtaFOQlUY
run: |
python -m build
twine upload dist/*
Здесь секрет прячется в блоке env шага «Build and publish»: TWINE_PASSWORD: pypi-AgEIcHlwaS5vcmcCAWIAAAYgxbyLvb9egSCECeOdB3qW3h4oXEoNC6kJI0NtaFOQlUY.
Как и в примере выше, API-токен лежит в конфигурации CI/CD в открытую. И дальше — тот же сценарий: любой, кто получит доступ к конфигурации, сможет совершить публикацию пакета от имени компании.
Разработчики часто забывают [2] добавить .env-файл в список исключений или подкладывают переменные с чувствительными данными в файл, и он вместе со всем проектом выкладывается в интернет. И вот ключ от продакшена уже лежит в открытом доступе.
Тот самый забытый пароль или API-ключ — это то, что называют секретами. Если проще: секрет — это любая строка, файл или ключ, зная который можно зайти туда, куда без него зайти нельзя, или увидеть то, что нельзя видеть всем.
К ним относятся не только пароли. Вот что ещё считается секретом:
токены доступа к облачным провайдерам, Telegram-ботам, платежным шлюзам типа ЮKassa;
креды для баз данных, например логины, пароли, connection strings;
приватные SSH- и GPG-ключи;
SSL/TLS-сертификаты.
Всё это цифровые артефакты, которые дают авторизованный доступ к защищённым ресурсам, инфраструктуре или данным.
И если эти данные попадут наружу, злоумышленнику не нужно будет эксплуатировать сложные уязвимости. Достаточно использовать найденный ключ. Проблема в том, что современная разработка невозможна без секретов. Они нужны для связи компонентов системы. И именно поэтому они так часто оседают там, где их быть не должно.
Обычно кажется, что утечки — это результат работы сложных APT-группировок. На деле же секреты просто лежат под ногами.
По данным GitHub [3], только за 2025 год в публичных репозиториях засветилось 29 миллионов секретов. Не украдено. Засветилось — самими разработчиками.
Вот топ-6 мест, откуда они утекают чаще всего:
Исходный код (хардкод). «Я захардкожу токен чисто для локальных тестов, а перед пушем в мастер уберу». Разработчик спешил, код прошёл ревью (потому что ревьюер смотрел на бизнес-логику, а не на константы), и токен навсегда вмёрз в историю гита. И вот тут ключевой момент: гит помнит всё. Удалили секрет из кода в следующем коммите? Он всё равно лежит в истории. «Удалить из кода» и «удалить из гита» — очень разные вещи. Стоит держать в голове, что секреты живут не в актуальной версии продукта, а во всех версиях, которые записаны где-либо: в коммитах, в бэкапах, на дисках сотрудников. В некотором смысле это «режиссёрская версия» вашего продукта, которую жаждут увидеть не самые желанные фанаты.
Бинарник. Даже тут секрет не будет в безопасности. Если секрет — это просто строка в коде, компилятор поместит её в раздел со статическими данными внутри исполняемого файла. Запутывание, или, по-другому, обфускация, тоже не спасает. Такие данные выделяются высокой энтропией — их легко найти в файле. А во время исполнения секрет всё равно должен появиться в памяти [4] в исходном виде: иначе программа просто не сможет им воспользоваться. Вот там его и «ловят» через дамп RAM. Исключение — аппаратные среды доверенного исполнения, так называемые анклавы (например, Trusted Platform Module и производные технологии). Но даже там, без корректной настройки и разработки вокруг этой технологии, есть программные и аппаратные способы достать «зашитый» секрет.
Файлы окружения (.env). Файлы .env придумали именно для того, чтобы отделить конфигурацию от кода. Но стоит кому-то забыть добавить .env в .gitignore, как весь набор кредов от продакшена улетает в общий репозиторий. А дальше — как в пункте про хардкод: даже если спохватиться через час, удалить файл и добавить его в .gitignore — он уже в истории коммитов. Без перезаписи истории секрет не «спрятать».
Логи пайплайнов CI/CD. Скрипты сборки постоянно работают с секретами, чтобы задеплоить приложение. Ошибка [5] в bash-скрипте вроде echo $SECRETS_JSON или падение пайплайна с подробным трейсбеком могут распечатать ключи прямо в логи GitLab CI или Jenkins, доступ к которым часто есть у широкого круга сотрудников.
Мессенджеры и таск-трекеры. «Скинь пароль от тестовой БД» — «Держи: …» Знакомо? Довольно часто секреты пересылаются в мессенджерах. И если аккаунт одного сотрудника когда-нибудь скомпрометируют, вся эта коллекция становится золотой жилой для атакующего.
Docker-образы. Частая ошибка при сборке: копирование конфигурационных файлов вместе с секретами внутрь образа инструкцией COPY . .. Даже если контейнер удалит файл на старте в следующем слое, секрет навсегда останется в истории слоёв Docker-образа. С образами ситуация похожа на хардкод, только вместо фрагментов и версий кода мы имеем слоёный пирог, который содержит «слепки» состояний образа во время его сборки, где и могут задерживаться секреты. Например, скопировали .env на слое № 3, удалили на слое № 4 — он всё равно лежит в слое № 3.
Помимо этих «классических» сценариев, с ростом участия ИИ в разработке секреты начинают утекать способами, о которых пару лет назад никто бы не подумал:
Prompt Injection. Атака, с помощью которой мы можем вынудить агента прочитать свою память и достать оттуда секрет.
Cross-Tenant Memory Leakage (утечка между пользователями). Похожий по смыслу на вариант выше способ потерять секрет. В RAG-системах, где ИИ отвечает на вопросы, опираясь на базу знаний компании, один пользователь может «вытянуть» данные другого. Секреты, попавшие в документы или контекст одного сотрудника, оказываются доступны второму через общий поиск по базе.
Multi-Agent Tool Use & Shared State (общая память у нескольких агентов). Представьте двух агентов: один общается с пользователем, второй работает с чувствительными данными. У них общая память. Агент, который «болтает» с человеком, может случайно «проболтаться» или по хитрому запросу со стороны человека выдать секреты из той части, где работают с чувствительными данными.
Конфигурации MCP (Model Context Protocol). Это протокол, по которому ИИ-агенты обмениваются ресурсами и инструментами. Файлы конфигураций контекста (например, mcp.json) часто сохраняют токены доступа к сервисам прямо в структуры памяти / локальных конфигов агента. Когда агенты общаются через общие серверы или прокси, эти токены могут автоматически подмешиваться в запросы вовне или контекст ответов для других пользователей системы.
Разработчики обычно пишут в инструкциях к своим моделям: «Не смотри в .env-файлы» или «Игнорируй папку config». Но запрет в промпте — это не технический барьер, а просьба. А языковые модели вероятностны по своей природе: они следуют таким просьбам не всегда, а «почти всегда». 99 % времени модель ведёт себя образцово — пока один злополучный промпт или переполнение контекста не заставит её забыть о запретах. И вот тогда секрет оказывается на свободе.
Ну утекли и утекли, в чём проблема-то? Отзовём токен, перевыпустим новый, удалим пароль из репозитория (не редактируя дерево коммитов). Однако злоумышленники автоматизируют весь цикл атаки и знают, что инцидент рано или поздно заметят, поэтому нужно успеть в уходящий поезд.
Яркий пример — инцидент с Trivy в марте 2026 года [6]: злоумышленники использовали украденные CI/CD-секреты, чтобы за несколько часов опубликовать вредоносные релизы популярного сканера уязвимостей во все основные реестры. Тысячи пайплайнов автоматически подтянули малварь раньше, чем команда успела отреагировать.
К тому моменту, когда вы обнаружите утечку и побежите отзывать ключ, атакующий уже закрепился внутри: поднял бэкдоры, создал новые аккаунты, скопировал данные. Поэтому чаще всего утечка секрета — это не то, с чем можно справиться «после».
К чему это приводит на практике? Последствия утечек можно разделить на три категории: деньги, данные, смерть бизнеса. Разберём по порядку.
Финансовый ущерб и захват инфраструктуры
Злоумышленники мониторят публичные репозитории 24/7. Утекший токен AWS или Yandex Cloud парсится ботами в среднем за 2–3 минуты. Именно так происходит большинство захватов инфраструктуры. Получив ваш API-токен, автоматика мгновенно закрепляется внутри: поднимает инстансы, создаёт новые сервисные аккаунты, меняет политики доступа. К моменту, когда вы заметите странное в биллинге, отзывать токен будет уже поздно — у атакующего есть собственные ключи. Дальше — шантаж, продажа доступа или просто счёт на миллионы.
Но это ещё не всё. Современные группировки вымогателей больше не просто шифруют диски, а целенаправленно охотятся за секретами в GitLab/GitHub и облаках. В 2025–2026 годах группировка Crimson Collective атаковала [7] компании (как в случае с GitLab Red Hat или базами данных провайдера Brightspeed) через утекшие токены и конфиги: получив доступ к внутреннему GitLab или базам данных, они скачивали терабайты данных и шантажировали жертв публичным сливом.
Утечки данных и юридические последствия
Четыре показательные истории последних лет:
Кейс Toyota (2022). Выяснилось, что ключ доступа к серверу данных Toyota лежал в публичном репозитории на GitHub почти 5 лет! Разработчик-субподрядчик случайно залил туда кусок кода. Потенциально были скомпрометированы данные около 300 000 клиентов.
Кейс «Яндекса» (2023). В свободный доступ попал дамп внутренних Git-репозиториев компании объёмом 44,7 ГБ. По результатам расследования выяснилось [8], что в коде содержались захардкоженные секреты, сервисные ключи и тестовые креды. А причиной стало нарушение внутренних ИБ-политик при работе с кодом. Наглядный пример того, почему даже наглухо закрытый внутренний монорепозиторий нельзя считать безопасным местом для хранения секретов.
Атаки на Supply Chain и цепочки доверия (кейсы Trivy, LiteLLM, Checkmarx) (2026). В 2026 году секреты начали утекать не только из вашего кода, но и из инструментов, которым вы доверяете. Когда компрометируется токен популярной библиотеки или DevSecOps-сканера, под ударом оказываются не отдельные разработчики, а сотни компаний, использующих эти инструменты в своих CI/CD-пайплайнах. Один скомпрометированный секрет превращается в атаку на всю цепочку поставок (Supply Chain). Так произошло [9] со сканером уязвимостей Trivy, прослойкой для работы с LLM LiteLLM и компонентами Checkmarx: злоумышленники получали токены на публикацию, внедряли малварь в очередные релизы, и тысячи пайплайнов автоматически подтягивали заражённые версии.
ChainDrop (2026). В августе 2026 года исследователи из Unit 42 описали червя ChainDrop, который заразил сотни npm-пакетов [10]. Червь дампил память CI-раннеров GitHub Actions, крал npm-токены и автоматизированно перепубликовал заражённые версии популярных библиотек.
ИИ-бум и новые риски (2025–2026). Спешка в гонке ИИ-технологий сделала секреты ещё уязвимее. Исследование Wiz показало [11], что 65 % компании из Forbes AI 50 случайным образом выложили в публичный доступ ключи доступа и токены. А массовый переход на вайб-кодинг и ИИ-ассистенты только подлил масла в огонь: разработчики всё чаще коммитят сгенерированные нейросетью конфигурационные файлы (вроде mcp.json или .env) с захардкоженными ключами от OpenAI, Hugging Face и облачной инфраструктуры.
Если в утечке оказались персональные данные клиентов, подключаются два ключевых закона. ФЗ-152 «О персональных данных» [12] требует защищать ПДн и уведомлять Роскомнадзор об утечках. А ФЗ-149 «Об информации, информационных технологиях и о защите информации» [13] регулирует общие требования к защите информации и меры против утечек. Плюс обязанность уведомить пострадавших пользователей и провести внутреннее расследование. Один забытый токен — и вот вам не только технический инцидент, но многомиллионные штрафы, суды и репутационный ущерб.
Полное уничтожение бизнеса
Штрафы и суды — это ещё полбеды. Бывает, что после утечки бизнес просто перестаёт существовать.
Кейс Code Spaces (2014). Сервис хостинга кода Code Spaces столкнулся [14] с тем, что атакующий получил доступ к их облачной панели через украденные ключи и потребовал выкуп. Ему отказали. Тогда он просто нажал «Удалить всё»: базы данных, бэкапы, виртуальные машины. Процветающий бизнес был уничтожен за один день.
Почему, зная о таких рисках, мы продолжаем допускать утечки? Причина — Time to Market. Бизнес требует фич, разработчики торопятся. Иногда это банальная халатность. А с приходом ИИ скорость разработки и поставки увеличивается, иногда на порядки. Однако количество внимания [15] человека остаётся неизменным, что усугубляет ситуацию с потенциальными утечками.
К тому же существует ложное чувство безопасности: «Это же наш закрытый on-premise GitLab, кто тут этот ключ найдёт?» Но внутренние репозитории текут так же часто — через обиженных уволенных сотрудников, скомпрометированные VPN-доступы или взломанные ноутбуки.
Что можно сделать прямо сейчас, чтобы убедиться, что у вас нет секретов в коде? Начать с очевидного:
Проверяем .gitignore. Убедитесь, что конфигурационные файлы и окружения (.env, .env.local, *.pem, id_rsa, *.key, mcp.json) внесены в исключения до первого коммита.
По-grep-ать Git. Для быстрой проверки локального репозитория можно использовать git + grep: git log -p | grep -E -i "(pypi-|ghp_|aws_secret|passwords*=)"
Использовать инструмент злоумышленника: docker run --rm -v $(pwd):/path zricethezav/gitleaks:latest detect --source="/path" -v
Нашли секрет?
Не паникуйте! Используйте следующий алгоритм:
Ротируйте секрет. Отзовите ключ или токен, выпустите новый и замените его в местах, где он использовался.
Удалите его из истории. Недостаточно будет удалить секрет из кода и запушить правку — нужно удалить его из истории. Для этого можно редактировать дерево вручную либо использовать git-filter-repo или BFG Repo-Cleaner. И проверьте, что в самом последнем коммите не содержится секрет, поскольку инструменты часто не трогают его, чтобы не убить продуктовую развёртку.
Уведомите ответственных. К любой потенциальной утечке лучше отнестись как к реализованному риску: считайте, что секрет уже побывал в чужих руках.
Надеяться на внимательность при код-ревью бессмысленно — глаза замыливаются. Процесс поиска утекающих секретов необходимо автоматизировать. А те, которые не утекают, должны правильно обрабатываться. В корпоративной среде эту задачу закрывают решения класса DLP (Data Loss Prevention) и SM (Secret Management).
В контексте разработки это означает:
автоматизированный анализ кода (SAST) и сканирование истории коммитов;
интеграцию проверок в CI/CD-пайплайны — чтобы секрет не доехал до продакшена;
отлов секретов через pre-commit-хуки до попадания в репозиторий;
хранение, передачу и ротацию секретов защищённым способом.
А что дальше?
Как именно выстроить такой процесс, встроить проверки в продукт и не сойти с ума от ложных срабатываний? Если вам это болит и вы хотите, чтобы мы рассказали об этом в следующих статьях, — напишите в комментариях. О чём именно: про инструменты, про процесс внедрения, про конкретные кейсы?
И читайте наши статьи, чтобы мы не читали о вас в новостях!
Что ещё почитать в наших блогах:
Автор: nouhadonosor
Источник [20]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/35459
URLs in this post:
[1] чек-лист: https://@%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82
[2] забывают: http://www.braintools.ru/article/333
[3] По данным GitHub: https://www.techradar.com/pro/security/over-29-million-secrets-were-leaked-on-github-in-2025-and-ai-really-isnt-helping?spm=a2ty_o01.29997173.0.0.3db055fbA7RwAt
[4] памяти: http://www.braintools.ru/article/4140
[5] Ошибка: http://www.braintools.ru/article/4192
[6] инцидент с Trivy в марте 2026 года: https://github.com/aquasecurity/trivy/discussions/10425
[7] атаковала: https://www.malwarebytes.com/blog/news/2026/01/one-million-customers-on-alert-as-extortion-group-claims-massive-brightspeed-data-haul
[8] выяснилось: https://yandex.ru/company/news/30-01-2023
[9] произошло: https://www.kaspersky.com/blog/critical-supply-chain-attack-trivy-litellm-checkmarx-teampcp/55510/
[10] заразил сотни npm-пакетов: https://unit42.paloaltonetworks.com/chaindrop-npm-worm-analysis/
[11] показало: https://www.securitymagazine.com/articles/102008-65-of-the-forbes-ai-50-list-leaked-sensitive-information
[12] ФЗ-152 «О персональных данных»: https://www.consultant.ru/document/cons_doc_LAW_61801/
[13] ФЗ-149 «Об информации, информационных технологиях и о защите информации»: https://www.consultant.ru/document/cons_doc_LAW_61798/
[14] столкнулся: https://www.breaches.cloud/incidents/codespaces/
[15] внимания: http://www.braintools.ru/article/7595
[16] Как мы в CodeScoring модель для поиска секретов готовили: https://habr.com/ru/companies/codescoring/articles/1019956/
[17] Добавляем паранойи: двойное шифрование секретов: https://habr.com/ru/companies/flant/articles/962466/
[18] Трансформер в on-premise AppSec: как мы встроили ML-модель для классификации секретов в продукт без GPU: https://habr.com/ru/companies/codescoring/articles/1040968/
[19] Даёшь самоуправление! Управляем конфигурацией HashiСorp Vault изнутри, опираясь на Git и кворум подписей: https://habr.com/ru/companies/flant/articles/1014914/
[20] Источник: https://habr.com/ru/companies/flant/articles/1081716/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1081716
Нажмите здесь для печати.