Как безопасно хранить API keys в 2026 году
Но переменные окружения — это лишь базовый уровень для конфигурации с низким уровнем воздействия; production требует полноценной стратегии работы с секретами. Всё, что отправляется в код браузера, является публичным независимо от минификации или обфускации.
Поэтому храните каждое значение в соответствии с ущербом, который может причинить его утечка. Сделайте этот выбор исполнимым — от локальной разработки до отзыва. Мы определим обычные пути утечек и выберем уровень хранения. Затем будем получать секреты во время выполнения, установим политику ротации на основе риска и рассмотрим Git, CI/CD, браузеры и AI coding assistants.
Ваш key — это credential, а не безобидная строка
Но обычные рекомендации заканчиваются на .env и .gitignore. Это обеспечивает порядок в репозитории, но игнорирует runtime-доступ, логи CI и отзыв.
Поэтому классифицируйте значение по тому, что неавторизованный человек мог бы с ним сделать. Утёкший credential может открыть database или авторизовать платежи. В зависимости от разрешений он может раскрыть private data, изменить infrastructure или подписывать tokens. Публичный analytics identifier имеет иной профиль риска по сравнению с cloud credential с административным доступом.
Кроме того, доступное резюме 42Crunch State of API Security 2026 report сообщает, что отсутствие authentication было наиболее часто выявляемой уязвимостью API в 2025 году. В нём также отмечаются нарушения object-level и function-level authorization, а также ошибка, связанная с доверием к client: поведение frontend не может обеспечивать authorization.
Хранение секретов не исправит отсутствующую проверку authorization. Но оно может усложнить злоумышленнику получение credential.
Большинство утечек — обычные операционные ошибки
Большинство инцидентов с секретами скучны. Поэтому политика хранения должна выдерживать обычные debugging, deployment и collaboration.
Раскрытие в source control сохраняет копии с течением времени. Раскрытие во время выполнения показывает значения, загруженные process, container, pod или pipeline. Поэтому защищайте оба пути отдельно.
| Место | Как происходит утечка | Более безопасная практика |
|---|---|---|
| Git repository | Key захардкожен или файл .env закоммичен; clones, forks, caches и backups могут сохранить его | Игнорируйте локальные файлы с секретами. Сканируйте history. Запускайте incident response |
| Запущенный container | docker exec <ctr> env выводит окружение process пользователю с достаточным уровнем доступа | Ограничьте runtime-доступ и запрашивайте только необходимые значения |
| Kubernetes pod | kubectl exec <pod> -- env выгружает переменные окружения оператору с shell-доступом | Используйте workload identity и контролируемую injection или получение во время выполнения |
| CI/CD job | echo $SECRET_KEY или printenv отправляет значения в logs и artifacts | Маскируйте значения и ограничивайте permissions pipeline. Изолируйте production credentials |
| Chat или shared drive | Developers отправляют файлы .env, чтобы разблокировать deployment | Предоставляйте individual access через auditable secret service |
| Debugging | Configuration objects, request data или connection strings попадают в logs и exceptions | Редактируйте diagnostics. Исключайте secrets из error messages |
Переменные окружения не позволяют значениям попасть в source code и дают возможность использовать разные настройки для разных environments. Они также находятся в process memory и могут быть прочитаны пользователями или processes с достаточными привилегиями. Используйте их как границу репозитория, а затем добавляйте более сильную защиту там, где это оправдано возможным ущербом.
.env — это нижний уровень; secrets manager — верхний
Рассматривайте эту лестницу как увеличение пригодности для использования приложением, а не как универсальную шкалу зрелости:
- Захардкоженный source code: худший вариант. Значение попадает в version control, где history предназначена для сохранения изменений.
- Переменные окружения: полезная базовая мера. Они отделяют значения от application code и поддерживают конфигурацию для разных environments.
- Secrets managers: production choice для credentials с высоким уровнем воздействия. Они добавляют encrypted storage, access policies, expiry, rotation workflows и access review.
- Client-side encrypted notes: только для личной справки. Notes application может помочь отдельному человеку запомнить credential; server никогда не должен зависеть от того, что engineer скопирует его в production.
Как отмечает FloopFloop, «Environment variables are the floor; a proper secrets manager is the ceiling». Эта фраза запоминается, потому что различие имеет значение.
Manager добавляет SDKs или agents, identity policies, изменения deployment, обработку сбоев и operational ownership. Для локального prototype без production credentials и shared deployment это может быть ненужной формальностью. Используйте игнорируемый локальный файл, а затем пересмотрите решение до выхода в production.
На локальных машинах оставляйте .env доступным для чтения только application user и никогда не используйте его как механизм distribution team credentials.
Используйте один вопрос, чтобы решить, где должно находиться значение
Спросите:
Если это значение утечёт, сможет ли кто-то получить доступ или причинить ущерб?
Ответ «да» означает, что это secret; в production храните его в manager или эквивалентной контролируемой системе secret-injection. Ответ «нет» означает, что это обычная configuration. «Public» означает намеренно публичное значение, ограниченное по дизайну, и только в том случае, если provider предполагает такую открытость.
| Обычная configuration | Production secret |
|---|---|
APP_ENV и LOG_LEVEL | Database passwords и database URLs, содержащие credentials или предоставляющие connection access |
| Feature flags, ports и filesystem paths | Stripe secret keys и GitHub tokens |
| Hostnames и base URLs | Webhook secrets |
| Public OAuth client IDs, предназначенные для использования в browser | JWT signing keys |
| Analytics identifiers, разработанные для client use | TLS private keys, IAM keys и service-account credentials |
Backend Stripe credential остаётся чувствительным, поскольку его provider называет его API key. Analytics identifier, видимый в browser, может быть публичным, потому что его permissions и quota изначально рассчитаны на такую открытость.
Возьмём небольшое Node.js application, которое обрабатывает Stripe webhooks и сохраняет orders в PostgreSQL. APP_ENV и LOG_LEVEL описывают runtime behavior. STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET и DATABASE_URL могут авторизовать действия или раскрыть data. Поместите их в разные tiers.
Получайте секреты во время выполнения, а не через копирование и вставку
Для разных providers и languages аутентифицируйте workload, запрашивайте значения по именам, проверяйте их при startup и не допускайте их попадания в logs. Реализация меняется в зависимости от platform; граница остаётся прежней.
Node.js
Следующий provider-neutral pseudocode показывает границу; это не runnable SDK example. Замените secretManager.getMany client выбранного provider и настройкой workload-identity.
// Pseudocode: replace with your provider's SDK.
const config = await secretManager.getMany([
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"DATABASE_URL",
]);
for (const name of [
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"DATABASE_URL",
]) {
if (!config[name]) {
throw new Error(`Required secret is unavailable: ${name}`);
}
}
const stripe = createStripeClient(config.STRIPE_SECRET_KEY);
const database = createDatabase(config.DATABASE_URL);
// Pass only the credential or client each component needs.
// Never log config or include secret values in errors.
На этом этапе orders service получает все три чувствительных значения; APP_ENV и LOG_LEVEL остаются обычной deployment configuration. (См. гид по безопасности Node.js API.)
Python
Для Azure Microsoft документирует DefaultAzureCredential с SecretClient из azure-identity и azure-keyvault-secrets. Пропущенный vault_url должен поступать из non-secret deployment configuration.
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
credential = DefaultAzureCredential()
client = SecretClient(vault_url=vault_url, credential=credential)
stripe_secret_key = client.get_secret("StripeSecretKey").value
stripe_webhook_secret = client.get_secret("StripeWebhookSecret").value
database_url = client.get_secret("DatabaseUrl").value
if not all((stripe_secret_key, stripe_webhook_secret, database_url)):
raise RuntimeError("Required secrets are unavailable")
Рассматривайте failure retrieval как deployment или startup failure. Записывайте provider exceptions только через redacted, access-controlled error channel.
Go
Здесь manager.Get и OpenDatabase также являются placeholders для provider SDK и database package.
// Pseudocode: replace manager.Get and OpenDatabase with real clients.
secrets, err := manager.Get(ctx, []string{
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"DATABASE_URL",
})
if err != nil {
return fmt.Errorf("load required secrets: %w", err)
}
for _, name := range []string{
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"DATABASE_URL",
} {
if secrets[name] == "" {
return fmt.Errorf("required secret %s is unavailable", name)
}
}
db, err := OpenDatabase(secrets["DATABASE_URL"])
if err != nil {
return fmt.Errorf("open database: %w", err)
}
Передавайте narrowly scoped client или credential handle, а не всю configuration map. Это ограничивает случайный доступ внутри process.
Выбирайте manager, который может обеспечить ваша инфраструктура
Не покупайте secrets manager только потому, что comparison table называет его “best”. Выбирайте в следующем порядке:
- Сначала рассмотрите native service вашего cloud provider, если ваши workloads уже используют identity и monitoring этого cloud.
- Сопоставьте deployment model: hybrid infrastructure может оправдать Vault; developer-focused teams могут предпочесть более простой distribution workflow.
- Проверьте operating capacity: мощный service, который никто не может patch, monitor или recover, становится liability.
В CiphersSecurity comparison описаны общие варианты использования:
- HashiCorp Vault: complex hybrid environments, которым нужны dynamic secrets и team, готовая эксплуатировать Vault.
- AWS Secrets Manager: AWS-standardized teams, использующие native identity и deployment controls.
- Azure Key Vault: Azure workloads, использующие Microsoft Entra identities и Azure monitoring.
- Doppler или Infisical: developer-first workflows, которым нужна straightforward distribution между environments.
- Akeyless: organizations, оценивающие vaultless, zero-knowledge model.
Используйте их как statements о соответствии, а не как rankings. Как минимум требуйте encrypted storage, workload identity, least-privilege reads и auditable access. Требуйте поддержку expiry или rotation и CI/CD integration, исключающую попадание значений в output. Retrieval logging зависит от service и integration, поэтому проверьте, какие данные записывает выбранный path.
Ни один вариант не будет стоить меньше для любой архитектуры; измерьте setup time, incident response и developer friction в собственной environment.
Azure Key Vault демонстрирует паттерн контроля
В Microsoft Learn example создаётся vault с RBAC authorization и purge protection:
az keyvault create --name "<vault-name>" \
--resource-group "myResourceGroup" \
--enable-rbac-authorization true \
--enable-purge-protection true
Добавьте secret с документированным примером expiry в 180 дней:
az keyvault secret set \
--vault-name "<vault-name>" \
--name "MyApiKey" \
--value "<secret-value>" \
--expires "$(date -u -d '+180 days' +'%Y-%m-%dT%H:%M:%SZ')"
Этот синтаксис date предназначен для GNU/Linux. Пользователям macOS нужно адаптировать команду timestamp. Перед запуском замените имя vault, subscription, identity, region и organizational policies.
Практическая настройка затем назначает application роль Key Vault Secrets User, а не широкий administrative access. Рассмотрите отдельные vaults для каждого environment или application, если isolation и administration оправдывают это.
Настройте оставшиеся элементы следующим образом:
- Подпишитесь на события
SecretNearExpiryчерез Event Grid. - Отправляйте diagnostic logs
AuditEventв Log Analytics. - Настройте alert на unauthorized
SecretGetoperations. - Ограничьте network access с помощью Private Link или firewall rules.
- Используйте HTTPS endpoints service для всей communication.
Конкретные provider-specific names меняются, но control pattern обобщается: workload identity, narrow permissions, expiry, alerts, network restriction и access logs.
Ротация — это график на основе риска, а не магическое число
Напоминание в календаре с текстом «rotate key» мало помогает, если никто не может отозвать старое значение, проверить замену или узнать, кто получил к нему доступ.
Это рекомендуемая Blackhawk starting policy, а не universal standard:
- Credentials с высоким уровнем воздействия или часто раскрываемые credentials: ротируйте ежедневно или при каждом deployment, где это практически возможно, и отдавайте предпочтение short-lived или dynamic credentials.
- Human или long-lived production API keys: ротируйте каждые 30–90 дней с учётом provider limits и operational risk.
- Certificates: отслеживайте expiry и проверяйте их ежемесячно; обновляйте в соответствии с lifetime certificate.
- Signing и private keys: определите key-specific rollover plan, включая периоды, когда старые и новые версии могут работать одновременно.
- Service credentials с более низким риском: используйте до 180 дней как starting point, сокращая этот срок при более высоком impact.
Microsoft использует 180 дней в своём Azure example и документирует near-expiry notifications. Infisical описывает dynamic secrets как credentials, генерируемые по запросу с automatic expiration, что сокращает период между issuance и revocation.
Если provider не поддерживает overlap или automated rollback, не навязывайте daily rotation. Уменьшите permissions, сократите token lifetime, где это возможно, и задокументируйте фактический provider limit. Безопасность daily rotation зависит от rollback time, overlap support и credential usage. Это deployment facts, а не universal policy.
Используйте dual-key rollover, если provider его поддерживает:
- Выпустите replacement.
- Сохраните и разверните новую версию.
- Проверьте traffic и application health.
- Отзовите или отключите старый credential.
- Проверьте logs на продолжающееся использование старого значения.
Для application со Stripe и PostgreSQL оставьте старый Stripe key действующим, пока проверяется новый deployment, а затем отзовите его. Для PostgreSQL создайте replacement role, если database и deployment setup поддерживают overlapping credentials. В противном случае используйте maintenance window или отдельную role, подтвердите writes и отключите старый credential.
Браузер не может скрыть key от своего пользователя
Если secret попадает в browser JavaScript, source maps, network requests или mobile binary, исходите из того, что пользователь может его получить. Build step этого не меняет.
Если provider называет значение, передаваемое в browser, API key, рассматривайте permissions, а не label, как security boundary.
Используйте следующий flow:
- Browser аутентифицируется в вашем application.
- Ваш backend авторизует запрошенную operation.
- Ваш backend вызывает third-party API с server-side secret.
- Ваш backend возвращает только разрешённые data.
Если provider требует key, видимый в browser, используйте намеренно публичный key, ограниченный domain, origin, operation, quota и другими доступными механизмами. Privileged server credential должен находиться на server.
AI assistants усложняют обеспечение безопасной работы с секретами
Coding assistants добавляют места, куда developers могут вставить или откуда могут сгенерировать secret: prompts, context windows, configuration edits, generated patches, fixtures и chat transcripts.
Не помещайте secrets в prompts и test fixtures. Используйте placeholders и injected test credentials. Проверяйте generated configuration и patches на hardcoded credentials до их попадания в repository.
Добавьте repository и pipeline scanning с помощью TruffleHog, Gitleaks или GitHub Secret Scanning. Scanning обнаруживает ошибки; workload permissions ограничивают то, что эти ошибки могут раскрыть.
Перенесите существующие секреты за четыре прохода
Emergency branch: если credential раскрыт сейчас, немедленно отзовите его, когда provider поддерживает revocation. В противном случае ротируйте его и отключите раскрытую версию. Очистка repository выполняется после этого.
Normal migration: сначала проведите audit, затем перенесите значения с наиболее высоким impact.
1. Проверьте каждую копию
Ищите в source code, Git history, .env files, Dockerfiles и CI/CD configuration. Также проверьте build artifacts, logs и shared documents. Запустите TruffleHog, Gitleaks или GitHub Secret Scanning, затем вручную проверьте findings.
2. Сначала перенесите критические секреты
По возможности создайте replacement credentials. Поместите их в выбранный manager, предоставьте application узкую identity и измените application так, чтобы оно получало значения во время выполнения.
3. Очистите plaintext
Удалите значения из текущих files, logs, fixtures и pipeline output. Удаление строки — это housekeeping. Отзыв credential — это incident response.
Перезапись history может сократить вероятность повторного обнаружения, но не заменяет revocation. Исходите из того, что любой, кто клонировал repository, всё ещё может владеть старым значением.
4. Обсудите и улучшите процесс
Задокументируйте owner, purpose и permitted readers. Зафиксируйте expiry и rotation procedure, emergency revocation path и local-development workflow. Если developers всё ещё обмениваются .env files в chat, исправьте workflow, а не документируйте эту привычку.
Чек-лист безопасного хранения должен находиться в pull request
Используйте его как инструмент review:
- Может ли reviewer определить storage location?
- Использует ли workload managed identity, OIDC или эквивалентный mechanism?
- Явно ли указаны разрешённые secret names и readers?
- Указан ли revocation owner?
- Есть ли expiry или rotation signal?
- Какой access log подтверждает retrieval?
- Чисты ли browser bundles, prompts, fixtures, logs, Dockerfiles и Git history?
- Был ли отозван раскрытый credential?
Если на какой-либо вопрос ответ — «решим позже», не помещайте credential в production. Такова policy: используйте .env для configuration с низким уровнем воздействия, controlled manager — для секретов, способных причинить ущерб, и делайте revocation частью design, а не экстренной импровизацией.