--- id: devsec-pre-commit-security title: Pre-commit Security — Secret / Lint / 빠른 가드 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [devsecops, pre-commit, hook, vibe-coding] tech_stack: { language: "Various", applicable_to: ["DevOps"] } applied_in: [] aliases: [pre-commit, husky, lefthook, lint-staged, gitleaks, secret detection] --- # Pre-commit Security > 사고 나기 전에 잡기. **Husky / lefthook / pre-commit framework + lint-staged + gitleaks**. Secret commit 차단 + lint + format. CI 보다 빠른 feedback. ## 📖 핵심 개념 - Pre-commit: commit 전 hook 실행. - Pre-push: push 전 (더 무거운 검사 가능). - Lint-staged: 변경 파일만. - Bypass: --no-verify (긴급). ## 💻 코드 패턴 ### Husky (Node 친화) ```bash yarn add -D husky lint-staged yarn husky init ``` ```jsonc // package.json { "lint-staged": { "*.{ts,tsx}": [ "eslint --fix", "prettier --write" ], "*.{json,yaml,md}": ["prettier --write"] } } ``` ```bash # .husky/pre-commit yarn lint-staged yarn typecheck ``` ### Lefthook (Go, 빠름, multi-language) ```yaml # lefthook.yml pre-commit: parallel: true commands: lint: glob: '*.{ts,tsx}' run: yarn eslint --fix {staged_files} stage_fixed: true secrets: run: gitleaks protect --staged --no-banner format: glob: '*.{ts,tsx,json,md}' run: yarn prettier --write {staged_files} stage_fixed: true pre-push: commands: typecheck: run: yarn tsc --noEmit test: run: yarn test --run commit-msg: commands: conventional: run: npx commitlint --edit {1} ``` ```bash lefthook install ``` ### pre-commit framework (Python, 다양 hook) ```yaml # .pre-commit-config.yaml repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files args: [--maxkb=500] - id: check-merge-conflict - id: detect-private-key - repo: https://github.com/gitleaks/gitleaks rev: v8.18.0 hooks: - id: gitleaks - repo: https://github.com/returntocorp/semgrep rev: v1.45.0 hooks: - id: semgrep args: [--config=p/secrets, --error] - repo: local hooks: - id: typecheck name: TypeScript entry: yarn tsc --noEmit language: system pass_filenames: false ``` ```bash pre-commit install pre-commit run --all-files # 한 번 모든 파일 ``` ### Secret detection (gitleaks) ```toml # .gitleaks.toml — 자체 rule [[rules]] id = "company-api-key" description = "Company internal API key" regex = '''cmp_[a-zA-Z0-9]{32}''' [allowlist] paths = [ '''.*\.test\.ts$''', '''fixtures/.*''', ] ``` ```bash gitleaks detect --source . --verbose gitleaks protect --staged --verbose # pre-commit ``` ### Detect-secrets (Yelp) ```bash # Baseline 생성 (current secrets) detect-secrets scan --baseline .secrets.baseline # Pre-commit detect-secrets-hook --baseline .secrets.baseline $(git diff --cached --name-only) ``` → Baseline 으로 false positive 관리. ### Commit message — Conventional Commits ```bash npm install -D @commitlint/cli @commitlint/config-conventional ``` ```js // commitlint.config.js module.exports = { extends: ['@commitlint/config-conventional'] }; ``` ```bash # .husky/commit-msg npx --no -- commitlint --edit $1 ``` → `feat: xxx` / `fix: yyy` 강제. semantic-release 와 결합. ### TypeScript / lint 빠르게 ```bash # 변경 파일만 lint (lint-staged) yarn lint-staged # tsc — incremental tsc --noEmit --incremental --tsBuildInfoFile .tsbuildinfo ``` ### Bypass (긴급, 사용 주의) ```bash git commit --no-verify -m "WIP" git push --no-verify ``` → CI 가 second gate. ### Force re-check (skip 한 후) ```yaml # CI - name: Pre-commit run: pre-commit run --all-files ``` → Bypass 해도 PR 에서 잡힘. ### Husky vs lefthook vs pre-commit ``` Husky: + Node 친화, 친숙 - Shell script Lefthook: + 매우 빠름 (Go) + 병렬 + Multi-language pre-commit (Python): + 큰 hook ecosystem + 매우 강력 framework - Python 의존 ``` ### Skip in CI (이미 fixed) ```yaml - run: SKIP=eslint pre-commit run --all-files ``` ### Secret rotate after leak ```bash # Force push 로 history 삭제 X — secret 가 git history 에 남음. # 1. Secret 즉시 rotate (모든 곳) # 2. .git history 안 secret 도 cleanup git filter-repo --invert-paths --path file-with-secret # 강력 # 또는 BFG repo-cleaner # 3. force push (조심) + 모든 사람 re-clone ``` ### Large file check ```yaml - repo: https://github.com/pre-commit/pre-commit-hooks hooks: - id: check-added-large-files args: [--maxkb=500] ``` → 큰 binary git 안 — Git LFS. ### Private key check ```yaml - id: detect-private-key ``` → -----BEGIN RSA PRIVATE KEY----- 자동 검출. ### Branch naming ```bash # .husky/pre-commit branch=$(git rev-parse --abbrev-ref HEAD) if ! [[ "$branch" =~ ^(main|feat|fix|chore)/ ]]; then echo "Branch must start with feat/, fix/, or chore/" exit 1 fi ``` ### Performance (큰 monorepo) ```bash # Turborepo + pre-commit yarn lint-staged # 변경 파일만 turbo run lint --filter='[HEAD^1]' # 변경 package 만 ``` ### Onboarding 자동 ```bash # postinstall script "scripts": { "prepare": "husky install" # auto-install hook } ``` → `npm install` 시 hook 자동 setup. ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | Node only | Husky + lint-staged | | Multi-language / monorepo | Lefthook | | Strong ecosystem | pre-commit framework | | Secret detection | Gitleaks (강) / Detect-secrets (baseline) | | Conventional commits | commitlint | | 빠른 feedback | lint-staged + 작은 hook | ## ❌ 안티패턴 - **모든 hook 동기 + 느림**: 사용자가 bypass. - **--no-verify 자주**: 의미 잃음. - **모든 file lint 매 commit**: 큰 repo 느림. lint-staged. - **Secret 발견 후 history 안 cleanup**: 영원 git history 에. - **Onboarding 수동 hook install**: 새 dev 가 잊음. prepare script. - **CI 가 hook 안 검사**: bypass = 통과. CI 가 second gate. - **Format / lint 가 commit 변경**: stage_fixed. ## 🤖 LLM 활용 힌트 - Husky / lefthook + lint-staged + gitleaks 가 baseline. - Conventional commit + commitlint. - Bypass 가능 but CI 가 검사. ## 🔗 관련 문서 - [[DevSec_DAST_SAST]] - [[DevSec_Supply_Chain]] - [[DevOps_CI_CD_Pipeline_Patterns]]