
AI에게 무인으로 PR을 만들게 하기 전에 분리하고 싶은 권한
요약
AI를 활용한 무인 PR 생성 자동화 과정에서 발생할 수 있는 보안 리스크와 권한 분리 전략을 다룹니다. 프롬프트 인젝션과 빌드 설정 변조를 방지하기 위해 AI의 편집 권한과 실행 권한을 격리하는 방법을 제안합니다.
핵심 포인트
- 무인 자동화 시 AI의 명령 무시 및 설정 파일 변조 리스크 존재
- 코드 편집 권한과 GitHub API 권한이 동일한 Job에 모이지 않도록 격리 필요
- 프롬프트 인젝션을 통한 임의 코드 실행 방지를 위해 빌드 단계를 분리
- 빌드 설정 변경을 차단하기 위한 차분 검사(Diff inspection) 도입
Kurashiru 사에서 Android 엔지니어를 하고 있는 kenzo입니다.
테스트나 lint 수정, 조금씩 진행하고 싶은 간단한 정형 리팩터링 (Refactoring) 정도라면 자동으로 AI가 해주었으면 좋겠다. 그렇게 생각하여, GitHub Actions 상에서 정기적으로 AI를 통해 수정부터 PR 생성까지 수행하는 메커니즘을 만들기 시작했습니다 (그 당시의 배경 등은 이 기사에 적었습니다). 동작하는 것은 금방 만들어졌지만, 그대로 두기에는 위험한 부분이 여러 군데 있어 하나씩 막았습니다. 이 기사는 같은 일을 하려는 분들을 위해, 가볍게 시도했다가 위험해질 수 있는 부분을 소개하는 것입니다. 여기서의 대책을 모두 적용한다고 해서 완전히 안전한 것은 아니지만, 빈틈은 확실히 좁아질 것이라고 생각합니다.
무인 정기 실행의 리스크
사람이 직접 AI에게 작업을 시킨다면, 이상한 움직임을 눈치챈 시점에 멈출 수 있습니다. 무인 정기 실행에서는 그 이상을 눈치채지 못하고 놓치게 됩니다. AI는 대부분 지시대로 움직이지만, 명령이나 규칙을 무시할 때도 있습니다.
또 하나는 영향이 나중에 남는다는 점입니다. 수동으로 단발 실행하는 경우에는 그 변경 사항을 반영하지 않는 한 나중에 남지 않지만, 정기 실행은 다릅니다. AI가 설정 파일이나 스크립트를 다시 써서 다음 회차의 처리 내용이 바뀌어 버릴 가능성도 있습니다.
이번에 저희가 만든 메커니즘에서는 PR과 사람의 머지 (Merge)를 사이에 두기 때문에, 다시 쓴 코드가 그대로 남는 일은 기본적으로 없습니다. 다만, 캐시나 셀프 호스트 러너 (Self-hosted runner)의 디스크 등 저장소 외부에 남는 상태는 PR을 거치지 않고 다음 회차로 이어질 가능성이 있습니다.
편집할 수 있는 AI와 조작할 수 있는 권한이 동일한 잡 (Job)에 존재
PR을 만들려면 저장소 취득이나 push, PR 생성과 같은 GitHub 권한이 필요합니다. 거기에 코드를 편집하는 AI가 더해지면, 파일을 자유롭게 다시 쓸 수 있는 힘도 동일한 잡에 모이게 됩니다. 게다가 AI에게 전달하는 입력값에 의도하지 않은 문자열이 섞여 들어올 수 있습니다. 라이브러리를 SHA로 고정하고 참조 대상을 사내로 한정하고 있다면 가능성은 낮지만, 설정 실수나 침입의 여지는 남아 있으며, 그러한 문자열이 AI에 대한 지시 (프롬프트 인젝션, Prompt Injection))가 될 수 있습니다.
파일을 편집할 수 있는 AI, 외부로 작용하는 권한이나 키, 신뢰할 수 없는 입력. 이 세 가지가 동일한 잡에 모이면, 의도하지 않은 입력으로 AI를 움직여 그 편집력과 권한을 사용하게 만들 수 있습니다. 역으로 말하면, 이후의 대책은 모두 이 세 가지를 같은 장소에 모으지 않기 위한 것입니다.

