Как делать11 октября 2026 г.9 мин чтения
Docker и деплойШаг 5 из 8

Секреты и переменные окружения: как не утечь ключами

Коротко

Секреты — пароли, ключи API, токены и строки подключения — не пишут в код: приложение читает их из переменных окружения, локально — из файла .env, который закрыт .gitignore, а в репозитории лежит только шаблон .env.example. В CI и на хостинге значения хранятся в их хранилищах секретов, а в браузерный бандл не попадает ничего, кроме переменных с публичным префиксом вроде NEXT_PUBLIC_. Если ключ всё же утёк, его первым делом отзывают и перевыпускают, и только потом решают, нужно ли переписывать историю Git.

РуководствоУровень: СреднийУправление секретамиПрактика: ~30–60 мин

После материала вы сможете

  • отделить конфиг от секретов и оформить .env, .env.example и .gitignore так, чтобы секреты не попадали в Git
  • загрузить переменные в Node.js через --env-file и остановить приложение при старте, если обязательной переменной нет
  • объяснить, какие переменные Next.js и Vite встраивают в клиентский бандл, и проверить сборку на утечку
  • передать секреты в GitHub Actions, на хостинг и в контейнер без утечки в образ и логи, найти ключ в истории через gitleaks и действовать по плану при утечке

Проверено на

Node.js 24 LTS (24.20)Next.js 16.4gitleaks 8.30.1git-filter-repo 2.47Docker Engine 29.6GitHub Actions

Сверено с документацией: 11 октября 2026 г.

Содержание
  1. 01Что считается секретом
  2. 02.env, .env.example и .gitignore
  3. 03Загрузка в Node.js: --env-file и проверка при старте
  4. 04Фронтенд: что попадает в бандл
  5. 05Секреты в CI, на хостинге и в Docker
  6. 06Сканирование: gitleaks и secret scanning
  7. 07Ключ утёк: план действий

Что считается секретом

Секрет — любое значение, которое даёт доступ: пароль базы данных, ключ платёжного провайдера, токен бота, ключ подписи JWT, приватный SSH-ключ, строка подключения с логином и паролем. Конфиг — всё, что отличается между окружениями (разработка, staging, production), но доступа не даёт: порт, адрес сайта, уровень логов. Методология The Twelve-Factor App требует строго отделять конфиг от кода и хранить его в переменных окружения. Её проверка простая: можно ли прямо сейчас открыть код репозитория всем, не скомпрометировав ни одного доступа.

PORT=3000, APP_URL, LOG_LEVEL

Секрет?
нет — конфиг
Где хранить
переменные окружения; значения по умолчанию можно держать в коде

DATABASE_URL с паролем

Секрет?
да
Где хранить
переменные окружения или менеджер секретов; локально — .env

ключ платёжного провайдера, токен бота

Секрет?
да
Где хранить
то же; отдельный ключ на каждое окружение

ключ карт для браузера (NEXT_PUBLIC_MAPS_KEY)

Секрет?
нет — он публичный по природе
Где хранить
в бандле; защищается ограничениями у провайдера: домены, лимиты

маршруты, связи модулей, флаги сборки

Секрет?
нет — внутренний конфиг
Где хранить
в коде: не меняется между окружениями

.env, .env.example и .gitignore

Локально переменные удобно держать в файле .env — по строке ИМЯ=значение. Сам .env в Git не попадает никогда. В репозиторий кладут шаблон .env.example: те же имена, но без настоящих значений и с комментариями, откуда их взять. Новый разработчик копирует шаблон в .env и заполняет — список нужных переменных всегда виден и обновляется вместе с кодом.

.env.example
# Copy to .env and fill in. Never commit .env — only this template.

# --- Config (not secret: may differ between dev, staging and production) ---
PORT=3000
APP_URL=http://localhost:3000
LOG_LEVEL=info

# --- Secrets (get them from the team's password manager, never from chat) ---
# PostgreSQL connection string with login and password
DATABASE_URL=postgres://app:CHANGE_ME@localhost:5432/leads
# Payments: test key from the provider dashboard, test mode only
PAYMENTS_SECRET_KEY=sk_test_YOUR_KEY
# Anti-spam API key
SPAM_API_KEY=YOUR_API_KEY
Шаблон: имена и пояснения, вместо значений — плейсхолдеры
.gitignore
node_modules/
# Local secrets: every .env file except the template
.env
.env.*
!.env.example
Игнорируются .env и любые .env.* (.env.local, .env.production), кроме шаблона

