Ch11. 보안과 코드 거버넌스
CODEOWNERS, 의존성 감사, 정적 분석, 시크릿 관리, 에이전트 보안
핵심 요약
- CODEOWNERS로 앱·패키지·인프라별 소유 팀을 지정하고 "Require review from Code Owners" 설정으로 머지 게이트를 강제합니다.
- 의존성 감사는 Dependabot·Renovate·Socket 중에서 고르되, Renovate
group:monorepos로 관련 패키지를 한 PR로 묶어 노이즈를 줄입니다. - 정적 분석은 에이전트가 아니라 CI 게이트(ESLint·타입 검사·SAST·dependency audit)가 병합 허용을 결정하고, 여기에
eslint-plugin-security규칙을 더합니다. - 시크릿은 로컬
.env.local→Preview/Production Vercel 변수→CI GitHub Secrets로 계층 분리하고, gitleaks 등 pre-commit hook으로 유출을 차단합니다. - 에이전트 보안은 파일 접근·명령 권한·MCP allowlist·승인 정책·CI 게이트의 권한 계층으로 설계하고, Codex
requirements.toml은 로컬에서 못 풀어내리는 상한선이며 자율성은 게이트가 안정된 뒤 한 단계씩 높입니다.
CODEOWNERS와 코드 소유권
# .github/CODEOWNERS
# 전체 기본 리뷰어
* @acme/engineering-leads
# 앱별 소유자
/apps/web/ @acme/frontend-team
/apps/admin/ @acme/frontend-team
# 패키지별 소유자
/packages/db/ @acme/backend-team @acme/platform-team
/packages/ui/ @acme/design-system-team
/packages/types/ @acme/backend-team
/packages/utils/ @acme/platform-team
# 인프라/설정 — 플랫폼팀 승인 필수
/turbo.json @acme/platform-team
/.github/ @acme/platform-team
/package.json @acme/platform-teamGitHub의 branch protection rule에서 "Require review from Code Owners"를 켜면 CODEOWNERS에 지정된 팀이 승인하지 않는 한 머지가 막힙니다.
의존성 감사
| 도구 | 방식 | 장점 | 단점 |
|---|---|---|---|
| Dependabot | GitHub 네이티브 | 무료, 설정 최소 | PR이 많이 생성됨 |
| Renovate | 오픈소스 앱 | 그룹 업데이트, 자동 머지 | 설정 복잡 |
| Socket | Supply chain 보안 | 악성 패키지 탐지 | 유료 |
// renovate.json (추천 설정)
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"group:monorepos",
":automergeMinor",
":automergePatch"
],
"packageRules": [
{
"groupName": "devDependencies",
"matchDepTypes": ["devDependencies"],
"automerge": true
},
{
"groupName": "major updates",
"matchUpdateTypes": ["major"],
"automerge": false
}
]
}Renovate의 group:monorepos는 @types/*, eslint-* 같은 관련 패키지를 하나의 PR로 묶어 PR 노이즈를 줄입니다.
정적 분석
정적 분석은 "에이전트가 잘 봐주겠지"에 맡기지 않고 CI 게이트로 고정합니다. 에이전트 스킬은 리뷰를 거드는 정도로만 쓰고, 병합 여부는 ESLint, 타입 검사, SAST, dependency audit 결과로 가릅니다.
ESLint에도 보안 규칙을 추가합니다:
// packages/eslint-config/base.js
import security from 'eslint-plugin-security';
export default [
security.configs.recommended,
{
rules: {
'security/detect-object-injection': 'warn',
'security/detect-non-literal-regexp': 'warn',
'security/detect-possible-timing-attacks': 'error',
},
},
];시크릿 관리
# .gitignore — 시크릿 파일 차단
.env
.env.local
.env.*.local
*.pem
*.key| 계층 | 저장소 | 접근 범위 |
|---|---|---|
| 로컬 개발 | .env.local (gitignore) | 개인 머신 |
| Preview | Vercel 환경 변수 (Preview 스코프) | PR 배포 |
| Production | Vercel 환경 변수 (Production 스코프) | 프로덕션만 |
| CI | GitHub Secrets | Actions 워크플로우 |
# .github/workflows/ci.yml — 시크릿 사용
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}시크릿 유출 방지
git-secrets나 gitleaks를 pre-commit hook으로 설정하면,
API 키나 비밀번호가 포함된 커밋을 차단할 수 있습니다.
에이전트 보안
에이전트 보안은 도구별 설정이 아니라 권한 계층으로 설계합니다. 로컬 설정, 조직 managed policy, CI 게이트, CODEOWNERS가 서로를 받쳐줘야 합니다.
| 계층 | 예시 | 목적 |
|---|---|---|
| 파일 접근 | .env*, private/** 읽기 차단 | 시크릿·고객 데이터 보호 |
| 명령 권한 | rm, curl, git push 제한 | 파괴적 명령과 외부 유출 방지 |
| MCP allowlist | 승인된 docs/search/db 도구만 허용 | 외부 도구 공급망 통제 |
| 승인 정책 | 위험 작업은 human 또는 auto review | 자율성과 감사 가능성 균형 |
| CI/CODEOWNERS | 테스트·보안·소유자 리뷰 필수 | 에이전트 결과물 병합 통제 |
Claude Code에서는 프로젝트 설정으로 허용 도구와 hook을 제한합니다:
// .claude/settings.json
{
"permissions": {
"allow": [
"Read",
"Write",
"Edit",
"Glob",
"Grep",
"Bash(yarn:*)",
"Bash(turbo:*)",
"Bash(git:*)",
"Bash(npx vitest:*)",
"Bash(npx playwright:*)"
],
"deny": [
"Bash(rm -rf:*)",
"Bash(curl:*)",
"Bash(wget:*)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "echo '$CLAUDE_TOOL_INPUT' | jq -r '.command' | grep -qiE '(DROP|DELETE FROM|TRUNCATE|rm -rf /)' && echo 'BLOCK: 파괴적 명령 차단' && exit 1 || exit 0"
}
]
}
}Codex managed configuration
Codex는 requirements.toml로 보안에 민감한 설정을 조직 차원에서 묶어 둡니다. ChatGPT Business/Enterprise라면 cloud-managed requirements로 CLI, IDE Extension, App에 정책을 내려보내고, MDM이나 시스템 파일로도 배포합니다.
allowed_approval_policies = ["untrusted", "on-request"]
allowed_sandbox_modes = ["read-only", "workspace-write"]
allowed_web_search_modes = ["cached"]
[features]
codex_hooks = true
in_app_browser = false
computer_use = false
[permissions.filesystem]
deny_read = [
"./.env*",
"./private/**",
]
[rules]
prefix_rules = [
{ pattern = [{ token = "rm" }], decision = "forbidden", justification = "Use an explicit cleanup script instead." },
{ pattern = [{ token = "git" }, { any_of = ["push", "commit"] }], decision = "prompt", justification = "Require review before changing shared history." },
]
[mcp_servers.docs]
identity = { command = "codex-mcp" }requirements.toml은 사용자가 로컬 config.toml로 풀어내릴 수 없는 상한선입니다. MCP 서버 allowlist를 비워 두면 모든 MCP 서버가 꺼지므로, 승인된 도구만 적어 두는 쪽으로 시작하세요.
Graduated Autonomy
에이전트 권한은 한 번에 완전 자율로 열지 않습니다. 팀 신뢰도와 감사 로그, 테스트 안정성을 보면서 한 단계씩 올립니다.
| 단계 | 에이전트 권한 | 인간 개입 | 적합한 상황 |
|---|---|---|---|
| Level 1: 승인 | 모든 도구 실행 전 승인 필요 | 높음 | 도입 초기, 민감 프로젝트 |
| Level 2: 알림 | 실행 후 알림, 위험 작업만 승인 | 중간 | 6개월+ 운영, 신뢰 구축 후 |
| Level 3: 자율 | 정책 범위 내 자율 실행 | 낮음 | 성숙한 거버넌스, 완전한 감사 체계 |
// Graduated Autonomy 구현 예시 (.claude/settings.json)
{
// Level 2: 알림 기반
"permissions": {
"allow": [
"Read", "Write", "Edit", "Glob", "Grep",
"Bash(yarn:*)", "Bash(turbo:*)", "Bash(git:*)"
],
"deny": ["Bash(rm -rf:*)", "Bash(curl:*)", "Bash(docker:*)"]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"command": "echo '[알림] 에이전트 Bash 실행: $CLAUDE_TOOL_INPUT' | notify-slack"
}
],
"PreToolUse": [
{
"matcher": "Bash",
"command": "echo '$CLAUDE_TOOL_INPUT' | jq -r '.command' | grep -qiE '(DROP|DELETE FROM|TRUNCATE)' && echo 'BLOCK: Level 2 정책 위반' && exit 1 || exit 0"
}
]
}
}자율성은 게이트 이후에 높입니다
Level 3 자율 실행은 테스트, 로그, 권한 정책, CODEOWNERS, rollback 절차가 자리 잡은 뒤에만 엽니다. 에이전트가 빠르다고 해서 병합·배포 게이트를 건너뛰면 안 됩니다.