하기 쉽지만 피하고 싶은 것
빌드까지 AI에게 맡겨버리는 것
수동으로 AI에게 수정·빌드를 반복시키는 것은 일반적이라고 생각하지만, 무인 정기 실행에서는 더 큰 리스크가 있습니다. AI가 자유롭게 편집할 수 있게 되면 빌드 설정 자체를 다시 쓸 수 있습니다. 빌드는 설정에 적힌 처리를 실행하므로, 설정에 임의의 처리를 심어 빌드가 실행되면 잡 안에서 임의의 코드가 실행되게 됩니다.
그래서 AI에게는 편집과 읽기만을 허용하고, 빌드 검증은 AI의 턴이 끝난 후의 독립된 스텝 (Step)으로 옮겼습니다. AI의 턴 안에 실행 수단이 없다면, 외부 입력에 의해 유도되더라도 임의의 코드는 실행되지 않습니다. 다만, 그 검증 스텝이 AI가 다시 쓴 빌드 설정을 그대로 실행해서는 의미가 없으므로, 검증 전에 후술할 차분 검사 (Diff inspection)를 통해 빌드 설정 등의 변경을 차단합니다. AI에게 전달하는 API 키를 사용하는 스텝에만 격리하는 것도, 임의의 코드가 실행되는 경로를 차단해야 비로소 효과가 있습니다. 동일한 스텝에서 임의의 코드가 실행되면 그 API 키는 읽혀버리기 때문입니다. 이 독립된 검증 스텝 자체가 새로운 발판이 되지 않는 것도 이 순서 덕분입니다. 실행하는 것은 검증된 설정 위의 앱 코드뿐이며, 그 코드도 리뷰 대상인 차분에 포함되어 사람의 눈을 거치게 됩니다.
키를 잡 전체에 두는 것
AI에게 전달하는 API 키는 AI를 움직이는 스텝에서, GitHub 인증 정보는 push나 PR 생성 스텝에서 사용합니다. 둘 다 여러 스텝에서 사용하기 때문에 잡이나 workflow 레벨의 env:에 모아두기 쉽습니다. 하지만 그렇게 하면 동일한 잡의 어느 스텝에서든 볼 수 있으며, 위의 빌드를 통해 임의의 코드가 실행되면 읽혀버립니다. 그래서 각각 사용하는 스텝에만 전달합니다.
마찬가지로, checkout도 기본 설정 그대로 두지 않습니다. 기본적으로는 push용 인증 정보가 .git/config에 기록되어 잡 중에 남으므로, persist-credentials: false로 기록을 중단합니다. push에 필요한 인증은 그것을 수행하는 스텝에만 전달합니다.
배포를 동일한 잡에서 직접 기동해버리는 것
저희 팀에서는 수정 사항을 쉽게 확인하기 위해 디버그 버전 앱을 배포하고 있습니다. 수정과 동일한 잡(Job)에서 배포할 수 있다면 편하겠지만, 거기에는 위험이 따릅니다. 배포 처리를 동일한 잡에 직접 작성하면, 배포 권한(서명 키나 배포 인증 정보)이 AI와 동일한 잡에 공존하게 됩니다. 임의 코드 실행(Arbitrary Code Execution)이 발생하면 그 키가 그대로 읽히게 됩니다.
배포를 별도의 워크플로(Workflow)로 분리하여 호출하는 방식을 취하더라도, 해당 워크플로를 실행하기 위한 키는 필요합니다. GITHUB_TOKEN은 애초에 다른 워크플로를 실행할 수 없기 때문에, PAT(Personal Access Token)나 GitHub App을 사용하게 되며, 그 키 역시 AI와 동일한 잡에서 보일 수 있습니다. 배포 키 그 자체는 아니므로 직접적인 배포는 불가능하더라도, 그 키로 실행할 수 있는 워크플로는 돌릴 수 있습니다. 배포에 국한되지 않고, 권한이 미치는 기존 워크플로에 따라 할 수 있는 일은 더욱 확장됩니다.
이러한 키가 임의 코드 실행으로 탈취될 경우, 그중에서도 영향이 가장 큰 것이 바로 운영(Production) 배포입니다. 예를 들어, 탈취된 키로 운영 배포 워크플로가 직접 실행되어, 리뷰나 검증을 거치지 않은 빌드가 사용자에게 전달될 가능성이 있습니다. 그래서 PR 생성 잡에는 실행 권한을 부여하지 않고, 배포를 직접 실행시키지 않도록 했습니다. 배포해 달라는 요청과 결과물(Artifact)을 잡에 남겨두기만 하면, workflow_run으로 그 완료를 감지한 배포용 워크플로가 결과물과 브랜치 이름을 확인한 후 배포를 진행합니다. 실행 측이 키를 가지고 있지 않아도, GitHub이 상류(Upstream)의 완료를 감지하여 수신 측을 동작시킵니다. 수신 측에서는 배포 대상을 검증용 빌드로 고정하고, 브랜치 이름을 검증하여 예상치 못한 것은 차단합니다.
변경해서는 안 되는 것의 수정을 놓치는 경우
AI가 실수로, 혹은 유도되어 변경해서는 안 되는 것을 수정할 때가 있습니다. 보호 대상은 워크플로 자신, AI 설정, 빌드 기반(빌드 설정 및 스크립트), 그리고 이 메커니즘 자체의 스크립트입니다. 앞의 두 가지는 이 메커니즘의 동작 방식 그 자체를 결정하며, 뒤의 두 가지는 다음 빌드 시 임의 코드 실행으로 이어질 수 있습니다. 따라서 PR 생성 전에 이러한 변경 사항이 포함되어 있지 않은지 체크하고, 포함되어 있다면 중지하는 메커니즘을 도입했습니다.
다만, 이 체크 메커니즘 자체가 AI가 편집할 수 있는 위치에 있다면, AI가 이를 수정하여 체크를 통과시켜 버릴 가능성도 있습니다. 이를 피하기 위해 체크 메커니즘은 AI가 작업하는 디렉토리 외부에 두고, 그곳에서 호출하도록 했습니다. 실제로 에이전트가 태스크를 완수하려다 주어진 제약을 스스로 회피하는 동작도 보고된 바 있습니다 (How Claude Code escapes its own denylist and sandbox).

