에이전트가 환자 집단에 대한 질문을 받는다. 도구를 하나 호출하니 그 도구가 임상 데이터베이스를 조회한다. IAM은 그 호출을 허용했고 OAuth 스코프도 통과했고 게이트웨이는 호출 기록을 남겼다. 경로 위의 authorization 결정은 모두 옳았다. 그런데 그중 어느 것도 이 사용자가 이 호출로 어느 행과 어느 컬럼을 보게 되는지는 정하지 않았다.

이 공백을 메우려고 AWS가 내놓은 것이 TOLAP(Tool-Object Level Access Protocol)이다. Apache-2.0으로 github.com/awslabs/tolap에 공개됐고 버전 관리되는 policy schema, .NET, Python, TypeScript용 SDK, 참조 policy 서버, 그리고 주요 에이전트 프레임워크와 맞춘 14개 integration이 들어 있다. 여기서 볼 것은 그 아래 깔린 아키텍처 주장이다. 논쟁할 값어치가 있는 대목이 바로 그 주장이다.

지금 쓰고 있는 계층들은 왜 이 공백을 못 메우나

이미 믿고 쓰는 접근 제어 모델을 그대로 재사용하고 싶어진다. AWS가 각각 왜 안 되는지 직접 설명했고 이번 발표에서 제일 쓸모 있는 부분이 그것이다. 결정 비교로 다시 적어 둔다.

계층무엇에 답하는가왜 이 공백을 못 메우는가
역할 기반 접근 제어(RBAC)이 사용자가 이 리소스에 닿을 수 있는가리소스 단위로만 판단하고 그 안에서 어느 컬럼이 보이고 어느 행을 걸러야 하는지는 표현할 방법이 없다
속성 기반 접근 제어(ABAC)정책이 이 요청을 허용하는가중앙 엔진에서 판단한다. 데이터베이스에 직접 붙은 도구는 그 엔진을 거치지 않는 쿼리를 조립한다
데이터베이스 행 수준 보안이 테이블의 어느 행인가진짜 소스 강제지만 데이터베이스 안에 갇혀 있다. 에이전트는 REST API, vector store, object storage도 읽는다
콘텐츠 가드레일모델이 무엇을 말했는가출력을 본다. 제한되지 않은 데이터가 이미 context window에 들어간 뒤다

RBAC과 ABAC은 판단 근거가 다르다. RBAC은 권한을 역할에 묶고 신원이 결정을 나른다. 이 분석가에게 analyst 역할이 있고 그 역할은 patients 테이블을 읽을 수 있다는 식이다. ABAC은 요청의 속성을 본다. 레코드가 속한 리전의 사용자인지, 관리 대상 기기인지 같은 조건을 걸 수 있지만, 대신 모든 요청이 지나가야 하는 중앙 판단 지점이 생긴다. 둘 다 리소스에서 멈춘다. 그 리소스 안에서 무엇이 돌아와야 하는지는 어느 쪽도 말하지 않는다.

마지막 행은 커버리지로 오해하기 쉽고 이 글에서 가장 날카로운 논점이기도 하다. 가드레일은 모델이 말하는 것을 제한한다. 가드레일이 동작할 무렵 데이터는 이미 context window 안에 들어가 있고 그 안에 들어간 데이터는 요약, 추론, 후속 질문, prompt injection을 통한 추출에 그대로 열려 있다. 응답 하나에서 필드를 가려도 모델이 들고 있는 대화에서 그 필드가 사라지지는 않는다. 데이터 객체 정책을 가드레일에 맡기면 강제 지점이 유출 뒤에 있다.

세 가지 원칙

TOLAP의 설계는 세 가지 주장으로 요약된다.

소스 지점에서 강제한다. 정책은 데이터가 생기는 곳에 적용되고 그 위 계층에는 적용되지 않는다. 도구가 데이터 소스를 감싸고 데이터가 경계를 넘기 전에 강제한다. wrapper 자체가 경로이므로 강제를 건너뛰는 데이터 경로가 없다.

