AI 코드 리뷰어가 찾아낸 22개 이슈: Critical은 전부, Warning은 선택적으로

시리즈: AI 팀과 함께 기술블로그 만들기 | Episode 6
작성일: 2026-02-08
태그: code-review, docker, security, best-practices, ai


이전 에피소드에서 UX를 다듬었습니다. 이제 돌아보겠습니다. AI 코드 리뷰 에이전트가 초기 설정에서 찾아낸 22개 이슈를 하나씩 분석합니다. 무엇을 수정했고, 무엇을 무시했는지. 그리고 왜 그랬는지.


리뷰 결과 요약

등급발견수정채택률
🔴 Critical77100%
🟡 Warning9889%
🟢 Info6467%
합계221986%

Critical은 전부 수정했습니다. Warning 중 하나는 Cloudflare 환경 때문에 따르지 않았고, Info 중 일부는 우선순위가 낮아서 보류했습니다.


Critical 이슈: 전부 수정

Critical-01: PostgreSQL 포트가 인터넷에 노출

발견:

# docker-compose.yml (초기)
postgres:
  ports:
    - "5432:5432"  # 0.0.0.0:5432에 바인딩

문제점:

  • WordPress는 PostgreSQL을 지원하지 않음
  • 사용하지도 않는 DB가 인터넷에 포트 열림
  • 방화벽 없으면 누구나 DB 접속 시도 가능

수정:

# docker-compose.yml (수정 후)
# postgres 서비스 전체 삭제

PostgreSQL을 완전히 제거했습니다. 불필요한 서비스는 보안 리스크입니다.


Critical-02: WordPress latest 태그

발견:

wordpress:
  image: wordpress:latest

문제점:

  • 빌드 시점마다 다른 버전 설치 가능
  • 메이저 업데이트로 사이트 깨질 위험
  • 재현 불가능한 배포

수정:

wordpress:
  image: wordpress:6.9-php8.3-apache

PHP 8.3을 명시한 것도 중요합니다. JIT 컴파일러를 활용하기 위해서입니다.


Critical-03: Health Check 없음

발견:

# 모든 서비스에 healthcheck 없음

문제점:

  • 프로세스는 살아있지만 응답하지 않는 “좀비” 상태 감지 불가
  • docker compose up 완료 ≠ 서비스 준비 완료

수정:

wordpress:
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:80/"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s

mariadb:
  healthcheck:
    test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
    interval: 30s
    start_period: 30s

redis:
  healthcheck:
    test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
    interval: 10s

MariaDB의 --innodb_initialized가 핵심입니다. 포트만 열렸는지가 아니라, InnoDB 엔진이 완전히 초기화되었는지까지 확인합니다.


Critical-04: MariaDB와 WordPress 비밀번호 불일치

발견:

# .env (초기)
MARIADB_PASSWORD=d&a9X5McNt7s2PX^@$M^
WORDPRESS_DB_PASSWORD=different_password_causes_error

문제점:

  • 끝자리가 ^8로 다름
  • MariaDB가 ^로 사용자 생성, WordPress가 8로 접속 → 인증 실패
  • 게다가 $M이 Docker 환경변수 보간으로 빈 문자열로 치환됨

수정:

# .env (수정 후)
MARIADB_PASSWORD=your_secure_password_here_hex32
WORDPRESS_DB_PASSWORD=your_secure_password_here_hex32

openssl rand -hex 16으로 생성한 hex-only 비밀번호입니다. $, @, & 같은 특수문자가 없으므로:

  • Docker $ 보간 문제 없음
  • 셸 이스케이핑 문제 없음
  • URL 인코딩 문제 없음

.env.example에 주석으로 명시했습니다.

MARIADB_PASSWORD=                # 필수! WORDPRESS_DB_PASSWORD와 반드시 동일한 값
WORDPRESS_DB_PASSWORD=           # 필수! MARIADB_PASSWORD와 반드시 동일한 값

Critical-05: WordPress Salt가 기본값

발견:

// wp-config.php (Docker 이미지 기본값)
define('AUTH_KEY', getenv_docker('WORDPRESS_AUTH_KEY',
    'ebe9df86132e4bef46fbc0cfa0f3869f7e23bc1f'));

