AI 에이전트가 자체 권한 모델을 필요로 하는 이유
요약
AI 에이전트가 복잡한 워크플로우를 수행하며 프로덕션 시스템에 진출함에 따라, 비결정론적 특성으로 인해 보안 위험이 커지고 있습니다. 따라서 최소 권한 원칙을 적용하여 기능 기반의 접근 제어와 단기 인증 정보 발행이 필수적입니다.
핵심 포인트
- AI 에이전트는 인간 사용자나 전통적인 백엔드 서비스와 다른, 비결정론적 행위자임.
- 광범위한 영구 권한(Superuser)은 위험하며, 최소 권한 원칙 적용이 핵심이다.
- 기능 기반의 접근 제어 및 단기 인증 정보 발행 방식을 도입해야 한다.
- 프롬프트 주입이나 도구 출력 오염 등 새로운 위협 클래스에 대비해야 한다.
AI 에이전트는 실제 고객 데이터, 내부 도구(tooling), 그리고 수익에 영향을 미치는 워크플로우에 접근하면서 빠르게 프로덕션 시스템으로 진출하고 있습니다. 지원 자동화 에이전트는 CRM에서 티켓을 읽고, 대화 기록을 요약하며, 부분 환불을 처리하고, 내부 에스컬레이션 티켓을 생성하며, Slack에 상태 업데이트를 게시하는 등 단일 작업 내에서 모든 것을 수행할 수 있습니다. 이러한 에이전트가 작동할 때는 워크플로우를 간소화하고 일상적인 운영 비용을 절감할 수 있습니다. 하지만 실패할 때는 전통적인 자동화와는 다르게 실패합니다: 비결정론적으로, 대규모로, 그리고 종종 전체 프로덕션 자격 증명(credentials)을 가지고서 말입니다.
에이전트가 유용하려면 여러 도구에 걸쳐 동적이고 다단계의 워크플로우를 실행할 수 있을 만큼 충분한 접근 권한이 필요합니다. 하지만 안전하기 위해서는 해당 도구들에 대해 영구적이고 광범위한 권한을 가진 '슈퍼유저(superuser)'로 작동해서는 안 됩니다. 대부분의 팀은 환경 변수에 내장된 장기 실행 API 키나 인간 사용자를 위해 설계된 OAuth 흐름과 같이 이미 알고 있는 접근 패턴에 의존하는 경향이 있으며, 이러한 패턴들이 비결정론적인 소프트웨어를 위해 만들어진 것이 아니라는 것을 빠르게 깨닫게 됩니다. 잘못 구성된 프롬프트(prompt), 악의적인 도구 응답(tool response), 또는 단순한 환각(hallucination)은 심각한 사고로 이어질 수 있습니다.
이 가이드에서는 자율 시스템에 최소 권한 원칙(least-privilege principle)을 적용하는 방법을 알려드립니다. 여기서는 리소스가 아닌 기능(capabilities)에 권한 범위를 지정하는 방법, 특정 실행 계획과 연결된 단기 인증 정보(short-lived credentials)를 발행하는 방법, 신원(identity), 인가(authorization), 그리고 실행(execution)을 별개의 계층으로 분리하는 방법, 그리고 고위험 작업에 대한 인간 개입형 승인(human-in-the-loop approval)을 도입하는 방법을 배울 것입니다.
접근 모델의 불일치
전통적인 접근 제어 모델은 사용자가 다음 두 가지 행위자 중 하나라고 가정합니다:
- 첫 번째는 **인간 사용자(human user)**입니다: 상호 작용적이며, 세션에 의해 제한되고, UI에 구속되며, 일반적으로 단일 워크플로우 내에서 예측 가능합니다.
- 두 번째는 **백엔드 서비스(backend service)**입니다: 결정론적이며, 워크플로우가 고정되어 있고, 정적인 코드 경로를 통해 감사할 수 있습니다. AI 에이전트는 이 두 범주 어느 쪽에도 속하지 않습니다.
에이전트는 설계상 비결정론적(non-deterministic)입니다. 동일한 프롬프트를 동일한 데이터에 대해 두 번 실행해도 다른 도구 호출 시퀀스가 생성될 수 있습니다. 에이전트는 개발자가 명시적으로 작성하지 않은 방식으로 도구 호출을 연결하며, 다음 두 가지 잘 알려진 위협 클래스를 통해 신뢰할 수 없는 입력의 영향을 받을 수 있습니다:
- 프롬프트 주입(Prompt injection): 문서나 웹페이지에 삽입된 지침이 에이전트의 동작을 가로챕니다.
- 도구 출력 오염(Tool output poisoning): 도구 응답이 체인 내 후속 결정을 조작합니다.
점점 더 많은 경우, 에이전트는 서브 에이전트를 생성하기도 합니다. 따라서 단일 사용자 요청으로 시작하는 위임 체인은 완료되기 전에 여러 독립적인 컨텍스트로 분산될 수 있습니다.
이러한 두 가지 유형의 행위자(세션 토큰, 서비스 계정 및 OAuth 스코프를 위해 구축된 ID 원시 요소들은 고립되어 깨진 것은 아닙니다. 엄격한 범위 지정(scoping), 중재(mediation), 런타임 강제 적용과 함께 사용된다면 잘 작동할 수 있습니다. 문제는 이것들이 비결정론적이고 다단계적인 자율 실행을 위해 설계되지 않았으며, 그 전형적인 사용 패턴이 바운디드 세션 또는 결정론적 워크플로우를 가정한다는 점에서 유효하지 않다는 것입니다. 세션 토큰은 제한된 상호작용 흐름을 가정합니다. 서비스 계정은 결정론적 동작을 가정합니다. billing:write와 같은 OAuth 스코프는 다른 쪽의 애플리케이션이 사용자가 의도한 대로 해석한다고 가정합니다. 에이전트가 추가적인 레이어 없이 이러한 요소 중 어느 것을 상속받으면, 원래 모델을 안전하게 만들었던 해당 책임 구조(accountability structures) 없이 권한만 얻게 됩니다.
일반적인 안티 패턴 (Common Anti-Patterns)
더 나은 패턴을 검토하기 전에, 팀들이 일반적으로 선택하는 지름길들을 살펴보는 것이 가치가 있습니다. 각각은 고립된 상태에서는 합리적으로 느껴지지만, 함께 사용되면 위험할 수 있습니다.
장기 지속 자격 증명 임베딩 (Embedding long-lived credentials)
가장 흔한 안티패턴은 장기간 유지되는 API 키를 에이전트 설정이나 환경 변수에 직접 임베딩하는 것입니다. 에이전트는 도구에 광범위한 접근 권한을 부여하는 루트 자격 증명(root credential)을 보유하며, 에이전트 프로세스, 로그 또는 메모리가 손상되면 이 자격 증명이 노출됩니다. 이는 비(非)에이전트 소프트웨어에서도 문제가 되는 부분이지만, 위험도는 에이전트에서 더욱 높아집니다. 왜냐하면 설정 방식에 따라 에이전트는 전통적인 장기 자격 증명을 가진 소프트웨어가 악용되는 것보다 훨씬 쉽게 원치 않는 행동을 하도록 속거나 강요당하거나, 접근 권한이 있는 자격 증명을 노출할 수 있기 때문입니다.
Vault나 AWS Secrets Manager 같은 것을 통해 로테이션(rotation)을 적용하더라도 실제 문제는 피해 범위(blast radius)입니다. 로테이션은 종종 일관성이 없거나 느리며, 손상과 로테이션 사이의 시간 간격만으로도 공격자(또는 오작동하는 에이전트)가 상당한 피해를 입히기에 충분합니다.
전체 사용자 OAuth 범위 상속 (Full user OAuth scope inheritance)
두 번째 안티패턴은 전체 사용자 OAuth 범위 상속입니다. 사용자가 자신의 CRM에 접근할 권한을 에이전트에게 부여하면서, 에이전트는 그 자신이 가진 권한과 동일한 권한을 영구적으로 보유하게 됩니다. 이 권한은 어떤 작업을 수행하든 관계없이 모든 미래의 행동에 걸쳐 적용됩니다. 즉, 에이전트는 사용자가 선택적으로만 행사할 의도였던 권한(authority)을 상속받게 되는 것입니다. 하지만 에이전트는 사용자가 가진 재량권(discretion), 상황 인식(contextual awareness), 또는 책임성(accountability)을 가지고 있지 않으며, 이 과도하게 광범위한 권한을 실수로 오용하는 것은 시간문제일 뿐입니다.
효과적인 슈퍼유저 특권을 가진 에이전트 (Agents with effective superuser privileges)
세 번째이자 가장 위험한 안티 패턴은 에이전트가 개별 사용자 레벨이 아닌 시스템 레벨에서 오케스트레이션하는 도구들에 대해 실질적인 슈퍼유저 권한을 가지고 작동하는 경우입니다. 이는 더 좁은 범위를 정의하기보다 사용 가능한 가장 광범위한 스코프(scope)를 사용할 때 암묵적으로 발생합니다. 앞서 언급된 지원 에이전트의 경우, 유일하게 이용 가능한 청구 관련 스코프이기 때문에 billing:write 권한을 부여받았고, 그 결과 실제 업무가 좁은 정책 내에서 소액의 선의 환불을 처리하는 것임에도 불구하고 모든 크기의 환불을 발행하고, 고객의 결제 수단을 수정하며, 모든 구독을 취소할 수 있게 됩니다.
지원 운영 에이전트에게 billing:write 권한이 부여된 상태에서 사용자가 "주문 번호 12345에 대해 전액 환불을 처리해 주세요"라고 요청한다고 가정해 봅시다. 주문 총액은 10,000 USD입니다. 이 에이전트는 허용되는 환불 금액과 그렇지 않은 금액을 구별할 방법이 없기 때문에 단순히 작업을 진행합니다. 접근 계층(access layer)에서는 아무것도 이를 막지 못합니다. 피해는 밀리초 단위로 발생하며, 유일한 감사 추적 기록은 어떤 합법적인 기록과도 동일하게 보이는 환불 기록에 불과합니다.
능력 범위 기반 권한 (Capability-Scoped Permissions)
에이전트 권한 문제를 해결하기 위한 첫 번째 단계는 권한을 리소스(resource)의 관점에서 생각하는 것을 멈추고, 능력을(capability) 관점에서 생각하는 것입니다. billing:write와 같은 스코프는 리소스 카테고리와 동사(verb)를 설명합니다. 반면, billing.refund.issue_under_50_usd와 같은 능력은 특정 작업 유형, 리소스 제한, 그리고 암묵적인 위험 경계를 인코딩합니다. 물론, 이러한 능력을 스코프에 포함하는 것은 필연적으로 폭발적인 스코프 증가를 초래하기 때문에 일반적이지는 않습니다.
Capability-scoped permissions는 비즈니스가 실제로 추론하는 용어로 권한을 표현합니다. 예를 들어, 제품 관리자가 지원 에이전트가 최대 50 USD까지의 사과성 환불(courtesy refunds)을 처리할 수 있도록 결정했다면, 이 결정은 API를 호출하는 애플리케이션 코드에 여기저기 흩어져 있는 명령형 검사(imperative checks)가 아니라, 권한 시스템이 평가하는 선언적 정책(declarative policy)에 존재해야 합니다. 최대 50 USD 임계값이 권한 엔진 자체에서 적용되든, 토큰 발급 전에 참조되는 전용 정책 계층과 같은 코드 내 검사를 통해 적용되든, 이는 구현 세부 사항일 뿐입니다. 중요한 것은 해당 규칙이 중앙 집중식으로 관리되고 감사 가능(auditable)해야 한다는 점입니다.
바로 여기에 정교한 권한 시스템(fine-grained authorization systems)이 필요한데, 이것들이 퍼즐의 특정 조각을 해결해 주기는 하지만 항상 만능 해결책은 아닙니다.
Auth0의 FGA 팀에서 개발하고 현재 Cloud Native Computing Foundation (CNCF) 프로젝트인 OpenFGA는 관계 기반 접근 제어(Relationship-Based Access Control, ReBAC)를 구현합니다. ReBAC는 '누가 무엇에 대해 행동할 수 있는지'를 모델링하는 데 탁월합니다. 지원 에이전트는 단순히 역할(role)일 뿐만 아니라, 특정 리소스 유형과 구체적이고 제한된 관계(bounded relationships)를 가진 개체(entity)이며, 이러한 관계들이 다양한 권한을 생성합니다. 예를 들어,
OpenFGA에서 위 다이어그램의 두 계층은 단일 검사로 통합됩니다. 관계와 그 조건이 함께 평가되며, 둘 중 어느 것이든 거부할 수 있습니다. '에이전트가 금액이 설정 한도보다 적을 때 주문을 환불할 수 있다'와 같은 기능은 OpenFGA에서 조건을 통해 직접 표현될 수 있으며, 이때 금액은 튜플 자체에 포함되거나 검사 시점에 컨텍스트로 제공됩니다. 순수 권한 정책(authorization policy)을 선언적 정책 코드(declarative policy code)와 분리하고 싶어 하는 팀의 경우 Cedar나 OPA 같은 전용 엔진을 상위에 계층화하는 것이 합법적인 대안이지만, 필수는 아닙니다. OpenFGA는 관계 그래프와 속성 제약 조건(attribute constraints) 둘 다를 처리할 수 있습니다.
구체적으로, 에이전트별 한도가 있는 지원 에이전트의 환불 기능은 OpenFGA의 DSL에서 다음과 같이 보입니다:
model
schema 1.1
...
이 기능은 에이전트의 설정된 한도를 _영구 저장 조건 컨텍스트(persisted condition context)_로 담는 관계 튜플을 작성함으로써 부여됩니다. $50 USD 상한선을 가진 지원 에이전트의 경우:
await fgaClient.write({
writes: [
{
...
검사 시점(check time)에 런타임은 요청별 조건 컨텍스트의 절반(시도하는 실제 환불 금액)을 제공합니다:
const { allowed } = await fgaClient.check({
user: "agent:support_agent_01",
relation: "can_refund",
...
OpenFGA는 조건을 평가할 때 두 측면을 병합하며, 키가 둘 다에 존재하는 경우 영구 저장 튜플 컨텍스트가 요청 컨텍스트보다 우선권을 가집니다. 튜플에 agent_limit: 50이 저장되어 있고 검사 시점에 refund_amount: 75가 제공되면, 조건 refund_amount <= agent_limit은 false로 평가되고 allowed는 false를 반환합니다. 즉, 에이전트는 $75 USD 환불을 자율적으로 처리할 수 없으며, 런타임의 다음 동작은 환불 기능을 갖춘 토큰을 요청하는 것이 아니라 인간 승인 흐름(human approval flow)을 트리거하는 것입니다 (본문 후반부에서 다룹니다).
실질적인 교훈은 실제 비즈니스 경계에 맞는 기능을 정의하는 것입니다. 단순히 billing:write가 아니라, billing.refund.issue_under_50_usd와 같이 구체적이어야 합니다. 또한 crm:read가 아니라, crm.contact.read_for_assigned_tickets처럼 특정 목적을 명시해야 합니다. 만약 정의하려는 기능이 너무 광범위하게 느껴진다면, 잠시 멈추고 현재 구축하는 기능을 저해하지 않으면서도 더 제한적인 범위로 축소할 수 있는지 고려해 보세요.
작업별 자격 증명 (Task-Scoped Credentials)
기능 범위 지정(Capability scoping)은 에이전트가 '무엇을' 할 수 있는지를 다룹니다. 반면, 작업 범위 지정(Task scoping)은 그것을 '언제', 그리고 '얼마나 오랫동안' 할 수 있는지를 다룹니다. 이 둘은 별개의 문제입니다. 이 둘을 혼동하는 것은 흔한 실수입니다.
잘 설계된 에이전트는 가능한 한 영구적인 자격 증명(persistent credentials)을 최소화하거나 제거합니다. 지속적인 접근 권한을 보유하기보다는, 각 작업 실행 계획에 연결된 단기 토큰(short-lived tokens)을 요청합니다. 이 토큰은 해당 계획에 필요한 기능만을 담고 있으며, 빠르게 만료되고(며칠이 아닌 몇 분 단위), 사용 후 폐기됩니다. 실제로는 일부 시스템에서 여전히 단기 토큰을 캐시하거나 인프라 관련 문제로 범위가 지정된 서비스 계정(scoped service accounts)에 의존하기도 합니다. 목표는 순수성 그 자체보다는, 특정 자격 증명이 유용할 수 있는 시간 창(window)을 줄이는 데 있습니다.
이 패턴은 여러 가지 유용한 속성을 가집니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기