객체 단위로 본다. 정책은 개별 데이터 객체를 지목한다. 컬럼, 행, 필드, 태그, 엔드포인트, HTTP 메서드, 유사도 임계값, 스토리지 접두사, 결과 수 제한이다. 정책 하나로 “이 사용자는 patients 테이블을 조회할 수 있고 SSN 컬럼은 볼 수 없고 배정된 리전의 행만 보며 email 필드는 해시로 받는다"를 표현할 수 있다.

에이전트는 보안을 몰라도 된다. 호출하는 에이전트에 보안 인지 코드가 필요 없다. 제한된 데이터는 애초에 받는 것에 나타나지 않는다. AWS는 이 점을 prompt injection 아래에서도 방어가 버티는 이유로 든다. 모델의 행동이 아니라 아키텍처에 기대기 때문에, context 안에 추출할 것이 남지 않는다.

세 번째 원칙이 그 통제가 정말 통제인지 가르는 시험이다. 강제가 모델의 지시 준수에 기대는 순간 그것은 행동 통제가 되고 행동 통제는 공격자가 에이전트에게 말을 거는 바로 그때 약해진다.

정책 하나가 하는 일

헬스케어 분석가용 정책 하나를 보자. patients, encounters, diagnoses 테이블은 열어 주고 내부 billing, audit 테이블은 숨긴다. SSN과 생년월일은 아예 가리고 email은 해시로, 이름은 첫 글자만 남긴다. 행은 지정된 두 리전으로 제한하고 결과 집합에는 상한을 건다. 에이전트가 받는 것은 SSN 컬럼이 아예 없는 테이블, J*********로 읽히는 이름, email 자리에 들어간 해시, 허용된 두 리전의 행뿐이다. billing 테이블 조회는 데이터베이스에 닿기도 전에 거부된다.

정책 여러 개가 한 사용자에게 걸리면 가장 제한적인 것이 이긴다(most-restrictive-wins). 허용 집합은 교집합, 거부 집합은 합집합, 불리언은 AND, 숫자 한도는 더 엄격한 값, 같은 필드를 다르게 마스킹하면 더 제한적인 마스킹이 이긴다. 결과는 한 번 짚어 둘 만하다. 권한을 준다는 것에 대한 통상적인 직관과 반대이기 때문이다. 누군가에게 정책을 하나 더 붙이면 그 사람이 볼 수 있는 것이 줄어들 뿐, 늘어나지는 않는다.

구체적인 예가 있다. 정책 A가 [name, email, phone]을 허용하고 정책 B가 [name, email, address]를 허용하면, 병합된 정책은 [name, email]만 허용한다. A가 SSN을 가리고 B가 생년월일을 가리면 둘 다 가려진다. 정책이 늘어날수록 볼 수 있는 데이터는 항상 줄어든다.

resolve, sign, enforce

패턴은 세 단계다. 가운데 단계 때문에 이것이 단순한 필터 함수 이상이 된다.

  1. 사용자와 데이터 소스에 대해 정책을 해석(resolve) 한다. 사용자가 가진 할당은 여기서 하나의 유효 정책으로 병합된다.
  2. 서명(sign) 한다. 위조와 변조가 드러나고 수명이 정해진 봉투가 나온다.
  3. 모든 도구 호출에 강제(enforce) 한다.

서명은 봉투 전체의 정규형을 덮고 어느 데이터 소스에 발급했는지와 만료 시각까지 포함한다. 그래서 context는 편집할 수 없고 서명 키 없이는 수명을 늘릴 수 없고 다른 데이터 소스에 다시 쓸 수도 없다. 언어 간 검증도 된다. 한 SDK가 서명한 context를 나머지 두 SDK가 검증한다. 이 성질은 명세를 세 번 따로 읽은 결과가 아니라 공유 test fixture로 고정된다. 보안 경계에 어울리는 선택이다.