문제점:

  • 기본값은 Docker 이미지 소스코드에 공개됨
  • 누구든 이 값을 알면 세션 쿠키 위조 가능

수정:

# .env.example
WORDPRESS_AUTH_KEY=
WORDPRESS_SECURE_AUTH_KEY=
WORDPRESS_LOGGED_IN_KEY=
WORDPRESS_NONCE_KEY=
WORDPRESS_AUTH_SALT=
WORDPRESS_SECURE_AUTH_SALT=
WORDPRESS_LOGGED_IN_SALT=
WORDPRESS_NONCE_SALT=

8개의 Salt를 환경변수로 분리했습니다. https://api.wordpress.org/secret-key/1.1/salt/ 에서 생성한 값을 사용합니다.


Critical-06: Resource Limits 없음

발견:

# 모든 서비스에 리소스 제한 없음

문제점:

  • 하나의 서비스가 서버 전체 메모리 소비 가능
  • OOM으로 서버 다운 시 SSH 접속도 불가

수정:

wordpress:
  deploy:
    resources:
      limits:
        memory: 512M
      reservations:
        memory: 256M

mariadb:
  deploy:
    resources:
      limits:
        memory: 512M

redis:
  deploy:
    resources:
      limits:
        memory: 192M

합계: 최소 576M, 최대 1,216M. 1GB 메모리 VPS에서도 동작 가능한 사이즈입니다.


Critical-07: 로깅 설정 없음

발견:

# 로그 파일 크기 제한 없음

문제점:

  • Docker 로그가 디스크를 가득 채울 수 있음
  • Redis는 디버그 레벨에서 초당 수십 줄 로그

수정:

x-logging: &default-logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

services:
  wordpress:
    logging: *default-logging
  mariadb:
    logging: *default-logging
  redis:
    logging: *default-logging

각 서비스의 로그가 최대 30MB(10MB × 3파일)로 제한됩니다.


Warning 이슈: 8개 수정, 1개 무시

Warning-01: depends_on에 condition 미사용

발견:

wordpress:
  depends_on:
    - mariadb
    - redis

문제점:

  • 컨테이너 시작 순서만 보장
  • MariaDB가 “실제로 쿼리를 받을 준비가 됨”을 보장하지 않음

수정:

wordpress:
  depends_on:
    mariadb:
      condition: service_healthy
    redis:
      condition: service_healthy

Health check와 조합하여 서비스가 완전히 준비된 후에 WordPress가 시작됩니다.


Warning-02~05: 이미지 버전, 네트워크, Redis 인증

항목BeforeAfter
Redis 이미지redis:alpineredis:7-alpine
internal 네트워크기본internal: true
Redis 인증없음--requirepass ${REDIS_PASSWORD}
MariaDB 이미지mariadbmariadb:10.11

모두 수정했습니다.


Warning-06: Caddy HTTPS 설정 (유일하게 무시한 Warning)

AI 권장:

labels:
  caddy: "yndl.dev"  # http:// 제거하면 auto-HTTPS 활성화

실제 적용:

labels:
  caddy: "http://yndl.dev"  # http:// 유지

왜 무시했는가:

AI 권장대로 http://를 제거하면 Caddy가 auto-HTTPS를 활성화합니다. 하지만 이 블로그는 Cloudflare 뒤에 있습니다.

[사용자] --HTTPS--> [Cloudflare] --HTTP--> [Caddy] --HTTP--> [WordPress]

Cloudflare가 이미 SSL을 처리합니다. Caddy가 또 HTTPS를 하려고 하면:

  1. Caddy가 auto-HTTPS 활성화
  2. Let’s Encrypt에 인증서 발급 요청
  3. DNS-01 챌린지 시도 → Cloudflare 프록시 뒤에 있어서 실패
  4. 사이트 전체 다운 (308 무한 리다이렉트)

Episode 3에서 자세히 다뤘습니다.

핵심 원칙:

Cloudflare Proxy 뒤에서는 Caddy의 auto-HTTPS를 끄세요. SSL은 Cloudflare가 처리합니다.

