~/wiki / bezopasnost / api-keys-secure-storage-env-secrets

Как безопасно хранить API-ключи в проекте: .env, GitHub Secrets, серверные переменные

Основной чат

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

$ cd раздел/ $ join vibe dev
Как безопасно хранить API-ключи в проекте: .env, GitHub Secrets, серверные переменные - обложка

В 2024 году компания GitGuardian обнаружила в публичных репозиториях GitHub более 12 миллионов секретов — API-ключей, паролей, токенов. Большинство из них попали туда случайно: разработчик просто забыл убрать ключ перед коммитом.

Последствия бывают разными. Кому-то списывают деньги за чужие запросы к OpenAI. Кому-то взламывают базу данных. Кому-то приходит счёт на несколько тысяч долларов за облачные ресурсы, которые кто-то использовал для майнинга.

Хорошая новость: защититься несложно. Нужно один раз выстроить правильную привычку — и большинство проблем исчезнет.


Почему нельзя хранить ключи в коде

Начнём с самого важного, потому что у новичков часто возникает вопрос: «Ну и что, если ключ в коде? Репозиторий же приватный».

Приватный репозиторий — не защита. Достаточно одной случайной смены видимости, одного взломанного аккаунта участника, одного неверного клика — и всё. Публичные репозитории сканируются ботами автоматически в течение секунд после появления коммита.

Git помнит всё. Даже если вы удалили ключ из файла и сделали новый коммит — в истории он остался. git log покажет все предыдущие версии файла. Удаление из текущего кода не удаляет из истории.

Команда видит ключи. Когда в коде написано const apiKey = "sk-abc123" — этот ключ видит каждый, кто клонирует репозиторий. Даже если вы доверяете всей команде, это плохая практика: ключи смешиваются, непонятно чей ключ используется, ротация превращается в кошмар.

Правило простое: секрет в коде — это уже не секрет.


Переменные окружения: основа основ

Правильное место для секретов — не файлы с кодом, а переменные окружения (environment variables). Это пары «имя=значение», которые операционная система предоставляет запущенному процессу. Код читает их в момент запуска — не хранит их в себе.

Читать переменные окружения просто:

javascript
// Node.js
const apiKey = process.env.OPENAI_API_KEY;
const dbUrl = process.env.DATABASE_URL;
python
# Python
import os
api_key = os.getenv('OPENAI_API_KEY')
db_url = os.getenv('DATABASE_URL')

Откуда берутся эти переменные — зависит от окружения. Локально — из .env файла. На сервере — из настроек хостинга. В CI/CD — из GitHub Secrets. Код одинаков везде, секреты — разные в каждом окружении.


.env файл: для локальной разработки

.env — это простой текстовый файл в корне проекта, где вы перечисляете переменные окружения для локальной работы.

bash
# .env
OPENAI_API_KEY=sk-proj-abc123...
DATABASE_URL=postgresql://localhost:5432/myapp
STRIPE_SECRET_KEY=sk_test_xyz789...
TELEGRAM_BOT_TOKEN=123456:ABCdef...

Библиотека dotenv читает этот файл при запуске и загружает переменные:

bash
# Node.js
npm install dotenv
javascript
// Первая строка в точке входа (index.js, app.js)
require('dotenv').config();

// Теперь process.env содержит всё из .env
console.log(process.env.OPENAI_API_KEY); // работает
bash
# Python
pip install python-dotenv
python
from dotenv import load_dotenv
import os

load_dotenv()  # читает .env файл
api_key = os.getenv('OPENAI_API_KEY')

Самое важное: .env в .gitignore

Сразу после создания .env — добавьте его в .gitignore. Это список файлов и папок, которые Git игнорирует и не включает в коммиты.

bash
# .gitignore
.env
.env.local
.env.production
.env.*.local

Проверьте, что файл действительно игнорируется:

bash
git status  # .env не должен появляться в списке
git check-ignore -v .env  # должен показать: .gitignore:1:.env

.env.example: шаблон для команды

Если .env нельзя коммитить — как другие разработчики узнают, какие переменные нужны? Для этого создают .env.example — файл с теми же именами переменных, но без реальных значений. Его коммитить можно и нужно.

bash
# .env.example — коммитится в репозиторий
OPENAI_API_KEY=           # ключ от OpenAI, получить на platform.openai.com
DATABASE_URL=             # строка подключения к PostgreSQL
STRIPE_SECRET_KEY=        # тестовый ключ Stripe (sk_test_...)
TELEGRAM_BOT_TOKEN=       # токен от @BotFather

Новый разработчик клонирует репозиторий, копирует файл и заполняет своими значениями:

bash
cp .env.example .env
# открывает .env и заполняет значения

GitHub Secrets: для CI/CD и деплоя

Когда нужно запустить деплой или тесты через GitHub Actions — переменные окружения нужны уже не на вашем компьютере, а на серверах GitHub. Для этого используются GitHub Secrets.

Как добавить секрет

  1. Откройте репозиторий на GitHub
  2. Settings → Secrets and variables → Actions
  3. Нажмите «New repository secret»
  4. Введите имя (например OPENAI_API_KEY) и значение
  5. Сохраните