훑고 지나치기 쉬운 설계 세부가 하나 있다. SQL 소스에 대해 .NET SDK는 행 필터를 WHERE 절로, 결과 제한을 LIMIT으로 밀어 넣어 데이터베이스가 돌려주는 양을 줄일 수 있다. AWS는 이것이 자원 최적화이지 강제 경계가 아니라고 못박는다. 필드 마스킹, 해시 변환, 복잡한 필터는 이식 가능한 SQL로 안정적으로 표현할 수 없어서 사후 단계는 그대로 돈다. 구현 교훈 하나를 꼽자면 이것이다. 최적화와 강제는 계산을 공유해도 되지만 같은 코드 줄이면 안 된다.

purpose-binding과 delegation chain

객체 단위 강제는 에이전트가 어떤 데이터를 볼 수 있는지 답한다. 왜 지금 이 데이터에 접근하는지, 누구 권한으로 접근하는지는 답하지 않는다. 그 공백을 메우는 기능이 이번 릴리스에서 흥미로운 대목이다.

purpose-binding은 보안 컨텍스트에 선언된 목적을 더한다. 할당에 purposeProfile 태그가 붙고 해석 단계는 선언된 목적이 맞을 때만 목적 태그가 달린 정책을 포함한다. 여기서 곧바로 나오는 반론이 “선언한 목적을 어떻게 믿나"인데, AWS의 답은 쓸모 있게 틀을 바꾼다. 목적은 신뢰 주장이 아니라 제약 선택자다. 관리자가 목적 프로필 집합을 정의하고 각 프로필은 특정하고 잠긴 정책에 매핑된다. 에이전트는 그 메뉴에서 고른다. 실제로는 billing 데이터가 필요하면서 “campaign-overlap"을 고르면 campaign-overlap의 제약을 받는다. 거기에는 billing 테이블이 없다. 목적은 무엇을 할 수 있는지를 정하고 무엇을 한다고 말하는지를 정하지 않는다. 거짓말이 스스로를 무너뜨리는 구조다.

delegation chain은 사람에서 에이전트로 이어지는 권한 경로를 담는다. 순서가 있는 hop 목록이고 각 hop은 principal 식별자, principal 유형(user/agent/service), 선택적 선언 목적, 선택적 scope 축소 목록을 기록한다. 각 hop은 scope를 좁힐 수만 있고 넓힐 수 없다. glob 매칭으로 강제된다. IAM 기반 시스템에 대한 내 불만 하나가 여기서 정면으로 다뤄진다. 에이전트가 도구를 호출하면 서비스는 IAM principal 하나, 대개 service role 하나만 본다. 그것을 만들어낸 위임 경로는 보이지 않는다. 사람이 오케스트레이터에 위임하고 오케스트레이터가 에이전트에, 에이전트가 sub-agent에 위임해도 접근 결정은 마지막 identity 하나로만 판단된다.

action validation은 목적의 allowedActions, prohibitedActions 목록에 대한 결정적 검사를 더한다. 객체 단위 단계와는 독립적이고 그 뒤에 돈다. 세 기능은 함께 동작한다. 해석 시점의 목적 필터링이 어느 정책을 적용할지 정하고 action validation이 호출이 범위 안인지 확인하고 위임 검증이 경로가 정당하게 좁혀졌는지 확인하고 객체 단위 강제가 컬럼과 행을 가린다.

선택적인 LLM judge는 허용 범위 안의 의미적 이탈을 다룬다. 개별로는 규칙을 지키지만 합쳐지면 재식별 패턴을 이루는 쿼리다. 이걸 방어 가능하게 만드는 설계 결정이 두 가지다. 1차 계층이 아니라 2차 계층이라서 이미 결정적 검사를 통과한 쿼리만 본다. 에이전트가 말로 컬럼 제한을 넘어갈 수 없다. 그리고 judge prompt는 관리자가 통제한다. 에이전트는 그것을 보지 못하고 영향을 줄 수도 없다. 기본값은 꺼짐이다.

프로젝트가 스스로 밝힌 한계

