AI 생성 코드 보안 검사를 한 달 미뤄봤더니 생긴 일
배포 직전 발견된 API 키 하나가 한 달 동안의 개발 속도를 모두 되돌려 놓았습니다. AI 코딩 도구가 만든 코드는 테스트도 통과했고 화면에서도 정상 작동했지만, 저장소 기록과 로그, 배포 설정까지 다시 확인하느라 팀 전체가 이틀을 써야 했습니다.
문제는 AI가 위험한 코드를 만들었다는 사실보다 사람이 생성 결과를 일반 코드보다 덜 의심했다는 점이었습니다. 자동 생성된 코드를 빠르게 합치는 팀이라면 아래 실패를 남의 이야기로 넘기지 마세요.
첫 주에는 테스트 통과를 안전하다는 뜻으로 받아들였다
기능 테스트가 찾아내지 못한 보안 결함
처음에는 로그인, 파일 업로드, 외부 API 호출만 정상적으로 동작하면 충분하다고 생각했습니다. AI가 작성한 함수에는 단위 테스트가 붙어 있었고 테스트 커버리지도 높았기 때문에 리뷰어는 구현 세부 사항보다 기능 요구사항 충족 여부만 확인했습니다. 하지만 테스트 성공은 기대한 입력에서 기능이 작동했다는 의미이지, 공격자가 보낼 비정상 입력까지 안전하게 처리한다는 보증이 아닙니다.
실제로 파일 업로드 기능은 확장자만 검사하고 MIME 유형과 파일 내용을 검증하지 않았습니다. 데이터 조회 API는 정상적인 숫자 ID에서는 잘 작동했지만, 권한 확인이 조회 뒤에 실행되어 다른 사용자의 데이터 존재 여부를 추측할 여지를 남겼습니다. 코드 보안의 기본 개념이 낯설다면 지식백과의 코드 보안 설명을 함께 참고하면 기능 정확성과 보안 검증이 왜 다른지 이해하기 쉽습니다.
이 단계에서 특히 위험했던 습관은 AI에게 “보안 테스트도 추가해 줘”라고 한 문장만 요청한 것입니다. 생성 도구는 자신이 작성한 구현을 기준으로 비슷한 정상 경로를 반복 검증하는 경향이 있으므로, 사람이 위협 시나리오를 먼저 나누지 않으면 테스트 수만 늘고 방어 범위는 넓어지지 않습니다.
- 인증과 인가를 구분하지 않기: 로그인 여부만 검사하고 해당 자원에 접근할 권한은 확인하지 않습니다.
- 입력 검증을 프런트엔드에만 두기: 브라우저 검사를 우회한 직접 요청에 서버가 그대로 노출됩니다.
- 오류 메시지를 그대로 반환하기: 내부 경로, 쿼리 구조, 사용 중인 라이브러리 정보가 응답에 포함될 수 있습니다.
- 정상 입력만 테스트하기: 빈 값, 초과 길이, 중복 요청, 다른 사용자의 식별자 같은 경계 조건이 빠집니다.
실무 팁: AI에게 테스트를 맡기기 전에 “누가, 어떤 입력으로, 무엇을 빼내거나 바꿀 수 있는가”를 세 줄로 적으세요. 이 문장이 보안 테스트의 출발점이 됩니다.
둘째 주에는 비밀값을 지우면 흔적도 사라진다고 착각했다
코드에서 삭제해도 저장소 기록에는 남는다
외부 서비스 연결 예제를 요청했을 때 AI는 환경 변수 사용법과 함께 테스트용 토큰을 코드 상단에 넣었습니다. 개발자는 임시 값이라고 생각해 커밋했고, 몇 시간 뒤 환경 변수로 바꿨습니다. 현재 파일에서 토큰이 보이지 않자 문제가 해결됐다고 판단했지만, 값은 이미 Git 커밋 기록과 코드 리뷰 알림에 남아 있었습니다.
노출된 비밀값은 삭제가 아니라 폐기와 재발급이 원칙입니다. 커밋을 되돌리거나 기록을 정리해도 이미 복제된 저장소, CI 로그, 협업 도구 알림까지 회수할 수는 없습니다. 암호와 키를 다루는 배경 개념은 관련 코드 용어 설명처럼 기초 자료를 확인하되, 실제 대응은 사용하는 클라우드와 API 제공자의 키 폐기 절차를 따라야 합니다.
비용도 단순히 새 키를 만드는 데서 끝나지 않았습니다. 저장소 접근 기록 확인, 배포 환경 변수 교체, 연동 서비스 재시작, 비정상 호출 점검에 개발자와 운영 담당자의 시간이 함께 들어갔습니다. 유료 보안 도구를 도입하지 않더라도 커밋 전 비밀값 탐지와 보호 브랜치 규칙은 먼저 적용할 수 있습니다.
- 노출 사실을 발견하면 해당 키의 사용을 즉시 중지하고 새 키를 발급합니다.
- 접근 로그에서 평소와 다른 IP, 시간대, 호출량이 있었는지 확인합니다.
- 개발·스테이징·운영 환경에 저장된 값을 각각 교체하고 재배포합니다.
- 저장소 기록 정리는 영향 범위를 확인한 뒤 협업자와 조율해 진행합니다.
- 같은 형태가 반복되지 않도록 커밋 전 검사와 CI 비밀값 탐지를 추가합니다.
예제 코드의 편리함이 만든 두 번째 실수
또 하나의 문제는 AI가 제시한 예제 설정을 운영 환경에 거의 그대로 복사한 일이었습니다. 모든 출처를 허용하는 CORS 설정, 상세 오류 출력, 과도한 데이터베이스 권한은 로컬에서는 편리하지만 운영에서는 공격 표면을 넓힙니다. “나중에 닫자”는 주석은 배포 일정이 바빠지는 순간 가장 먼저 잊히므로 개발 전용 설정을 별도 파일과 명시적인 환경 조건으로 격리해야 합니다.
- 소스 코드와 프롬프트에 실제 API 키나 고객 데이터를 넣지 않습니다.
- 예제의 와일드카드 권한은 허용 대상 목록으로 교체합니다.
- 운영 로그에서는 토큰, 쿠키, 주민등록번호와 결제 정보를 마스킹합니다.
- 서비스 계정에는 필요한 읽기·쓰기 권한만 부여하고 만료 정책을 설정합니다.
셋째 주에는 의존성 이름만 맞으면 설치해도 된다고 여겼다
존재하지 않거나 오래된 패키지를 추천받았을 때
AI에게 인증 기능을 빠르게 구현해 달라고 요청하자 그럴듯한 패키지 이름과 설치 명령이 제시됐습니다. 이름이 자연스러워 별도 확인 없이 설치하려 했지만, 공식 문서에서 찾을 수 없는 패키지였습니다. 이런 경우 비슷한 이름의 악성 패키지나 관리가 중단된 프로젝트를 선택할 위험이 있으므로 설치 명령을 실행하기 전에 출처와 유지 상태를 사람이 확인해야 합니다.
확인 항목은 다운로드 수 하나로 끝나지 않습니다. 공식 저장소와 패키지 배포 계정이 연결되는지, 최근 릴리스와 보안 공지가 있는지, 유지관리자가 갑자기 바뀌지 않았는지 살펴봐야 합니다. 이미 잠금 파일이 있는 프로젝트라면 AI가 편의상 버전을 크게 올리지 못하게 하고, 변경된 전이 의존성까지 코드 리뷰 범위에 포함하는 편이 안전합니다.
무료 도구만으로도 기본 방어선은 만들 수 있습니다. 패키지 관리자의 감사 명령, 저장소 호스팅 서비스의 의존성 경고, 정적 분석을 CI에 연결하고 실패 기준을 정하세요. 다만 경고를 무조건 차단하면 개발자가 검사를 우회하기 쉬우므로 심각도, 외부 노출 여부, 실제 호출 경로를 함께 보고 처리 기한을 나누는 방식이 현실적입니다.
| 실수 | 즉시 확인할 항목 | 권장 대응 |
|---|---|---|
| AI가 새 패키지를 추천함 | 공식 문서, 저장소, 배포 계정 | 출처가 연결되지 않으면 설치 보류 |
| 취약점 경고가 발생함 | 심각도와 실제 사용 경로 | 외부 노출 경로부터 우선 수정 |
| 잠금 파일이 크게 바뀜 | 전이 의존성과 주 버전 변경 | 기능 코드와 분리해 별도 리뷰 |
| 유지보수가 중단됨 | 최근 릴리스와 미해결 이슈 | 대체 패키지와 제거 비용 검토 |
자동 수정 버튼도 검토 없이 누르지 않았다
취약점 검사기가 제안하는 자동 업데이트는 빠르지만 항상 안전한 것은 아닙니다. 주 버전이 바뀌면서 인증 기본값이나 직렬화 방식이 달라질 수 있고, 보안 문제는 사라져도 기능 장애가 생길 수 있습니다. 그래서 의존성 수정 커밋은 기능 변경과 분리하고, 회귀 테스트와 배포 전후 지표를 붙여 되돌릴 수 있게 했습니다.
- 패키지 이름과 설치 명령은 공식 문서에서 다시 확인합니다.
- 새 의존성이 꼭 필요한지 표준 라이브러리와 기존 패키지를 먼저 검토합니다.
- 버전 범위를 느슨하게 두지 말고 잠금 파일을 함께 관리합니다.
- 자동 업데이트 후 인증, 결제, 파일 처리처럼 위험도가 높은 경로를 재시험합니다.
- 예외 승인에는 담당자, 사유, 만료일을 기록해 영구 방치하지 않습니다.
보안 경고가 많다는 이유로 전부 무시하는 순간 검사는 장식이 됩니다. 당장 고치지 못하는 항목에도 소유자와 처리 날짜를 붙여야 합니다.
넷째 주의 작은 프로필 API를 배포까지 따라가 봤다
자동 생성에서 운영 확인까지 이어진 실제 흐름
마지막 주에는 실패를 줄이기 위해 작은 프로필 조회 API 하나를 대상으로 절차를 바꿨습니다. AI에는 사용자 ID로 이름과 프로필 이미지를 조회하는 코드를 요청하되, 인증 방식과 반환 가능한 필드, 다른 사용자의 정보를 조회할 조건을 먼저 명시했습니다. 생성 결과는 바로 합치지 않고 별도 브랜치에 두었으며 개발자가 데이터 흐름을 한 줄씩 추적했습니다.
첫 검토에서 AI 코드는 로그인 여부는 확인했지만 요청한 ID가 본인 것인지 검사하지 않았습니다. 응답 객체에는 화면에서 쓰지 않는 이메일과 내부 등급도 포함돼 있었습니다. 팀은 조회 조건에 소유권 검사를 추가하고 반환 형식을 허용 목록 방식으로 바꿨습니다. 보안 원칙을 더 확인할 때는 코드 보안 관련 요약 자료도 참고했지만, 최종 판단 기준은 서비스의 실제 데이터 분류와 접근 정책으로 삼았습니다.
수정 후에는 정상 사용자, 로그아웃 사용자, 다른 사용자의 ID, 존재하지 않는 ID, 지나치게 긴 ID를 각각 시험했습니다. 스테이징 배포에서는 응답 본문과 로그에 민감 정보가 남지 않는지 확인했고, 운영 배포는 일부 트래픽부터 열어 인증 실패율과 오류율을 관찰했습니다. 다른 사용자 ID 요청이 403으로 거부되고 정상 요청의 지연 시간도 기존 범위에 머문 것을 확인한 뒤에야 전체 트래픽을 전환했습니다.
- 요청 전: 데이터 종류, 공격 가능성, 허용 권한을 프롬프트와 작업 문서에 적었습니다.
- 생성 직후: 새 패키지, 비밀값, 위험한 기본 설정을 먼저 검색했습니다.
- 코드 리뷰: 인증뿐 아니라 객체 단위 인가와 응답 필드 제한을 확인했습니다.
- 검증 단계: 정상 경로와 함께 권한 우회, 잘못된 입력, 반복 요청을 시험했습니다.
- 배포 단계: 작은 비율로 공개하고 오류율, 거부율, 비정상 조회 패턴을 관찰했습니다.
한 달 뒤 남은 규칙은 세 개뿐이었다
팀은 복잡한 문서를 새로 만들기보다 모든 AI 생성 코드에 적용할 세 가지 규칙을 저장소 템플릿에 넣었습니다. 첫째, 작성자가 보안 경계를 설명하지 못하면 병합하지 않습니다. 둘째, 비밀값과 새 의존성 검사를 자동화합니다. 셋째, 인증·결제·개인정보 경로에는 실패 테스트와 배포 후 관찰 항목을 반드시 붙입니다. 이 프로필 API는 배포 뒤 일주일 동안 권한 우회 요청 없이 안정적으로 동작했고, 다음 기능부터는 같은 템플릿이 작업 시작 조건이 됐습니다.
- 이 코드가 읽거나 바꾸는 데이터의 소유자는 누구인가?
- 공격자가 직접 요청을 만들면 어떤 검사를 우회할 수 있는가?
- 로그와 오류 응답에 내부 정보가 노출되지 않는가?
- 새 비밀값과 의존성이 저장소 정책을 통과했는가?
- 문제가 생겼을 때 배포를 즉시 되돌리고 영향을 추적할 수 있는가?

- 다음글Cursor와 GitHub Copilot, 팀 저장소에서는 무엇이 빠를까 26.09.12
등록된 댓글이 없습니다.