그럼에도 남는 리스크
이러한 조치를 취해도 완전히 안심할 수는 없습니다. AI가 수정하여 PR을 만들기까지의 작업은 모두 사람의 리뷰보다 먼저 자동으로 진행되므로, 그곳에 구멍이 있다면 사람의 눈으로는 잡아낼 수 없습니다. 프롬프트 인젝션(Prompt Injection)을 방지하는 수단도 확률적입니다. 입력값 제한이나 방어 문구만으로는 완전히 막을 수 있는 것이 아닙니다. GitHub Actions 자체의 함정도 AI 등장 이전부터 변함없이 남아 있습니다. 예를 들어, 외부로부터의 PR을 입력으로 받을 때의 트리거 선택 같은 문제입니다. 주요 방어책은 임의 코드 실행의 입구를 막은 것이며, 형식 검증이나 방어 문구는 그 위에 얹은 추가 조치입니다. 완전히 막아낸 것이 아니라, 영향이 가장 넓게 퍼지는 경로와 그 기점을 막았다는 것이 정확한 표현입니다. 마지막으로 작동하는 것은 사람의 리뷰입니다. 모든 PR은 Draft 상태로 생성하며, 머지(Merge)는 반드시 사람이 수행합니다.
마치며
개별적인 구멍 하나가 단독으로 큰 문제가 될 가능성은 솔직히 그리 높지 않습니다. 위험한 것은 편집 가능한 AI와 권한, 그리고 외부 입력이 결합되었을 때이며, 그 확률은 낮습니다. 다만, 이 메커니즘은 무인으로 수없이 반복되므로, 낮은 확률이라도 시행 횟수만큼 현실성을 띠게 됩니다. 게다가 만약 발생한다면 돌이킬 수 없습니다. 참고로, 이러한 구조적 대책의 일부를 처음부터 갖춘 매니지드 서비스(GitHub Copilot의 coding agent 등)에 맡기는 것도 방법이지만, 입력값 제한이나 머지 전 확인은 여전히 필요합니다. 누가 관리하고 어디서 실행되는지도 메커니즘마다 다르므로, 팀 단위로 관리하고 싶다면 선택하기 전에 확인해 두어야 합니다.
리스크를 도외시하고 작동하는 것을 만드는 것만 목적이라면, 지금은 AI를 사용하면 간단합니다. 그리고 간단하게 만든 뒤 보안 대책까지 AI에게 맡기는 것도 가능할 것입니다. 다만, 각각의 대책이 무엇을 방어하고 있는지 이해하지 못하면 그것이 없다는 사실조차 깨닫지 못할 수 있으므로, 어떤 위험이 있기에 해당 대책을 사용하는 것인지는 개발자 스스로 파악하고 있는 것이 좋다고 생각합니다.
논의 (Discussion)

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기