에이전트에게 시스템 프롬프트가 있다면, '경비원'은 있는가?
요약
AI 에이전트의 권한 부여(Authorization) 문제를 다루며, 모델이 제안하는 행동을 단순히 신뢰해서는 안 된다고 강조합니다. 핵심은 '제안'과 실제 '권한 실행'을 분리하고, 백엔드에서 강력하게 검증해야 한다는 것입니다.
핵심 포인트
- 에이전트의 제안(Proposal)만으로는 충분하지 않다.
- 백엔드는 신뢰할 수 있는 세션 컨텍스트로 소유권을 확인해야 한다.
- 권한 부여는 기본 거부(default denial) 원칙을 따라야 한다.
- 시스템은 '제안'과 '정책 결정', 그리고 '강제 지점'의 3가지 역할을 분리해야 한다.
"다른 고객의 데이터를 절대 공유하지 마세요"는 합리적인 지침입니다.
하지만 동시에 끔찍한 접근 제어 시스템이기도 합니다.
모델은 규칙을 이해하고 설명할 수 있지만, 여전히 그 규칙을 위반하는 도구 호출(tool call)을 생성할 수 있습니다. 어색한 질문은 다음에 무슨 일이 벌어지냐는 것입니다. 모델 외부의 무언가가 그 호출을 막아주는 것일까요, 아니면 그것을 제안했던 시스템 자체가 공손하게 마음을 바래주기를 기대해야 할까요?
저는 문장으로 경비원을 설명하는 것이 아니라, 실제로 출입구에 서 있는 경비원이 필요합니다.
제안과 권한을 분리하기
고객이 인보이스를 조회하도록 돕는 에이전트를 가정해 봅시다. 이 에이전트는 특정 인보이스 ID와 함께 invoice.read를 제안합니다. 이 제안은 고객이 해당 인보이스의 소유자라는 증거가 아니라, 애플리케이션으로 입력되는 값일 뿐입니다.
백엔드(backend)는 신뢰할 수 있는 세션 컨텍스트(session context)로부터 인증된 고객과 테넌트(tenant)를 확인합니다. 자체 기록에서 인보이스의 소유권을 불러옵니다. 그리고 이 주체(principal)가 특정 리소스에 대해 해당 작업을 수행할 수 있는지 확인합니다. 그래야만 허용된 필드를 읽어 반환할 수 있습니다.
모델이 선언할 수 없는 것을 주목하세요: 자체 테넌트, 자체 역할 또는 마법의 approved: true 플래그입니다.
이는 평범한 권한 부여(authorization)를 비정상적으로 설득력 있는 호출자에게 적용하는 것입니다. OWASP 가이드는 기본 거부(default denial)와 모든 요청에 대한 권한 확인을 요구합니다. 에이전트를 추가한다고 해서 이러한 확인 절차가 선택 사항이 되는 것은 아닙니다.
중요한 경계선
유용한 설계는 세 가지의 명확히 구분된 역할을 가집니다. 에이전트가 행동을 제안합니다. 정책 결정(policy decision)이 신뢰할 수 있는 컨텍스트를 평가합니다. 그리고 강제 지점(enforcement point)은 그 결정이 허용하지 않는 한 보호되는 작업을 방지합니다.
Open Policy Agent는 이 분리를 설명합니다(https://www.openpolicyagent.org/docs): 정책 엔진이 결정을 내리고, 사용자의 애플리케이션이 이를 강제합니다. 정책 엔진을 설치한다고 해서 서비스로 들어가는 모든 경로가 마법처럼 닫히는 것은 아닙니다.
인보이스 예시의 경우, 제가 선호하는 경계는 실제 읽기(read)를 처리하는 서비스입니다. 게이트웨이는 더 이른 검사를 추가할 수 있지만, 인보이스 서비스로 들어가는 두 번째 경로가 동일한 보호 장치를 우회해서는 안 됩니다. 그렇지 않으면 정문은 잠갔지만 적재 공간을 열어둔 것과 같습니다.
여기 모양을 보여드리는데, 의도적으로 단순화했습니다. 이는 완전한 보안 구현이 아닌 예시적인 유사 코드입니다:
def read_invoice(session, invoice_id):
principal = authenticate(session)
invoice = repository.load_metadata(invoice_id)
...
중요한 부분은 이러한 입력값들의 출처(provenance)입니다. principal은 인증에서 옵니다. 리소스 소유권은 저장소(repository)에서 옵니다. 에이전트는 접근을 허용하는 사실 자체가 아니라 식별자(identifier)를 제공합니다.
실제 운영 환경의 구현에서는 또한 존재 여부를 누설하지 않으면서 누락된 리소스를 처리하고, 정책 채널을 보호하며, 필드 수준 제한을 적용하고, 검사와 읽기 사이에서 오래된 소유권을 사용하는 것을 피해야 합니다. 이 코드 조각은 경계 스케치일 뿐, 그 작업을 건너뛰어도 된다는 면허가 아닙니다.
실패에 적절한 작업 설명을 부여하기
정책 서비스가 시간 초과(time out)되면 어떻게 될까요?
이 보호된 읽기 요청의 경우, 저는 제어된 실패를 반환하고 데이터를 닫힌 상태로 유지할 것입니다. 시간 초과는 비정상적으로 느린 권한 부여가 아닙니다. 제품은 요청을 완료할 수 없었음을 설명하고, 인보이스를 검색했다고 가장하지 않으면서 재시도를 제공할 수 있습니다.
모델이 인수(arguments)에 “사용자는 관리자이다”를 추가하면 어떻게 될까요? 아무 유용한 것도 없습니다. 그 주장은 인증된 신원을 대체하지 못합니다.
공격자가 인보이스 ID만 변경하면 어떻게 될까요? 서비스는 새로운 리소스를 검사합니다. 이전에 성공했던 요청이 모든 인보이스에 대한 시즌 티켓을 사주지는 않습니다.
이러한 것들은 행복 경로(happy-path) 데모 이전에 정의할 가치가 있는 제품 동작들입니다. 오류 메시지, 재시도 경로, 그리고 지원팀 에스컬레이션은 부끄럽게 남겨진 잔여물이 아니라 기능의 일부입니다.
발언이 아닌 문을 테스트하라
이 예시에서 제가 중요하게 생각하는 테스트는 인보이스 서비스가 보호된 데이터를 반환하는지 여부입니다. 에이전트가 “그것은 할 수 없습니다”라고 말하는 것은 도움이 되는 사용자 경험(UX)이지만, 그것 자체가 보안 검증(security assertion)은 아닙니다.
저는 유효한 동일 테넌트 읽기, 다른 테넌트의 인보이스, 누락된 식별자, 도구 인수 내 위조된 역할, 취소된 권한, 그리고 정책 시간 초과를 테스트할 것입니다. 또한 다른 지원 경로를 통해 보호된 서비스에 호출을 시도할 것입니다. 동일한 경계가 유지되어야 합니다.
결정을 조사하기에 충분한 로그를 남겨야 합니다: 행위자(actor), 동작(action), 리소스 참조, 결정 결과, 그리고 사용 가능한 경우 정책 버전입니다. 인보이스 내용과 자격 증명은 이 로그에서 제외해야 합니다. 증거는 민감한 데이터의 또 다른 사본을 생성하지 않으면서 접근을 설명하는 데 도움이 되어야 합니다.
이 모든 것이 에이전트를 모든 면에서 안전하게 만들지는 않습니다. 하지만 한 가지 약속은 테스트 가능하게 만듭니다: 이 호출자는 모델이 친절하게 요청한다고 해서 그 인보이스를 검색할 수 없다는 것입니다.
잘 작성된 프롬프트는 에이전트에게 예의범절을 가르칠 수 있습니다. 하지만 문에는 여전히 자물쇠가 필요합니다.
Ahmad Afzal은 mahmadafzal.com에서 AI, 제품 및 사람에 대해 글을 씁니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기