AI는 개별 컴포넌트의 Best Practice는 정확히 아는 반면, 컴포넌트 간의 상호작용(Cloudflare + Caddy + WordPress 체인)은 놓칠 수 있습니다.


Info 이슈: 4개 수정, 2개 보류

Info-01: wp-config.php 보안 상수

권장:

define('DISALLOW_FILE_EDIT', true);
define('FORCE_SSL_ADMIN', true);
define('WP_AUTO_UPDATE_CORE', 'minor');

수정: ✅ 전부 적용

DISALLOW_FILE_EDIT는 특히 중요합니다. WordPress 관리자 패널의 “테마 편집기”를 비활성화합니다. 공격자가 관리자 계정을 탈취해도 편집기로 백도어를 심을 수 없습니다.


Info-02: OPcache 설정

권장:

opcache.jit=1255
opcache.jit_buffer_size=64M

수정: ✅ 적용

PHP 8.3의 JIT 컴파일러를 활성화했습니다. 1255는 Tracing JIT 모드로, CPU-bound 작업에서 30~50% 성능 향상 가능성이 있습니다.


Info-03~04: 백업 스크립트, .gitignore

항목권장적용상태
백업 스크립트4가지 모드 (db, content, full, pre-deploy)scripts/backup.sh
.gitignorewp_content/plugins/*, themes/*, languages/ 제외설치 가능한 항목 제외

둘 다 적용했습니다.


Info-05~06: 보류

항목권장상태
Redis 메모리 정책maxmemory-policy allkeys-lru⏸️ 추후 적용 예정
DB 쿼리 최적화Slow query log 활성화⏸️ 필요시 적용

Redis 메모리 정책은 Episode 4에서 이미 적용했습니다. DB 쿼리 최적화는 아직 병목이 없어서 보류했습니다.


코드 리뷰 에이전트의 가장 큰 공헌

1. 연쇄 이슈 발견

AI는 독립적인 이슈를 나열한 것이 아니라, 연쇄적 관계를 파악했습니다.

비밀번호에 $가 포함
  ↓
Docker 변수 보간으로 $M이 빈 문자열로 치환
  ↓
MARIADB_PASSWORD와 WORDPRESS_DB_PASSWORD가 실제로는 다른 값
  ↓
DB 인증 실패
  ↓
WordPress 설치 불가

이 연쇄를 한 번에 파악하고 “hex-only 비밀번호를 사용하라”는 근본 해결책을 제시한 것은 코드 리뷰 에이전트의 가장 큰 공헌입니다.


2. 우선순위 분류의 정확성

22개 이슈를 Critical / Warning / Info로 분류한 것이 매우 정확했습니다.

Critical 7개는 실제로 전부 보안 또는 안정성에 직접 영향을 미치는 것들이었습니다:

  • PostgreSQL 포트 노출
  • 비밀번호 불일치
  • Salt 기본값
  • Health check 없음
  • Resource limits 없음
  • 로깅 무제한

Warning 9개는 “지금 당장은 괜찮지만, 프로덕션에서는 문제가 될 수 있는 것들”이었습니다.

Info 6개는 “Best Practice이지만 필수는 아닌 것들”이었습니다.

이 분류 덕분에 “Critical은 무조건, Warning은 선택적으로, Info는 시간 날 때”라는 명확한 우선순위를 세울 수 있었습니다.


3. 컨텍스트 없는 추천의 한계를 보여줌

Caddy HTTPS 설정(Warning-06)은 AI가 틀린 것이 아닙니다. Caddy 단독으로 보면 100% 맞는 권장 사항입니다.

하지만 Cloudflare → Caddy → WordPress라는 전체 인프라 체인을 모른 채 권장했습니다. 이것은 AI의 한계를 명확히 보여줍니다.

인프라의 전체 그림은 사람이 판단해야 한다.

AI는 개별 파일을 완벽히 분석하지만, 외부 서비스(Cloudflare, DNS, 방화벽)와의 상호작용은 프로젝트 디렉토리 밖에 있어서 알 수 없습니다.


수정 전후 비교

docker-compose.yml

항목BeforeAfter
이미지 태그wordpress:latestwordpress:6.9-php8.3-apache
Health check없음3개 서비스 모두
Resource limits없음합계 최대 1,216M
네트워크기본proxy_net + internal(격리)
로깅무제한서비스당 30MB
불필요 서비스PostgreSQL 노출삭제

.env

항목BeforeAfter
비밀번호특수문자 포함 ($, @, &)hex-only
DB 비밀번호불일치동일 (명시적 주석)
Salt기본값환경변수 분리 (8개)
Redis 인증없음환경변수 비밀번호

PHP 설정

항목BeforeAfter
OPcache기본값JIT 활성화 (1255)
업로드 제한2M64M
메모리 제한기본256M

채택률 86%의 의미

22개 중 19개 수정 (86%)

AI 권장을 따르지 않은 3건:

  1. Caddy HTTPS (Warning-06): Cloudflare 환경 때문에 무시
  2. Redis 메모리 정책 (Info-05): 이미 적용함
  3. DB 쿼리 최적화 (Info-06): 아직 필요 없음

3건 모두 “AI가 틀렸기” 때문이 아니라 “AI가 모르는 컨텍스트가 있었기” 때문입니다.


이 경험에서 배운 것

1. Critical은 무조건 따라라

보안과 안정성에 관한 문제는 “나중에”가 없습니다. PostgreSQL 포트 노출, 비밀번호 불일치, Salt 기본값 — 이런 것들은 발견 즉시 수정해야 합니다.

AI가 Critical로 분류한 7건은 실제로 전부 치명적이었습니다. 하나라도 놓쳤으면 보안 사고나 서비스 다운으로 이어질 수 있었습니다.


2. 외부 컨텍스트는 AI가 알 수 없다

Caddy HTTPS 설정처럼, 프로젝트 디렉토리 밖의 정보(Cloudflare, DNS, 서버 방화벽)는 AI가 알 수 없습니다.

AI 권장을 맹목적으로 따르면 위험합니다. 전체 시스템 아키텍처를 아는 사람이 최종 판단을 내려야 합니다.


3. hex-only 비밀번호 원칙

openssl rand -hex 16으로 생성한 hex-only 비밀번호는:

  • Docker 환경변수 보간 문제 없음
  • 셸 이스케이핑 문제 없음
  • URL 인코딩 문제 없음
  • 특수문자 입력 오류 없음

이후 모든 프로젝트에서 이 원칙을 따르고 있습니다.


4. 보고서는 “의사 결정 프레임워크”다

AI 코드 리뷰 보고서를 “실행 매뉴얼”로 쓰면 위험합니다. Caddy HTTPS처럼 맹목적으로 따르면 사이트가 다운됩니다.

대신 “어떤 문제가 있고, 각각의 심각도가 어느 정도인지”를 파악하는 도구로 쓰면 매우 효율적입니다. 최종 결정은 전체 컨텍스트를 아는 사람이 내려야 합니다.


다음 에피소드 예고

지금까지 인프라, UX, 코드 리뷰를 다뤘습니다. 다음 에피소드에서는 마크다운으로 글을 쓰고 자동으로 WordPress에 발행하는 시스템을 구축합니다. Episode 1~6을 모두 마크다운으로 작성했는데, 어떻게 WordPress 블록 마크업으로 변환하고 발행했는지 공개합니다.

예고: Episode 7 — 마크다운으로 글 쓰고 자동 발행하기


시리즈 안내: AI 팀과 함께 기술블로그 만들기

회차제목상태
1Agent Teams란? 3인 AI 팀 구성기발행
2AI 3인이 내 블로그를 진단했다발행
3Caddy와 싸운 하루: 리다이렉트 루프 3연전발행
4Docker + WordPress + Redis: AI가 설계한 인프라 적용기발행
5기술블로그 UX: 코드가 읽히는 블로그발행
6AI 코드 리뷰어가 찾아낸 22개 이슈현재 글
7마크다운으로 글 쓰고 자동 발행하기예정
8AI 팀 vs 혼자 코딩: 비용 대비 효과 분석예정

이 글은 Claude Code를 사용하여 작성되었습니다.
프로젝트: yndl.dev

댓글 남기기