Проверить, что правило работает, можно командой git check-ignore -v .env: она покажет строку .gitignore, которая исключает файл. Если .env уже попал в коммит, .gitignore не поможет — он действует только на неотслеживаемые файлы. Файл нужно убрать из индекса, оставив на диске:

bash
echo ".env" >> .gitignore
git rm --cached .env
git commit -m "Stop tracking .env"
Файл перестаёт отслеживаться, но остаётся в истории — значения из него считаются утёкшими

Загрузка в Node.js: --env-file и проверка при старте

С Node.js 20.6 .env читается без пакета dotenv: флаг --env-file загружает файл в process.env, а с версий 24.10 и 22.21 он больше не экспериментальный. По документации, если переменная уже задана в окружении, побеждает окружение — так значения хостинга перекрывают локальный файл. Файлов можно передать несколько: следующий переопределяет предыдущий. Если файла нет, Node.js завершается с ошибкой; для необязательного файла есть --env-file-if-exists, а из кода — process.loadEnvFile().

package.json
{
  "scripts": {
    "dev": "node --env-file=.env --watch src/main.ts",
    "start": "node src/main.ts"
  }
}
Фрагмент: локально переменные берутся из .env, в production — из окружения, которое задаёт хостинг

Читать process.env по всему коду — значит узнать об отсутствующей переменной в момент первого платежа. Надёжнее собрать конфиг в одном модуле и проверить обязательные переменные при старте: приложение без ключа не должно запускаться вовсе.

src/config.ts
// Reads configuration once at startup and fails fast if something is missing.
const required = ['DATABASE_URL', 'PAYMENTS_SECRET_KEY', 'SPAM_API_KEY'] as const;

const missing = required.filter((name) => !process.env[name]);
if (missing.length > 0) {
  // Name the variables, never print their values
  console.error(`Missing environment variables: ${missing.join(', ')}`);
  process.exit(1);
}

const paymentsKey = process.env.PAYMENTS_SECRET_KEY!;
if (process.env.NODE_ENV === 'production' && paymentsKey.startsWith('sk_test_')) {
  console.error('PAYMENTS_SECRET_KEY: a test key in production');
  process.exit(1);
}

export const config = {
  port: Number(process.env.PORT ?? 3000),
  appUrl: process.env.APP_URL ?? 'http://localhost:3000',
  logLevel: process.env.LOG_LEVEL ?? 'info',
  databaseUrl: process.env.DATABASE_URL!,
  paymentsSecretKey: paymentsKey,
  spamApiKey: process.env.SPAM_API_KEY!,
};

// For logs: show that a secret is set, but not the secret itself
export const mask = (value: string) => `${value.slice(0, 8)}… (${value.length} chars)`;
Конфиг в одном месте: проверка при старте, имена отсутствующих переменных без значений, маска для логов
src/main.ts
import { config, mask } from './config.ts';

console.log(`port=${config.port} log=${config.logLevel}`);
console.log(`payments key: ${mask(config.paymentsSecretKey)}`);
Точка входа: в лог попадает только маска ключа
terminal
$ node --env-file=.env src/main.ts
port=3000 log=debug
payments key: sk_test_… (28 chars)
$ PORT=4000 node --env-file=.env src/main.ts
port=4000 log=debug
payments key: sk_test_… (28 chars)
$ node src/main.ts; echo "exit code: $?"
Missing environment variables: DATABASE_URL, PAYMENTS_SECRET_KEY, SPAM_API_KEY
exit code: 1
$ NODE_ENV=production node --env-file=.env src/main.ts; echo "exit code: $?"
PAYMENTS_SECRET_KEY: a test key in production
exit code: 1
Реальный вывод в node:24-alpine (Node.js 24.20): загрузка из .env, приоритет окружения, отсутствующие переменные и тестовый ключ в production
  • Сообщение об ошибке называет переменные, но не печатает значения: логи читают больше людей, чем имеют доступ к секретам.
  • Проверка sk_test_ в production ловит частую ошибку переноса: тестовый ключ платёжки на боевом сервере.
  • Тип string после проверки задан через ! — сама проверка выше гарантирует, что значение есть. Для большого конфига удобнее схема валидации, например на Zod.

Фронтенд: что попадает в бандл

