Опирается на правила: R-SEC-SECRET-1R-SEC-SECRET-3 и R-SEC-SECRET-X1R-SEC-SECRET-X2 из Security Style Guide → раздел 3. Секреты в коде и истории.

Важно знать

  • Gitleaks в pre-commit + CI — два слоя защиты, pre-commit до push, CI страховочно.
  • Сканирование full history раз в неделю — leaked secret может всплыть задним числом.
  • Pre-commit hook через husky или pre-commit-framework — не ручная инструкция в README.
  • Утечка → rotate сначала, удаление из истории — опционально.
  • GitHub уже проиндексировал, git rebase не помогает.
  • Секреты в application.yml — запрещены, только ${ENV_VAR}.
  • .env в git — запрещён, даже с пометкой «example»; используй .env.example без значений.

Один закоммиченный пароль или API-key — это инцидент даже если удалить через минуту. GitHub индексирует репо, malicious bots сканируют new commits 24/7, экспонированные credentials используются в первые минуты. UCP формулирует двухслойную защиту: предотвращение через pre-commit + детекция через CI + быстрая rotation при обнаружении.

Gitleaks в pre-commit

R-SEC-SECRET-1: первый слой защиты — до push.

.gitleaks.toml в корне репо:

title = "vikulin.va gitleaks config"

[allowlist]
description = "Allowed patterns"
paths = [
    '''\.example$''',
    '''^test/.*\.fixture\.json$''',
    '''README\.md''',
]

[[rules]]
id = "generic-api-key"
description = "Generic API Key"
regex = '''(?i)(?:api[_-]?key|apikey|api[_-]?token)['"\s:=]+['"]([0-9a-zA-Z\-_]{20,})'''
keywords = ["api_key", "apikey", "api-key"]

[[rules]]
id = "aws-access-key"
description = "AWS Access Key ID"
regex = '''AKIA[0-9A-Z]{16}'''

[[rules]]
id = "private-key"
description = "Private SSH/TLS key"
regex = '''-----BEGIN (?:RSA |OPENSSH |DSA |EC |PGP )?PRIVATE KEY( BLOCK)?-----'''

[[rules]]
id = "jwt"
description = "JWT token"
regex = '''eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'''

Pre-commit hook через husky:

# install once
npm install --save-dev husky
npx husky init

.husky/pre-commit:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

docker run --rm -v "$(pwd):/repo" zricethezav/gitleaks:latest detect \
    --source=/repo \
    --config=/repo/.gitleaks.toml \
    --no-git \
    --staged

--staged — сканирует только staged changes (то, что собирается коммитить). Если finding — exit 1, коммит блокируется.

При срабатывании — разработчик видит:

Finding:     AKIAIOSFODNN7EXAMPLE
Secret:      AKIAIOSFODNN7EXAMPLE
RuleID:      aws-access-key
File:        config/credentials.yml
Line:        5
Commit:      —

Removing secret + retry commit.

Pre-commit hook автоматически

R-SEC-SECRET-2: hook коммитится в репо.

package.json:

{
  "scripts": {
    "prepare": "husky"
  },
  "devDependencies": {
    "husky": "^9.1.0"
  }
}

Любой разработчик после git clone + npm install (или yarn) получает hook автоматически. Без husky / pre-commit-framework — README пишет «установи hook вручную», 80% разработчиков забывают.

Альтернатива для не-JS проектов — pre-commit framework:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.0
    hooks:
      - id: gitleaks

После pre-commit install — hook установлен. Pre-commit framework универсален для любого языка.

CI как страховка

R-SEC-SECRET-1: pre-commit может быть пропущен (git commit --no-verify), CI — нет.

GitHub Actions:

name: Gitleaks
on:
  pull_request:
  push:
    branches: [main]
  schedule:
    - cron: '0 3 * * 1'  # weekly history scan

jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
  • На PR — сканирует diff.
  • На push в main — сканирует diff.
  • Раз в неделю — сканирует full history (--no-git=false). Это критично: secret мог быть закоммичен полгода назад, тогда правила были мягче, теперь pattern его ловит.

Если секрет утёк — rotate first

R-SEC-SECRET-3: порядок действий.

Сценарий: gitleaks-bot нашёл закоммиченный AWS access key.

Шаг 1 (в течение часа): rotate.

  • Войти в AWS console, deactivate этот access key.
  • Создать новый key, обновить env vars в CI/cluster.
  • Notify security team.

Шаг 2 (опционально): удаление из истории.

  • git filter-repo или bfg-repo-cleaner — переписать историю без secret.
  • Force push в main.
  • Уведомить всех contributors сделать git clone заново.

Почему именно в таком порядке:

GitHub индексирует репо, поисковые боты тоже. Через минуту после push attacker уже знает секрет. Время от push до exploit — секунды для AWS keys (есть бот, постоянно сканирующий GitHub). git rebase через час бесполезен — secret уже у attacker.

bfg-repo-cleaner помогает только для compliance audit («у нас в репо больше нет secret»), но не возвращает скомпрометированный key. Rotation — единственная реальная защита.

Запреты

Секреты в application.yml

R-SEC-SECRET-X1:

# КАТАСТРОФА
spring:
  datasource:
    password: super-secret-prod-password
  security:
    oauth2:
      client:
        registration:
          payment-service:
            client-secret: another-secret

Любой с доступом к репо (включая read-only forks) видит prod-credentials. Один external contributor — leak.

Корректно:

spring:
  datasource:
    password: ${DB_PASSWORD}
  security:
    oauth2:
      client:
        registration:
          payment-service:
            client-secret: ${PAYMENT_CLIENT_SECRET}

Откуда брать env vars в prod:

  • Kubernetes Secret (минимальный baseline).
  • Vault через Vault Agent Injector.
  • Cloud Secret Manager (AWS SM, GCP Secret Manager).
  • SealedSecrets — encrypted secret в git, расшифровывается оператором в кластере.

Локально — .env файл в .gitignore.

.env в git

R-SEC-SECRET-X2: даже с пометкой «example».

# КАТАСТРОФА — .env в git
DB_PASSWORD=devpassword123
PAYMENT_API_KEY=test-key-please-replace

Если разработчик не заметит и положит prod значение — leak. Plus malicious bots scrape .env файлы.

Правильно:

# .env.example (закоммитен, без значений)
DB_PASSWORD=
PAYMENT_API_KEY=
KAFKA_BROKERS=
# .env (в .gitignore, локальный)
DB_PASSWORD=devpassword123
PAYMENT_API_KEY=test-key

.gitignore:

.env
.env.local
.env.*.local
*.pem
*.key
config/credentials.yml

Что запрещено — таблица

АнтипаттернПравилоЧто взамен
Секреты в application.ymlR-SEC-SECRET-X1${ENV_VAR} placeholder
.env в git (даже с примером)R-SEC-SECRET-X2.env.example без значений
git rebase после leak без rotationR-SEC-SECRET-3rotate first
Pre-commit hook в README, не автоматическиR-SEC-SECRET-2husky / pre-commit framework
Gitleaks только в CI, без pre-commitR-SEC-SECRET-1оба слоя
Скан только diff, не historyR-SEC-SECRET-1weekly full history scan
git commit --no-verify как привычкаR-SEC-SECRET-2CI как страховка
Hardcoded keys в Docker ENV ...R-SEC-SECRET-X1env через runtime

Куда дальше

  • Security → раздел 3. Секреты в коде и истории — нормативные формулировки.
  • SAST по коду — FindSecBugs ловит HARD_CODE_PASSWORD.
  • CVE в зависимостях — другой класс уязвимостей.
  • Container/image-уязвимости — secrets не в Docker layers.
  • Реакция на findings — SLA для leak = hotfix < 1 час.
  • Auth → PII и секреты — Vault / SealedSecrets / env.