Опирается на правила:
R-SEC-SECRET-1…R-SEC-SECRET-3иR-SEC-SECRET-X1…R-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.yml | R-SEC-SECRET-X1 | ${ENV_VAR} placeholder |
.env в git (даже с примером) | R-SEC-SECRET-X2 | .env.example без значений |
git rebase после leak без rotation | R-SEC-SECRET-3 | rotate first |
| Pre-commit hook в README, не автоматически | R-SEC-SECRET-2 | husky / pre-commit framework |
| Gitleaks только в CI, без pre-commit | R-SEC-SECRET-1 | оба слоя |
| Скан только diff, не history | R-SEC-SECRET-1 | weekly full history scan |
git commit --no-verify как привычка | R-SEC-SECRET-2 | CI как страховка |
Hardcoded keys в Docker ENV ... | R-SEC-SECRET-X1 | env через runtime |
Куда дальше
- Security → раздел 3. Секреты в коде и истории — нормативные формулировки.
- SAST по коду — FindSecBugs ловит
HARD_CODE_PASSWORD. - CVE в зависимостях — другой класс уязвимостей.
- Container/image-уязвимости — secrets не в Docker layers.
- Реакция на findings — SLA для leak = hotfix < 1 час.
- Auth → PII и секреты — Vault / SealedSecrets / env.