Всё, что попало в JavaScript для браузера, может прочитать любой посетитель: открыть DevTools достаточно. Поэтому сборщики по умолчанию не отдают переменные клиенту, а пропускают только помеченные префиксом. В Next.js это NEXT_PUBLIC_: по документации, при next build значение подставляется в бандл как строка. Переменная без префикса на клиенте остаётся пустой. В Vite роль префикса играет VITE_, и документация Vite прямо говорит: такие переменные не должны содержать ключей API.

.env.local
# Public: goes into the browser bundle at build time
NEXT_PUBLIC_MAPS_KEY=pk_test_FAKE_maps_browser_key
# Secret: server only
PAYMENTS_SECRET_KEY=sk_test_FAKE7d2c9e41b8a05f36
Next.js загружает .env.local автоматически; файл закрыт .gitignore по шаблону create-next-app
app/map-widget.tsx
'use client';

export function MapWidget() {
  // NEXT_PUBLIC_: the value is inlined into the browser bundle
  const mapsKey = process.env.NEXT_PUBLIC_MAPS_KEY;
  // No prefix: on the client Next.js leaves this empty
  const secret = process.env.PAYMENTS_SECRET_KEY;
  return (
    <p>
      maps key: {mapsKey}, payments key on the client: {secret || 'not available'}
    </p>
  );
}
Клиентский компонент: публичный ключ встраивается, секретный — нет
lib/payments.ts
import 'server-only';

// Any import of this module from a Client Component fails the build
export async function createPayment(amount: number) {
  const key = process.env.PAYMENTS_SECRET_KEY;
  if (!key) throw new Error('PAYMENTS_SECRET_KEY is not set');
  // A real app would call the payment provider's API with this key
  return { amount, keyIsSet: key.length > 0 };
}
Серверный модуль с секретом; импорт server-only защищает его от подключения на клиенте

Проверка, которую стоит повторять перед каждым релизом, — поиск значений секретов в собранных клиентских файлах:

terminal
$ npx next build
▲ Next.js 16.4.0 (Turbopack)
- Environments: .env.local
…
$ grep -rl "pk_test_FAKE_maps_browser_key" .next/static
.next/static/chunks/3r_kr6wnxt_zd.js
$ grep -rl "sk_test_FAKE7d2c9e41b8a05f36" .next/static || echo "not in the client bundle"
not in the client bundle
Реальный вывод: публичный ключ в клиентском чанке, секрета в .next/static нет

Если клиентский компонент попробует импортировать lib/payments.ts, сборка остановится:

terminal
$ npx next build
> Build error occurred
Error: Turbopack build failed with 2 errors:
./lib/payments.ts:1:1
Error: You're importing a module that depends on "server-only". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
    Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
> 1 | import 'server-only';
    | ^^^^^^^^^^^^^^^^^^^^^
Ecmascript file had an error
Import traces:
  Server Component:
    ./lib/payments.ts
    ./app/page.tsx
  Client Component Browser:
    ./lib/payments.ts [Client Component Browser]
    ./app/map-widget.tsx [Client Component Browser]
…
Реальный вывод next build 16.4 (сокращён на месте …): server-only не пускает модуль с секретом в клиентский код. Текст ошибки упоминает Pages Router, но причину показывает Import traces — импорт из клиентского компонента

Секреты в CI, на хостинге и в Docker

На сервере файла .env обычно нет: значения задаются в панели хостинга или в хранилище секретов CI, а платформа передаёт их процессу как переменные окружения. В GitHub Actions секрет создают в Settings → Secrets and variables → Actions или командой gh secret set NAME и читают через secrets.NAME. Секреты окружения (environment: production) получает только задание, которое на это окружение ссылается.

.github/workflows/migrate.yml
name: Migrate

on:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  migrate:
    runs-on: ubuntu-24.04
    environment: production # the job gets production secrets only
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
        with:
          node-version: 24
      - run: npm ci
      - name: Run migrations
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }} # an env var, not a command-line argument
        run: npm run migrate
Секрет передаётся шагу через env: документация GitHub не советует передавать секреты аргументами командной строки. Предполагается, что в package.json есть скрипт migrate
  • GitHub маскирует значения секретов в логах, но, по его же документации, не гарантированно: если секрет преобразован (Base64, JSON), маска может не сработать. Не храните структурированные данные одним секретом и не печатайте секреты даже «для отладки».
  • Pull request из форка секретов не получает. Проверки pull request должны обходиться без них — подробнее в материале о CI/CD на GitHub Actions.
  • На хостинге разделяйте конфиг и секреты. Например, в Vercel переменная типа Secret после сохранения доступна только на запись, а её значение длиной от 32 символов маскируется в логах сборки.
  • Задавайте разные значения для production и preview/staging: preview-сборки собираются из любых веток и не должны видеть боевые ключи.

