AI 생성 풀 리퀘스트(Pull Request)를 위한 보안 체크리스트
요약
AI가 생성한 풀 리퀘스트(PR)의 보안 취약점을 방지하기 위한 검토 체크리스트를 제안합니다. AI는 정상 흐름 구현에는 능숙하지만, 권한 부여(Authorization) 및 예외 케이스 처리에 취약할 수 있으므로 면밀한 보안 리뷰가 필요합니다.
핵심 포인트
- AI 생성 코드는 해피 패스에는 강하지만 보안 예외 케이스에는 취약함
- 인증(Authentication)과 권한 부여(Authorization)를 반드시 구분하여 검토할 것
- 외부 입력부터 부수 효과까지의 데이터 경로를 추적하여 검증 누락 확인
- 객체 수준의 권한 확인(Object-level check)이 포함되었는지 점검
AI가 생성한 코드가 자동으로 보안에 취약한 것은 아닙니다.
문제는 팀이 검토할 수 있는 속도보다 더 빠르게 설득력 있는 풀 리퀘스트(Pull Request, PR)를 만들어낼 수 있다는 점입니다. Diff(차이점)가 잘 정돈되어 있고, 헬퍼(helper) 이름이 합리적으로 보이며, 테스트가 통과(green)될 수도 있습니다. 하지만 이 중 어느 것도 해당 변경 사항이 앱이 의존하는 보안 규칙을 유지하고 있다는 것을 증명하지는 않습니다.
저는 AI가 생성한 PR을 검토할 때 짧은 체크리스트를 사용합니다. 이는 우리가 Critique의 [critique-review](https://www.critique.sh/skills/critique-review) 스킬을 작성한 방식과 유사합니다: 범위를 설정하고, 폭발 반경(blast radius)을 매핑하며, 위험한 경로를 추적하고, 권한 부여(authorization)를 확인하며, 실제 코드에 근거한 결과만을 보고하는 것입니다.
AI가 생성한 코드는 흔히 해피 패스(happy path, 정상적인 흐름)를 처리하는 데 능숙합니다. 페이로드(payload)를 파싱하고, 헬퍼(helper)를 호출하며, 응답을 반환하고, 예상되는 케이스에 대한 테스트를 추가하는 식입니다.
보안 리뷰(Security review)는 대부분 그 외의 케이스들에 관한 것입니다.
웹훅(webhook) 페이로드가 재전송(replayed)된다면 어떻게 될까요? 업로드된 파일이 예상보다 크다면 어떻게 될까요? 검색된 문서에 모델을 위한 지침(instructions)이 포함되어 있다면 어떻게 될까요? 사용자가 다른 사용자의 ID를 전달한다면 어떻게 될까요?
필요하다면 경로를 적어보세요:
외부 입력(external input) -> 검증(validation) -> 권한 확인(permission check) -> 부수 효과(side effect)
만약 이 단계 중 하나라도 누락되었다면, 바로 그 지점에서 리뷰 속도를 늦추고 면밀히 살펴봐야 합니다.
3. 인증(authentication)뿐만 아니라 권한 부여(authorization)를 확인하세요
이것은 생성된 코드에서 제가 가장 자주 목격하는 실수입니다.
풀 리퀘스트(PR)는 사용자가 로그인했는지는 확인하지만, 해당 사용자가 특정 객체에 접근할 수 있는지는 확인하지 않습니다.
인증(Authentication)은 다음과 같이 묻습니다:
당신은 누구입니까?
권한 부여(Authorization)는 다음과 같이 묻습니다:
당신은 이 특정 행위를 할 권한이 있습니까?
다음 사항들을 질문하세요:
- 사용자 A가 사용자 B의 객체에 접근할 수 있는가?
- 한 테넌트(tenant)가 다른 테넌트의 데이터를 읽을 수 있는가?
- 관리자가 아닌 사용자가 관리자 전용 경로에 도달할 수 있는가?
- 변경 사항이 기존의 소유자 확인(owner check)을 우회했는가?
- API가 UI와 동일한 규칙을 강제하고 있는가?
다음은 충분하지 않습니다:
if (!session.user) {
throw new Error("Unauthorized")
}
여전히 객체 수준의 확인(object-level check)이 필요합니다:
const project = await getProject(projectId)
if (project.ownerId !== session.user.id) {
...
실제 멀티 테넌트(multi-tenant) 앱에서는 이조차도 너무 단순할 수 있습니다. 조직 멤버십(organization membership), 역할 확인(role checks), 기능 정책(feature policy) 또는 플랜 제한(plan limits)이 필요할 수도 있습니다.
핵심은 정확한 코드가 아닙니다. 핵심은 "로그인 상태"가 규칙의 전부인 경우는 거의 없다는 점입니다.
4. 모델 출력을 신뢰할 수 없는 것으로 취급하세요
만약 LLM이 권한이 있는 작업(privileged action)에 영향을 미칠 수 있다면, 그 출력은 신뢰할 수 없는 입력(untrusted input)입니다.
여기에는 다음과 같은 용도로 사용되는 출력이 포함됩니다:
- 도구 호출 (Tool calls)
- 파일 쓰기 (File writes)
- 셸 명령 (Shell commands)
- API 요청 (API requests)
- 데이터베이스 업데이트 (Database updates)
- 워크플로 라우팅 (Workflow routing)
- 프롬프트 구성 (Prompt construction)
프롬프트 인젝션(Prompt injection)은 단지 챗봇만의 문제가 아닙니다. 그것은 도구 권한 부여(tool authorization)의 문제입니다.
위험한 패턴은 다음과 같습니다:
모델이 신뢰할 수 없는 콘텐츠를 읽음 -> 모델이 동작을 결정함 -> 앱이 동작을 실행함
해결책은 단순히 "더 나은 프롬프트(Prompt)를 사용하는 것"이 아닙니다. 프롬프트가 도움이 될 수는 있지만, 보안 경계(security boundary)가 될 수는 없습니다.
지루하지만 확실한 제어 수단(boring controls)을 사용하세요:
- 도구 화이트리스트(Allowlist tools) 지정
- 모델 외부에서 도구 인자(tool arguments) 검증
- 자격 증명(credentials)의 범위를 엄격하게 제한
- 민감한 쓰기(write) 작업에 대해 확인 절차 요구
- 읽기 도구와 쓰기 도구를 분리하여 유지
- 도구 호출(tool calls) 로그 기록
- 요청이 불분명할 경우 차단(Fail closed) 처리
만약 풀 리퀘스트(PR)가 에이전트(agent) 동작을 추가한다면, 이를 새로운 공개 API(public API)처럼 검토하십시오. 해당 에이전트가 무엇을 읽을 수 있는지, 무엇을 쓸 수 있는지, 그리고 입력값이 악의적일 때 어떤 일이 발생하는지 질문하십시오.
5. 수정 사항 검증
보안에 민감한 변경 사항에 대해 "패치된 것 같다"는 말만 믿고 수락하지 마십시오.
다음 중 하나를 요구하십시오:
- 회귀 테스트(regression test)
- 재현 코드(reproducer)
- 공격 경로(exploit path)의 전/후 비교
- 코드가 현재 강제하고 있는 명확한 불변성(invariant)
좋은 검증 사례는 다음과 같습니다:
이전: 사용자 A가 ID를 통해 사용자 B의 송장을 요청할 수 있었음.
이후: API가 송장 상세 정보를 불러오기 전에 조직 구성원 여부를 확인함.
테스트: org_1의 사용자가 org_2의 송장을 요청할 때 403 에러를 받음.
이는 다음과 같은 방식보다 훨씬 낫습니다:
인증 버그 수정함.
동일한 규칙이 테스트에도 적용됩니다. 생성된 PR에 테스트가 포함될 수 있지만, 그 테스트가 무엇을 증명하는지 확인하십시오. 해피 패스(happy-path, 정상 경로) 커버리지는 유용하지만, 보안 버그를 잡아내는 것은 경계값(boundary) 커버리지입니다.
부정 테스트(negative tests)를 확인하십시오:
- 로그아웃된 사용자가 엔드포인트에 접근할 수 없음
- 일반 사용자가 관리자 동작에 접근할 수 없음
- 테넌트(Tenant) A가 테넌트 B의 설정을 업데이트할 수 없음
- 유효하지 않은 웹훅(webhook) 서명이 거부됨
- 재전송된(replayed) 웹훅 이벤트가 중복 적용되지 않음
- 모델 출력이 허용되지 않은 도구를 호출할 수 없음
만약 PR이 권한 부여(authorization)를 변경하면서 허용된 케이스만 테스트한다면, 테스트 스위트에는 여전히 중요한 부분이 누락된 것입니다.
6. 리뷰 코멘트를 구체적으로 작성하기
가장 쓸모없는 보안 리뷰는 일반적인 경고를 나열하는 것입니다.
나쁜 예:
권한이 올바른지 확인하세요.
더 나은 예:
이 엔드포인트는 세션이 존재하는지는 확인하지만, 요청된 인보이스(invoice)가 호출자의 조직(organization)에 속해 있는지는 검증하지 않습니다. 다른 인보이스 ID를 획득할 수 있는 사용자는 이를 읽을 수 있을지도 모릅니다. 인보이스를 반환하기 전에 조직 범위(organization-scoped) 쿼리를 통해 로드하거나, 인보이스의 조직을 호출자의 멤버십(memberships)과 비교하십시오.
이렇게 하면 작성자가 수정할 수 있는 구체적인 내용을 제공하게 됩니다.
이 부분이 제가 Critique의 critique-review 스킬에서 가장 좋아하는 부분입니다. 이 스킬은 리뷰어가 발견 사항(findings)과 추측(guesses)을 분리하도록 유도합니다. 실제 발견 사항은 코드 경로(code path), 영향(impact), 그리고 수정 방향(fix direction)이 필요합니다. 만약 증거가 불충분하다면, 확인된 버그인 척하지 말고 대신 미결 질문(open question)으로 분류하십시오.
AI가 생성한 코드라고 해서 완전히 다른 리뷰 프로세스가 필요한 것은 아닙니다.
오히려 더 엄격한 프로세스가 필요합니다.
사람이 작성한 프로덕션 코드(production code)에 적용하는 것과 동일한 표준을 사용하십시오:
- 폭발 반경(blast radius) 파악
- 신뢰할 수 없는 입력(untrusted input) 추적
- 객체 수준 권한 부여(object-level authorization) 확인
- 모델 출력(model output)을 신뢰할 수 없는 것으로 취급
- 보안 수정 사항에 대한 증거 요구
- 발견 사항을 코드에 근거하여 유지
목표는 AI가 생성한 풀 리퀘스트(PR)를 차단하는 것이 아닙니다. 목표는 모든 프로덕션 변경 사항이 증명해야 하는 것과 동일한 것을 증명하게 만드는 것입니다. 즉, 권한이 있는 사용자는 올바른 작업을 수행할 수 있고, 권한이 없는 사용자는 할 수 없음을 증명하는 것입니다.
만약 이러한 리뷰 태도를 재사용 가능한 형태로 원하신다면, 공개된 [critique-review](https://www.critique.sh/skills/critique-review) 스킬이 바로 그 아이디어를 바탕으로 구축되었습니다: 더 적은 일반적인 코멘트, 더 많은 근거 있는 발견 사항을 제공합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기