После этого значение зашифровано и не отображается даже в интерфейсе — только звёздочки.

Использование в GitHub Actions

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to server
        env:
          # Переменные берутся из GitHub Secrets
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: |
          # Здесь ваши команды деплоя
          npm install
          npm run build
          npm run deploy

GitHub Secrets автоматически маскируются в логах — если значение случайно попадёт в вывод команды, оно будет заменено на ***.

Environments: разные секреты для dev и prod

Если у вас несколько окружений (staging, production) — для каждого можно создать отдельный набор секретов через GitHub Environments:

Settings → Environments → New environment → добавить секреты

yaml
jobs:
  deploy-production:
    environment: production  # использует секреты из окружения production
    steps:
      - env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }} # production-значение

Серверные переменные: на хостинге и VPS

Когда приложение развёрнуто на сервере — там тоже нужно задать переменные окружения. Способ зависит от платформы.

Timeweb Cloud / VPS с PM2

Если запускаете Node.js через PM2:

javascript
// ecosystem.config.js
module.exports = {
    apps: [{
        name: 'myapp',
        script: './index.js',
        env: {
            NODE_ENV: 'production',
            // НЕ пишите секреты здесь — этот файл попадёт в репозиторий
        },
        env_file: '.env.production', // читать из отдельного файла
    }]
};

Лучше создать на сервере отдельный .env.production прямо в командной строке:

bash
# На сервере через SSH
nano /var/www/myapp/.env.production
# Вводите значения, сохраняете
# Права только для владельца:
chmod 600 /var/www/myapp/.env.production

Docker и docker-compose

yaml
# docker-compose.yml — НЕ пишите секреты напрямую
services:
  app:
    image: myapp
    env_file:
      - .env.production  # файл на сервере, не в репозитории

Или передавайте через отдельный файл секретов:

bash
# На сервере создаёте файл с секретами
echo "OPENAI_API_KEY=sk-..." >> /etc/myapp/secrets.env
docker run --env-file /etc/myapp/secrets.env myapp

Vercel, Railway, Render и другие PaaS

Все современные платформы для деплоя имеют интерфейс для переменных окружения. Это самый простой и безопасный вариант:

Vercel: Project → Settings → Environment Variables

Railway: Project → Variables

Render: Service → Environment

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


Что нельзя коммитить: полный список

Сохраните этот раздел и добавьте в .gitignore каждого проекта.

bash
# .gitignore — секреты и конфиденциальные файлы

# Файлы переменных окружения
.env
.env.local
.env.development
.env.production
.env.staging
.env.*.local

# Ключи и сертификаты
*.pem
*.key
*.p12
*.pfx
id_rsa
id_ed25519
*.cert

# Конфиги с секретами (часто забывают)
config/secrets.yml
config/database.yml   # если содержит пароли
secrets.json
credentials.json      # Google Cloud credentials
service-account.json  # Firebase / GCP service account

# Кошельки и ключи криптовалют (если работаете с Web3)
*.keystore
wallet.json

Помимо очевидного .env, особое внимание обратите на:

credentials.json и service-account.json — файлы сервисных аккаунтов Google Cloud и Firebase. Очень часто утекают, дают полный доступ к проекту.

*.pem и *.key — приватные ключи SSL-сертификатов и SSH. Их компрометация критична.

Конфиги баз данных — в некоторых фреймворках (Ruby on Rails, Laravel) файлы конфигурации содержат реальные пароли к БД.


Как проверить, не утекли ли ключи уже

Перед тем как считать репозиторий чистым — стоит проверить историю коммитов. Ключ мог попасть туда несколько месяцев назад.

git log — быстрый поиск по истории

bash
# Ищем строки, похожие на секреты, во всей истории репозитория
git log -p --all | grep -i "api_key\|secret\|password\|token\|sk-\|Bearer"

truffleHog — автоматический сканер

bash
# Устанавливаем
pip install trufflehog
# или через brew:
brew install trufflehog

# Сканируем репозиторий
trufflehog git file://. --only-verified

truffleHog знает форматы ключей сотен провайдеров — OpenAI, Stripe, AWS, GitHub и других. Он не просто ищет слово «key», а проверяет паттерн реального значения.

gitleaks — ещё один популярный сканер

bash
# Скачать с github.com/gitleaks/gitleaks
gitleaks detect --source . -v

GitHub сам сканирует публичные репозитории

Если репозиторий публичный — GitHub уже ищет в нём секреты автоматически (функция Secret Scanning). При обнаружении вам приходит уведомление на email. Некоторые провайдеры (Stripe, OpenAI, GitHub) получают уведомление и автоматически инвалидируют ключ.


Что делать если ключ уже попал в репозиторий

Плохая новость: удаление файла или строки коммитом не поможет. Ключ остался в истории.

Хорошая новость: есть правильный порядок действий.

Шаг 1: немедленно отозвать ключ. Идите в панель провайдера и отзовите скомпрометированный ключ прямо сейчас. Не через час — сейчас. Если репозиторий был публичным хоть секунду — считайте ключ скомпрометированным.