Отдельная ловушка — Docker. Документация Docker предупреждает: аргументы сборки и переменные окружения не годятся для передачи секретов в сборку, потому что остаются в итоговом образе. Мы проверили на образе с ARG PAYMENTS_SECRET_KEY и ENV SPAM_API_KEY=… в Dockerfile:

terminal
$ docker build -q -t env-leak --build-arg PAYMENTS_SECRET_KEY=sk_test_FAKE7d2c9e41b8a05f36 .
sha256:1eda9198ee8f…
$ docker history --no-trunc env-leak | grep -o "PAYMENTS_SECRET_KEY=[a-zA-Z0-9_]*" | head -1
PAYMENTS_SECRET_KEY=sk_test_FAKE7d2c9e41b8a05f36
$ docker inspect env-leak --format "{{json .Config.Env}}"
["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","SPAM_API_KEY=FAKE_spam_key_baked_into_image"]
Реальный вывод: значение ARG видно в docker history, ENV — в метаданных образа. Любой, кто скачал образ, их прочитает

Секреты в контейнер передают при запуске (docker run --env-file, переменные окружения оркестратора или смонтированный файл), а если секрет нужен на этапе сборки — например, токен приватного npm-реестра — через secret mounts BuildKit (RUN --mount=type=secret). OWASP добавляет инженерный компромисс: переменные окружения видны всем процессам контейнера и могут попасть в дампы, поэтому для чувствительных систем предпочтительнее файлы, смонтированные оркестратором, или менеджер секретов (Vault, облачные Secret Manager).

Сканирование: gitleaks и secret scanning

Ошибка всё равно случится: ключ вставят в тест, в конфиг «на минутку», в Markdown с примером. Сканер находит такие строки по шаблонам известных ключей (Stripe, AWS, GitHub и десятки других) и по энтропии. gitleaks — открытый сканер, который работает локально и в CI. Режим git проверяет всю историю коммитов через git log -p, режим dir — файлы в каталоге.

Тестовый репозиторий: в первом коммите ключ записан прямо в src/payments.ts, во втором его заменили на process.env.PAYMENTS_SECRET_KEY. В текущих файлах ключа нет, но gitleaks находит его в истории:

bash
docker run --rm -v "$PWD:/repo" zricethezav/gitleaks:v8.30.1 git -v --redact --no-banner /repo
--redact скрывает значение в выводе: отчёт сканера не должен сам становиться утечкой
terminal
$ docker run --rm -v "$PWD:/repo" zricethezav/gitleaks:v8.30.1 git -v --redact --no-banner /repo
Finding:     REDACTED'
Secret:      REDACTED
RuleID:      stripe-access-token
Entropy:     4.521641
File:        src/payments.ts
Line:        1
Commit:      d7e5da9132cc2f72e810470d91a16146c0f49549
Author:      Dev
Email:       dev@example.com
Date:        2026-10-12T01:52:59Z
Fingerprint: d7e5da9132cc2f72e810470d91a16146c0f49549:src/payments.ts:stripe-access-token:1

1:53AM INF 2 commits scanned.
1:53AM INF scanned ~218 bytes (218 bytes) in 172ms
1:53AM WRN leaks found: 1
$ echo $?
1
Реальный вывод gitleaks 8.30.1: ключ найден в первом коммите, хотя файл уже исправлен; код выхода 1

В том же каталоге режим dir нашёл ключ в .env — файл не в Git, но лежит на диске. Поэтому для репозитория используйте git, а dir — для того, что уходит наружу целиком: каталога сборки или содержимого образа. Чтобы ловить ключ до коммита, gitleaks подключают хуком pre-commit (готовая конфигурация есть в README), а в CI — отдельным заданием на каждый pull request:

.github/workflows/secrets.yml
name: Secret scan

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  gitleaks:
    name: gitleaks
    runs-on: ubuntu-24.04
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          fetch-depth: 0 # the whole history, not just the last commit
          persist-credentials: false
      - name: Scan git history
        run: >-
          docker run --rm -v "$PWD:/repo" zricethezav/gitleaks:v8.30.1
          git --redact --no-banner /repo
