보안 코드리뷰 체크리스트
이 체크리스트는 AI 에이전트 Skill과 같은 파일(skills/kr-privacy-review/references/code-review-checklist.md)을 씁니다. PR 템플릿에 붙여 넣어도 됩니다.
개인정보·보안 코드리뷰 체크리스트
각 항목 끝의 표시는 근거 종류입니다. **[법률]·[시행령]·[고시]**는 법적 의무, **[표준]**은 OWASP ASVS 5.0 권고, **[권장]**은 모범사례입니다.
수집과 데이터 모델
- 새 개인정보 필드마다 수집 목적과 처리 근거가 정해져 있다 — [법률] 개인정보 보호법 제15조제1항
- 목적에 필요하지 않은 필드를 수집하지 않는다 — [법률] 제3조제1항, 제16조
- 민감정보·고유식별정보·주민등록번호는 별도 근거를 확인했다 — [법률] 제23조, 제24조, 제24조의2
- 필드에 보존기간 또는 파기 조건이 정의되어 있다 — [법률] 제21조
- 처리방침 갱신이 필요한 변경인지 표시했다 — [법률] 제30조
- 광고성 정보 수신동의를 채널별(문자·이메일·푸시)로 저장하고, 동의·거부·철회 이력을 남긴다 — 정보통신망법 제50조제1항·제7항, 같은 법 시행령 제62조의2
- 광고 발송 워커가 메시지마다 실제 전송 시각(KST)으로 수신동의·수신거부·야간(21~08시) 동의·2년 재확인 기한을 다시 확인한다 — 정보통신망법 제50조제2항·제3항·제8항, 같은 법 시행령 제61조, 제62조의3
인증과 세션
- 비밀번호는 일방향 암호화(적응형 해시: scrypt/argon2/bcrypt)로 저장한다 — [고시] 제7조제1항 / [표준] ASVS V6·V11
- 인증 실패가 일정 횟수를 넘으면 접근을 제한한다 — [고시] 제5조제6항
- 개인정보취급자가 외부에서 접속하면 안전한 인증수단(인증서·보안토큰·OTP 등)을 쓴다 — [고시] 제6조제2항
- 세션 쿠키에 HttpOnly·Secure·SameSite가 있고 로그인 시 세션 ID를 재발급한다 — [표준] ASVS V7
- 세션에 유휴·절대 만료 시간이 있다 — [표준] ASVS V7
- 토큰·비밀번호 비교에 상수 시간 비교를 쓴다 — [표준] ASVS V11
권한
- 모든 개인정보 API가 서버에서 권한을 확인한다 (UI 숨김만으로 막지 않는다) — [표준] ASVS V8
- 객체 ID로 조회할 때 소유자·소속을 검증한다 (IDOR/BOLA) — [표준] ASVS V8
- 권한은 업무에 필요한 최소 범위로 역할별 차등 부여한다 — [고시] 제5조제1항
- 권한 부여·변경·말소 내역을 기록하고 최소 3년 보관한다 — [고시] 제5조제3항
- 계정을 취급자별로 발급하고 공유 계정을 쓰지 않는다 — [고시] 제5조제4항
- 시스템 관리 권한과 개인정보 열람 권한이 분리되어 있다 — [권장]
응답·로그
- API 응답은 DTO 허용목록으로 필요한 필드만 반환한다 (ORM 엔티티 직접 직렬화 금지) — [법률] 제3조 / [표준] ASVS V4·V14
- 애플리케이션 로그·에러 트래커·APM에 개인정보와 인증 토큰이 남지 않는다 — [표준] ASVS V16 / [권장]
- 개인정보처리시스템 접속기록(누가·언제·어디서·무엇을·어떤 처리)을 남기고, 보관 기간(1년 이상, 해당 시 2년 이상)을 지킨다 — [고시] 제8조제1항
- 접속기록은 위변조·도난·분실 방지 조치가 되어 있다 — [고시] 제8조제3항
- 개인정보 다운로드 상황을 내부 관리계획에 정한 주기·방법으로 확인·점검한다 — [고시] 제8조제2항
- 대량 다운로드에 사유 입력·건수 제한을 둔다 — [권장]
암호화·전송
- 이용자의 주민등록번호·여권번호·운전면허번호·외국인등록번호·신용카드번호·계좌번호·생체인식정보는 암호화해 저장한다 — [고시] 제7조제2항
- 인증정보를 정보통신망으로 송수신할 때 암호화(TLS)한다 — [고시] 제7조제1항 / [표준] ASVS V12
- 암호화 키가 코드·저장소·로그에 없다 — [표준] ASVS V11·V13
외부 전송·LLM
- 외부 API·SaaS·LLM으로 보내는 필드가 허용목록으로 제한된다 — [권장]
- 외부 전송이 제3자 제공(제17조)인지 업무위탁(제26조)인지 판단 기록이 있다 — [법률] (판단은 review-required)
- 국외 이전 여부(처리 지역)를 확인했다 — [법률] 제28조의8
- LLM 전송 전 개인정보 탐지·마스킹이 있고, 테스트로 payload를 검증한다 — [권장]
- LLM 제공자의 학습 이용·보존 정책을 확인했다 — [안내서] (review-required)
삭제·파기
- 탈퇴·삭제 시 주 DB뿐 아니라 캐시·검색 인덱스·벡터 DB·분석 저장소에서도 지운다 — [법률] 제21조 / [권장]
- 다른 법령으로 보존해야 하는 데이터는 분리 저장한다 — [법률] 제21조제3항
- 파기 배치는 결과를 기록하고 실패를 알린다 — [고시] 제13조 / [권장]
테스트
- 권한 없는 사용자·다른 사용자의 객체 ID로 요청하는 음성 테스트가 있다
- 응답 스키마에 금지 필드(password_hash 등)가 없는지 검사하는 테스트가 있다
- 로그 마스킹 테스트가 있다
- 보존기간 만료 데이터가 파기되는지 시계를 주입한 테스트가 있다
- 테스트 픽스처·시드에 실제 개인정보가 없고, 주민등록번호·카드번호·휴대전화번호·이메일 패턴 검사가 CI에 있다 — [법률] 제3조제1항·제7항 / [권장]
- 운영 데이터를 개발·스테이징으로 옮기면 모든 컬럼에 처리 규칙이 있고, 규칙 없는 컬럼에서 변환이 실패한다 — [고시] 제12조제1항 / [권장]
테스트 케이스 템플릿
검토 보고서의 “9. 테스트 케이스”는 아래 형식으로 씁니다. 실행 가능한 예시는 가이드 저장소의 examples/python-fastapi/tests, examples/typescript/test에 있습니다.
ID: TC-<영역>-<번호>목적: <무엇을 막는지>전제: <사용자·역할·데이터 상태>절차: <요청/동작>기대 결과: <상태 코드, 응답 필드, 로그, 감사기록>근거: <[법률]/[고시]/[표준]/[권장] 조항>템플릿
| 영역 | 테스트 | 기대 결과 |
|---|---|---|
| 객체 권한 | 사용자 A가 사용자 B의 리소스 ID로 GET/PUT/DELETE | 404 또는 403, B의 데이터가 응답·로그에 없음 |
| 역할 권한 | 권한 없는 역할로 개인정보 API 호출 | 403, 접근 시도가 감사로그에 남음 |
| 관리자 분리 | 시스템 관리자(개인정보 열람 권한 없음)가 회원 상세 조회 | 403. break-glass 사용 시 사유·만료 시각·감사로그 필수 |
| 최소 응답 | 응답 JSON 키 집합 검사 | 허용목록 외 필드(password_hash, rrn 등) 없음 |
| 로그 마스킹 | 이메일·전화번호·주민등록번호·카드번호·Bearer 토큰을 포함한 로그 출력 | 로그 출력에 원문 없음 |
| 접속기록 | 개인정보 조회·다운로드 수행 | 계정·시각·접속지·처리 내용·정보주체 식별값이 기록됨, 변조 시 검증 실패 |
| 인증 실패 | 비밀번호 N회 연속 실패 | 계정 또는 접근 제한 |
| 세션 | 로그인 전후 세션 ID 비교, 유휴 시간 경과 후 요청 | 로그인 시 ID 변경, 만료 후 401 |
| 보존·파기 | 보존기간 경과 시점으로 시계를 옮기고 파기 배치 실행 | 대상 삭제, 법정 보존 대상은 분리 저장소에만 존재, 파기 기록 생성 |
| 삭제 요청 | 탈퇴 후 주 DB·캐시·검색 인덱스·벡터 DB 조회 | 결과 없음 |
| 내보내기 | 본인 데이터 내보내기 | 본인 데이터만 포함, 다른 사용자 데이터 없음 |
| LLM 필터 | 개인정보가 담긴 입력으로 기능 실행, 가짜 LLM 클라이언트로 payload 캡처 | payload에 원문 개인정보 없음, 허용목록 필드만 존재 |
| 다운로드 | 대량 내보내기 | 접속기록 생성(고시 제8조), 사유 입력·건수 제한([권장]) |