Файлы .env — 6 ошибок, которые приводят к утечке секретов на GitHub

Обновлено

Большинство утечек .env не происходят из-за хакеров — они происходят из-за разработчиков, которые передавали файлы до того, как был настроен .gitignore, размещали .env.example с реальными значениями или позволяли фреймворку безнаказанно включать секреты сервера в клиентский JavaScript. Вот 6 ошибок, которые на самом деле происходят.

Файлы .env — 6 ошибок, которые приводят к утечке секретов на GitHub 1
Реклама · УДАЛИТЬ?

Доклад GitGuardian о состоянии распространения секретов 2023 года выявил более 12 миллионов секретов, внесённых в публичные репозитории GitHub. Большинство из них не были украдены — они были загружены разработчиками, которые действительно думали, что правильно обработали данные. Эти паттерны являются причинами.

1. Добавление .gitignore после первого коммита

.gitignore только предотвращает добавление неотслеживаемых файлы из стейджинга. Как только файл начинает отслеживаться — даже кратко — он попадает в историю git. Если вы создали .env, запустили git add . && git commit, а затем добавили .env к .gitignore после этого, файл остаётся в каждом коммите, предшествующем этой изменению.

Проверьте, есть ли он уже в истории:

git log --all -- .env

Если это возвращает коммиты, секреты уже есть в истории. Сначала замените учётные данные. Затем удалите файл из истории с помощью git-filter-repo (рекомендуемый заменитель git filter-branch):

pip install git-filter-repo
git filter-repo --path .env --invert-paths

Сделайте форс-пуш на все удалённые репозитории и уведомите коллег, чтобы они перезагрузили репозиторий. Коммиты существуют во всех клонах, созданных до очистки — включая любые автоматизированные системы CI, которые проверяли репозиторий.

2. Копирование .env в .env.example без очистки значений

Стандартная последовательность: создать .env с реальными значениями, затем скопировать его в .env.example чтобы показать коллегам, какие ключи требует проект. Ошибка возникает именно в копировании.

cp .env .env.example копирует всё — ключи и значения. И .env.example должна быть коммитирована. Это и есть цель. Реальные значения в .env.example записываются в репозиторий намеренно.

❌ Что окончательно попадает в git:

DATABASE_URL=postgres://admin:supersecretpassword@prod-db.example.com/appdb
STRIPE_SECRET_KEY=sk_live_51AbcDefGhiJklMnopQrstUvwx...
JWT_SECRET=my-actual-production-jwt-secret

✅ Что .env.example должно выглядеть:

DATABASE_URL=postgres://user:password@localhost:5432/appdb
STRIPE_SECRET_KEY=sk_live_YOUR_KEY_HERE
JWT_SECRET=generate-a-random-secret-min-32-chars

Создавать .env.example с плацебо-значениями сначала, коммитируйте его, затем скопируйте в .env и заполните реальными учетными данными — не наоборот.

3. Логирование process.env в обработчиках ошибок

Этот случай начинается как «быстрое отладка» во время инцидента и никогда не удаляется. Или он остаётся в общих обработчиках ошибок, которые кажутся безвредными.

// Classic debug line that makes it to production
console.log('Starting with config:', process.env);

// Generic error handler that dumps everything
app.use((err, req, res, next) => {
  logger.error({ config: process.env, error: err.message });
  res.status(500).json({ error: 'Internal server error' });
});

process.env на время выполнения содержит все переменные, загруженные dotenv, плюс системные переменные. Передача полного объекта логгеру означает, что он попадает в вашу систему логирования, сервис по отслеживанию ошибок (Sentry, Datadog, Rollbar) и, возможно, в уведомления по электронной почте или веб-хуки. Многие из этих сервисов передают данные в сторонние хранилища с собственными правами доступа.

Логируйте только те значения, которые необходимы для диагностики:

logger.error({
  nodeEnv: process.env.NODE_ENV,
  appVersion: process.env.APP_VERSION,
  error: err.message,
  stack: err.stack
});

4. Встраивание секретов в слои образа Docker

Два паттерна, которые навсегда встраивают секреты в историю образа Docker:

# Pattern 1: COPY bakes the entire .env into a layer
COPY .env .

# Pattern 2: ARG/ENV burns values into build metadata
ARG DATABASE_URL
ENV DATABASE_URL=$DATABASE_URL

Даже если вы удалите файл в более позднем слое (RUN rm .env), значение всё ещё доступно в истории образа. Любой, кто имеет доступ к извлечению образа, может выполнить:

docker history --no-trunc your-image:tag

и восстановить значения ARG, использованные во время сборки. Секреты Docker BuildKit — это правильный инструмент — они монтируют секреты во время сборки, не записывая их в слои образа:

# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=db_url     DATABASE_URL=$(cat /run/secrets/db_url) ./setup.sh

Для конфигурации во время выполнения введите переменные среды при запуске контейнера через docker run -e или environment: в Docker Compose, ссылаясь на переменные среды хоста — никогда не используйте жёстко заданные значения, никогда не используйте COPY‘документы с секретами.

5. Использование слабых плацебо-секретов, которые отправляются в продакшн

JWT_SECRET=secret, SESSION_SECRET=keyboard cat, APP_KEY=changeme, ENCRYPTION_KEY=1234567890abcdef. Эти значения начинаются как плацебо для разработки и иногда никогда не заменяются. Атакующие, пытающиеся подобрать подписи JWT, активно используют эти строки — они присутствуют в списках слов специально потому что они появляются в поиске на GitHub.

JWT, подписанный с помощью HS256 и слабого секрета, может быть взломан в режиме офлайн с помощью инструментов, таких как c-jwt-cracker. Один перехваченный действительный токен достаточно, чтобы подобрать секрет и создать произвольные токены.

Правильные секреты должны быть криптографически случайными, минимальной длины 32 байта. Генерируйте их до того, как они будут нужны — инструмент Генератор секретов окружения на IO Tools будет генерировать корректно случайные значения для распространённых секретов в .env (ключи JWT, секреты сессий, API-ключи) без необходимости настройки. Устанавливайте их с самого начала; не используйте плацебо и планируйте «исправить до продакшна».

6. Правила переменных среды в фреймворках, которые раскрывают секреты клиенту

Несколько популярных фреймворков используют префиксы переменных для определения видимости на клиенте или сервере. Ошибки в этом приводят к тому, что секреты попадают в JavaScript-бандл и передаются каждому браузеру, загружающему приложение — в открытом виде.

  • Next.js: только NEXT_PUBLIC_-префикс переменных включаются в клиентскую часть. Но секреты на сервере утечивают при передаче через getServerSideProps props — любое значение, возвращаемое в props сериализуется в HTML-страницу и доступно в исходном коде.
  • Vite: Переменные с префиксом VITE_ включаются в клиентский JS. Использование VITE_DATABASE_URL «для удобства» — это ошибка, которую на самом деле совершают разработчики.
  • Create React App: Все REACT_APP_ переменные попадают в клиентский бандл, без исключений. В системе CRA нет серверного времени выполнения — всё, что загружается, идёт в браузер.

Проверьте после сборки, поищите в выходной директории известные значения секретов:

grep -r "sk_live_" ./dist
grep -r "sk_live_" ./.next/static

Если найдены совпадения, эти секреты присутствуют во всех вкладках браузера. Сразу замените их и проверьте, что ещё было включено в бандл.

Одна привычка, которую стоит внедрять с самого начала

Перед созданием любого файла, который будет содержать секреты, настройте .gitignore сначала — не как последующий шаг. Первый коммит в любом новом репозитории должен быть .gitignore и .env.example с плацебо-значениями. Инструмент .gitignore Генератор создаст полный, фреймворк-специфический файл .gitignore менее чем за минуту.

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

Хотите убрать рекламу? Откажитесь от рекламы сегодня

Установите наши расширения

Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска

в Расширение Chrome в Расширение края в Расширение Firefox в Расширение Opera

Табло результатов прибыло!

Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!

Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?

новости с техническими моментами

Примите участие

Помогите нам продолжать предоставлять ценные бесплатные инструменты

Купи мне кофе
Реклама · УДАЛИТЬ?