fetch-depth: 0 — без него checkout скачивает только последний коммит, и история не проверяется

Осознанный тестовый ключ помечают комментарием gitleaks:allow в той же строке или добавляют отпечаток находки в .gitleaksignore. Рекомендация OWASP — завести в команде стандартные тестовые секреты, чтобы не разбирать ложные срабатывания на каждом проекте.

gitleaks

Что делает
ищет секреты в истории, файлах и stdin; хук pre-commit и задание CI
Где доступен
везде: открытый, запускается локально и в любом CI

GitHub secret scanning

Что делает
сканирует всю историю всех веток, а также issues, pull request и wiki; партнёрские ключи сообщает провайдеру
Где доступен
публичные репозитории — бесплатно; приватные репозитории организаций — с GitHub Secret Protection на Team и Enterprise Cloud

GitHub push protection

Что делает
блокирует push с найденным секретом до попадания в репозиторий
Где доступен
для пользователей включена по умолчанию и защищает публичные репозитории; для репозитория — нужна Secret Protection

Ключ утёк: план действий

Утечкой считается любое попадание секрета туда, где его могут прочитать посторонние: коммит (даже в приватный репозиторий и даже удалённый следующим коммитом), лог CI, скриншот, сообщение в чате, клиентский бандл. И OWASP, и GitHub сходятся в главном: сначала ключ отзывают и заменяют, а чистка истории — вторична.

  1. Отозвать и перевыпустить ключ

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

  2. Заменить значение везде

    Хостинг, секреты CI и окружений, менеджер паролей команды, .env разработчиков. Перезапустите приложения, чтобы они прочитали новое значение.

  3. Проверить, пользовались ли ключом

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

  4. Найти все копии

    Прогоните gitleaks git по всем репозиториям, где мог оказаться ключ, проверьте логи CI и чаты. Логи прогонов GitHub Actions с открытым секретом удалите.

  5. Решить, нужно ли переписывать историю

    По документации GitHub, если секрет отозван, переписывание часто не требуется. Если всё же нужно (персональные данные, ключ, который нельзя отозвать), используйте git filter-repo --sensitive-data-removal.

  6. Разобрать причину

    Как ключ попал в код и почему этого не поймали: добавьте сканер в pre-commit и CI, включите push protection, обновите .env.example и правила команды.