Шаг 2: проверить логи на подозрительные запросы. Большинство провайдеров показывают историю использования ключа. Посмотрите, не было ли обращений с неизвестных IP.

Шаг 3: сгенерировать новый ключ и добавить в правильное место. Новый ключ — только в переменные окружения, не в код.

Шаг 4: почистить историю git (опционально, сложно). Если репозиторий приватный и вы уверены что никто не успел скопировать — можно переписать историю через git filter-repo. Но это сложная операция, требующая координации всей команды. Проще считать ключ утёкшим и просто сменить его.

bash
# Установить git-filter-repo
pip install git-filter-repo

# Удалить все вхождения строки с ключом из истории
git filter-repo --replace-text <(echo 'sk-abc123...==>REMOVED')

После этого нужно сделать git push --force — что перепишет историю на сервере. Все, кто клонировал репозиторий, должны перекачать его заново.

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


Дополнительный уровень: Vault и менеджеры секретов

Для небольших проектов .env и GitHub Secrets полностью достаточны. Но если проект растёт — появляются дополнительные инструменты.

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

AWS Secrets Manager / Parameter Store — аналог для AWS-инфраструктуры. Секреты хранятся в облаке, ротируются автоматически, права настраиваются через IAM.

Doppler, Infisical — более простые SaaS-альтернативы Vault. Синхронизируют переменные окружения между окружениями, интегрируются с CI/CD, есть бесплатные тарифы.

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


Быстрый старт: что сделать прямо сейчас

Если вы прочитали статью и хотите сразу привести проект в порядок — вот последовательность:

bash
# 1. Создать .env с реальными значениями
touch .env

# 2. Добавить .env в .gitignore
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore

# 3. Создать .env.example с пустыми значениями
cp .env .env.example
# Открыть .env.example и очистить все значения, оставив только имена

# 4. Проверить что .env не отслеживается git
git check-ignore -v .env

# 5. Если .env уже был добавлен в git — удалить из отслеживания
git rm --cached .env

# 6. Заменить все ключи в коде на process.env.НАЗВАНИЕ
# Было: const key = "sk-abc123"
# Стало: const key = process.env.OPENAI_API_KEY

# 7. Просканировать историю
git log -p --all | grep -iE "sk-|api[_-]?key|secret|password|token" | head -50

Чеклист безопасного хранения секретов

plaintext
Локальная разработка:
☐ .env файл создан в корне проекта
☐ .env добавлен в .gitignore
☐ .env.example с пустыми значениями добавлен в репозиторий
☐ Нет ни одного реального ключа в файлах с кодом

Git-история:
☐ Просканирована история на наличие секретов (git log -p или truffleHog)
☐ Если найдены — ключи отозваны у провайдера

CI/CD:
☐ Секреты добавлены в GitHub Secrets (не в файлы workflow)
☐ В .yml файлах нет реальных значений — только ${{ secrets.NAME }}

Продакшен-сервер:
☐ Переменные заданы через интерфейс хостинга или файл вне репозитория
☐ Файл с секретами имеет права 600 (только владелец)
☐ Для dev и production используются разные ключи

Общее:
☐ Никто кроме нужных людей не имеет доступа к production-ключам
☐ Есть план действий при компрометации: где отозвать, где сменить

Итог

Безопасное хранение секретов — это не про параноию, а про привычку. Один раз настроить .env и .gitignore, добавить GitHub Secrets для CI/CD — и большинство проблем исчезает.

Главные правила, которые стоит запомнить:

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

Если хотите проверить себя прямо сейчас — откройте любой свой проект и поищите в коде строки api_key =, secret =, password =. Если нашли реальные значения — пора исправить.


FAQ

Можно ли хранить .env в приватном репозитории? Технически можно, но это плохая практика. Приватный репозиторий может стать публичным по ошибке. Кроме того, все участники репозитория получат доступ к production-ключам, что нежелательно.

Как передать .env новому разработчику в команде? Через защищённый канал — зашифрованное сообщение в мессенджере, 1Password, Bitwarden для команд. Не через email и не через Telegram в открытом виде. В долгосрочной перспективе — менеджер секретов типа Doppler или Infisical.

Нужно ли скрывать ключи во фронтенде (React, Vue)? Да, и это отдельная сложная тема. Любой ключ, который попадает в JavaScript-бандл, виден пользователю через DevTools. Для фронтенда используйте только ключи с ограниченными правами (только чтение, привязанные к домену). Ключи с широкими правами должны быть только на бэкенде.

Что делать если не уверен, утёк ключ или нет? Действовать как будто утёк — отозвать и сгенерировать новый. Это занимает 5 минут. Проверять и сомневаться — проигрышная стратегия.

Переменная окружения задана, но приложение её не видит — почему? Скорее всего, dotenv загружается после кода, который использует переменную. Убедитесь, что require('dotenv').config() или load_dotenv() — первые строки в точке входа приложения, до любых других импортов.


Июнь 2026.

$ cd ../ ← назад к Безопасность