diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..6840982 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel)" +"$repo_root/scripts/check-secrets.sh" diff --git a/.gitignore b/.gitignore index d82e1a0..80e9572 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,16 @@ # QuoteForge config.yaml +.env +.env.* +*.pem +*.key +*.p12 +*.pfx +*.crt +id_rsa +id_rsa.* +secrets.yaml +secrets.yml # Local SQLite database (contains encrypted credentials) /data/*.db diff --git a/Makefile b/Makefile index 85b3286..d811f8d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build build-release clean test run version +.PHONY: build build-release clean test run version install-hooks # Get version from git VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") @@ -72,6 +72,12 @@ deps: go mod download go mod tidy +# Install local git hooks +install-hooks: + git config core.hooksPath .githooks + chmod +x .githooks/pre-commit scripts/check-secrets.sh + @echo "Installed git hooks from .githooks/" + # Help help: @echo "QuoteForge Server (qfs) - Build Commands" @@ -92,6 +98,7 @@ help: @echo " run Run development server" @echo " watch Run with auto-restart (requires entr)" @echo " deps Install/update dependencies" + @echo " install-hooks Install local git hooks (secret scan on commit)" @echo " help Show this help" @echo "" @echo "Current version: $(VERSION)" diff --git a/README.md b/README.md index e64bdd3..a40c386 100644 --- a/README.md +++ b/README.md @@ -111,10 +111,10 @@ go run ./cmd/migrate_ops_projects -apply -yes ```sql -- 1) Создать пользователя (если его ещё нет) -CREATE USER IF NOT EXISTS 'quote_user'@'%' IDENTIFIED BY 'DB_PASSWORD_PLACEHOLDER'; +CREATE USER IF NOT EXISTS 'quote_user'@'%' IDENTIFIED BY ''; -- 2) Если пользователь уже существовал, принудительно обновить пароль -ALTER USER 'quote_user'@'%' IDENTIFIED BY 'DB_PASSWORD_PLACEHOLDER'; +ALTER USER 'quote_user'@'%' IDENTIFIED BY ''; -- 3) (Опционально, но рекомендуется) удалить дубли пользователя с другими host, -- чтобы не возникало конфликтов вида user@localhost vs user@'%' @@ -144,7 +144,7 @@ SHOW CREATE USER 'quote_user'@'%'; Полный набор прав для пользователя квотаций: ```sql -GRANT USAGE ON *.* TO 'quote_user'@'%' IDENTIFIED BY 'DB_PASSWORD_PLACEHOLDER'; +GRANT USAGE ON *.* TO 'quote_user'@'%' IDENTIFIED BY ''; GRANT SELECT ON RFQ_LOG.lot TO 'quote_user'@'%'; GRANT SELECT ON RFQ_LOG.qt_lot_metadata TO 'quote_user'@'%'; GRANT SELECT ON RFQ_LOG.qt_categories TO 'quote_user'@'%'; @@ -187,6 +187,7 @@ make build-all # Сборка для всех платформ (Linux, mac make build-windows # Только для Windows make run # Запуск dev сервера make test # Запуск тестов +make install-hooks # Установить git hooks (блокировка коммита с секретами) make clean # Очистка bin/ make help # Показать все команды ``` diff --git a/scripts/check-secrets.sh b/scripts/check-secrets.sh new file mode 100755 index 0000000..89fd530 --- /dev/null +++ b/scripts/check-secrets.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! git rev-parse --git-dir >/dev/null 2>&1; then + echo "Not inside a git repository." + exit 1 +fi + +if ! command -v rg >/dev/null 2>&1; then + echo "ripgrep (rg) is required for secret scanning." + exit 1 +fi + +staged_files=() +while IFS= read -r file; do + staged_files+=("$file") +done < <(git diff --cached --name-only --diff-filter=ACMRTUXB) + +if [ "${#staged_files[@]}" -eq 0 ]; then + exit 0 +fi + +secret_pattern='AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AIza[0-9A-Za-z_-]{35}|-----BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY-----|(?i)(password|passwd|pwd|secret|token|api[_-]?key|jwt_secret)\s*[:=]\s*["'"'"'][^"'"'"'\s]{8,}["'"'"']' +allow_pattern='CHANGE_ME|REDACTED|PLACEHOLDER|EXAMPLE|example|<[^>]+>' + +found=0 + +for file in "${staged_files[@]}"; do + case "$file" in + dist/*|*.png|*.jpg|*.jpeg|*.gif|*.webp|*.pdf|*.zip|*.gz|*.exe|*.dll|*.so|*.dylib) + continue + ;; + esac + + if ! content="$(git show ":$file" 2>/dev/null)"; then + continue + fi + + hits="$(printf '%s' "$content" | rg -n --no-heading -e "$secret_pattern" || true)" + if [ -n "$hits" ]; then + filtered="$(printf '%s\n' "$hits" | rg -v -e "$allow_pattern" || true)" + if [ -n "$filtered" ]; then + echo "Potential secret found in staged file: $file" + printf '%s\n' "$filtered" + found=1 + fi + fi +done + +if [ "$found" -ne 0 ]; then + echo + echo "Commit blocked: remove or redact secrets before committing." + exit 1 +fi + +exit 0