알려진 한계 섹션이 놀랄 만큼 솔직하다. TOLAP이 자기에게 맞는지는 이 제약들이 가른다.

  • wrapper를 쓴 곳에서만 강제된다. 데이터베이스로 직접 접근하면 완전히 우회한다. 자기가 앉지 않은 경로는 지킬 수 없다. 데이터 소스마다 전부 아니면 전무이고 감싸지 않은 연결 하나가 구멍이다.
  • 서명된 context는 replay를 줄일 뿐 막지는 않는다. 만료가 서명 안에 있어서 연장은 안 되지만 선택적인 replay 가드가 없으면 유효한 context는 만료까지 다시 쓸 수 있다. 수명을 짧게 유지하고 이걸 기본값이 아니라 요구사항으로 다뤄야 한다.
  • 해시 마스킹은 salt를 설정해야 레인보우 테이블 공격을 막는다. salt는 해시를 계산하기 전에 입력에 섞어 넣는 비밀 문자열이다. salt 없이 alice@example.com 같은 값을 해시하면 우리 설치와 남의 설치에서 같은 해시가 나온다. 그러면 마스킹된 결과를 손에 넣은 공격자가 미리 계산해 둔 해시 테이블에서 원값을 찾거나, 다른 곳에서 계산한 해시와 맞춰볼 수 있다. salt를 설정하면 설치마다 같은 입력에 다른 해시가 나오므로 두 경로가 모두 막힌다. 한 설치 안에서는 여전히 같은 입력이 같은 해시를 낸다. 마스킹이 결정적으로 동작해야 조회와 검증이 되기 때문이다. 키 기반 해시(HMAC)가 더 강하지만 해시가 맞는지 검증하려면 서명 키가 필요하다.
  • LLM judge는 본질적으로 비결정적이다. 같은 쿼리가 평가마다 다른 정합성 점수를 받을 수 있다. AWS도 이게 자문용 계층에는 맞고 컴플라이언스 게이트에는 맞지 않다고 직접 말한다. 올바른 정리이고 보안 리뷰에서도 그대로 유지돼야 한다.
  • 데이터 경로에 구성 요소가 하나 더 늘어난다. 정책 서버, 스토어, 도구마다의 강제다. 세 언어 모두 코어 SDK의 외부 의존성이 0인 이유도 여기 있다. 그 코드가 의존성 트리를 보안 경로로 끌고 들어가지 않고 Lambda, 엣지 워커, 플러그인에 그대로 넣을 수 있어야 하기 때문이다.

이미 가진 것 위에 얹기

게이트웨이에서 이미 강제하고 있다면 TOLAP이 그것을 대체하지 않는다. 계층을 나누는 게 핵심이다.

관심사지금 강제하는 곳TOLAP이 얹는 계층
에이전트가 도구를 호출할 수 있는가에이전트 프레임워크, IAM, OAuth 스코프그대로다. 통과했다고 가정한다
이 엔드포인트를 이 메서드로 호출할 수 있는가게이트웨이 정책action validation
돌아오는 컬럼과 행은 무엇인가대개 강제 없음소스에서의 객체 단위 강제
누구 권한으로, 무슨 목적으로 접근하는가보통 보이지 않는다delegation chain과 purpose-binding
모델 출력이 안전한가콘텐츠 가드레일범위 밖이다. 의도적으로 사후 단계다

그래서 무엇을 할까

글 말미의 세 질문은 자기 아키텍처에 그대로 가져가 볼 만하다. SDK를 도입하지 않더라도 이 글을 읽을 목록에 남겨 두는 이유다.

  1. 데이터 객체 정책은 지금 어디서 강제되는가. 답이 게이트웨이나 prompt라면, 애플리케이션이 예상하지 못한 쿼리를 무엇이 막는가?
  2. 도구가 걸러지지 않은 데이터를 받는다면, prompt injection 아래에서 강제가 버티는가?
  3. 자율 에이전트가 사용자를 대신해 데이터에 접근할 때, 왜 그리고 누구 권한으로 접근하는지 기록하는가? 그리고 에이전트가 탐지 없이 그 목적에서 벗어날 수 있는가?

zero-trust agent systems를 설계할 때의 경계 설정 본능과 같고 방향은 데이터 쪽으로 한 계층 더 가깝다. identity와 authorization은 호출이 허용되는지 정한다. 돌아온 데이터가 애초에 허용된 것이었는지는 별개의 질문이고 최근까지 이걸 명시적으로 맡은 사람은 없었다.

참고 자료