bash
git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY
cd YOUR-REPOSITORY
echo 'sk_test_FAKE7d2c9e41b8a05f36' > ../leaked.txt
git filter-repo --sensitive-data-removal --replace-text ../leaked.txt
git push --force --mirror origin
Переписывание истории по документации GitHub: свежий клон, замена строки во всех коммитах, принудительный push всех веток и тегов
terminal
$ git filter-repo --sensitive-data-removal --replace-text ../leaked.txt
NOTICE: Fetching all refs from origin to make sure we rewrite
        all history that may reference the sensitive data, via
      git fetch -q --prune --update-head-ok --refmap "" origin +refs/*:refs/*
…
You rewrote 2 (of 2) commits.

NOTE: First Changed Commit(s) is/are:
  d7e5da9132cc2f72e810470d91a16146c0f49549
…
$ git log --oneline
d931c47 Move payments key to env
dd4cd57 Add payments client
$ git show HEAD~1:src/payments.ts | head -1
const PAYMENTS_SECRET_KEY = '***REMOVED***';
$ docker run --rm -v "$PWD:/repo" zricethezav/gitleaks:v8.30.1 git -v --redact --no-banner /repo
1:53AM INF 2 commits scanned.
1:53AM INF no leaks found
Реальный вывод git-filter-repo 2.47.0 на тестовом репозитории (сокращён на месте …): строка заменена во всех коммитах, хэши коммитов изменились, gitleaks больше ничего не находит
  • Хэши всех коммитов после утечки меняются: ломаются ссылки на коммиты, подписи коммитов снимаются, у закрытых pull request пропадает diff.
  • Защиту ветки от force push придётся временно отключить. Коллеги должны переклонировать репозиторий или пересадить свои ветки через rebase — один git pull и git push со старого клона вернёт секрет обратно.
  • Копии в форках, кэшированных представлениях GitHub и pull request удаляются только через поддержку GitHub, а в чужих клонах — никак. Ещё одна причина, почему первый шаг — отзыв ключа.
  • В репозитории есть .env.example с полным списком переменных и пояснениями; .env и .env.* в .gitignore.
  • Секреты передаются через менеджер паролей команды, а не в чатах и почте.
  • У каждого окружения свои ключи с минимальными правами; тестовые ключи не уходят в production.
  • Приложение проверяет обязательные переменные при старте и никогда не печатает значения секретов.
  • В клиентский код попадают только переменные с NEXT_PUBLIC_ / VITE_, и среди них нет секретов.
  • Секреты не передаются в Docker через ARG и ENV.
  • gitleaks работает в pre-commit и в CI с fetch-depth: 0; для публичных репозиториев включён secret scanning.
  • Утёкший ключ сначала отзывается и перевыпускается, и только потом решается вопрос с историей Git.
Как проверено5 · пройдено2 · частичносверено 11 октября 2026 г.
  • запущено, работает

    .env.example, .env (фейковые значения), .gitignore, src/config.ts, src/main.ts, package.json scripts

    node:24-alpine (Node 24.20.0) и Windows Node 24.18.0: tsc -p . (TypeScript 7.0.2, @types/node 24) без ошибок; node --env-file=.env — загрузка; PORT=4000 перекрывает значение из файла; без переменных — список имён и exit 1; NODE_ENV=production с sk_test_ — exit 1; отсутствующий --env-file — 'not found', exit 9; --env-file-if-exists продолжает без файла. git check-ignore -v: .env, .env.local, .env.production игнорируются, .env.example — нет (правило !.env.example). Вывод в статье — реальный из контейнера

  • запущено, работает

    git rm --cached .env

    тестовый репозиторий: .env закоммичен → .gitignore + git rm --cached + commit: файл не отслеживается, остаётся на диске (status !! .env), git log -- .env показывает его в истории

  • запущено, работает

    Next.js: .env.local, app/map-widget.tsx, lib/payments.ts (server-only)

    временный проект next 16.4.0, react 19.3.0, server-only 0.0.1, TypeScript 5: next build успешен; pk_test_FAKE_maps_browser_key найден в .next/static/chunks/3r_kr6wnxt_zd.js, sk_test_FAKE… нет ни в .next/static, ни в .next/server; на клиенте секрет даёт ветку 'not available'. Импорт lib/payments.ts из клиентского компонента → next build exit 1 с ошибкой server-only (текст ошибки упоминает Pages Router — так выводит Next 16.4)

  • запущено, работает

    Docker: ARG/ENV в образе

    alpine:3, docker build --build-arg PAYMENTS_SECRET_KEY=sk_test_FAKE…: значение видно в docker history --no-trunc, ENV — в docker inspect .Config.Env; в реальном прогоне тег образа kb-env-leak (в статье env-leak), образ удалён после проверки

  • запущено, работает

    gitleaks: поиск ключа в истории

    docker zricethezav/gitleaks:v8.30.1 на тестовом репозитории (commit 1 — sk_test_FAKE7d2c9e41b8a05f36 в src/payments.ts, commit 2 — process.env): git -v --redact --no-banner → stripe-access-token в commit d7e5da9, exit 1; режим dir находит ключ в неотслеживаемом .env

  • частично

    git filter-repo --sensitive-data-removal --replace-text

    git-filter-repo 2.47.0 (pip в venv) на свежем клоне локального bare-репозитория: переписаны 2 из 2 коммитов, строка заменена на ***REMOVED***, хэши изменились (d7e5da9 → dd4cd57), старый объект недоступен, gitleaks git — no leaks found. git push --force --mirror origin не выполнялся: команда force push отклонена системой разрешений среды даже для локального bare-репозитория; шаг взят из документации GitHub

  • частично

    .github/workflows/secrets.yml (gitleaks в CI), migrate.yml (секрет через env + environment)

    actionlint 1.7.12 (docker rhysd/actionlint) — 0 ошибок; та же команда docker run gitleaks выполнена локально на тестовом репозитории (exit 1 при находке). На GitHub не запускались: секреты и окружения GitHub не создавались; gh secret set не выполнялся (команда gh отклонена системой разрешений, синтаксис — по документации)

«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.

Расскажите о задаче — мы изучим запрос и свяжемся с вами

Есть задача?
Обсудим и оценим — бесплатно.

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

  • Оценка объёма и рисков до старта
  • NDA — по вашему или нашему шаблону
  • Старт работ в течение недели

Не присылайте через форму пароли, ключи доступа и исходный код — безопасную передачу доступа обсудим отдельно.

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