[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user