Gauntlet: 내 에이전트의 도구들이 나를 공격한다면?

Elasticsearch Agent Builder 해커톤

gauntlet-blog_(1).png

해커톤 마감을 단 이틀 앞두고, 저는 저는 한 걸음 물러서서 처음부터 접근 방식을 완전히 다시 고민하기로 결심했습니다.

원래의 아이디어는 '리허설'이었습니다. 에이전트가 실제 환경에서 작업을 실행하기 전, 다른 에이전트가 구축한 모의 샌드박스 환경에서 미리 동작을 리허설해 보는 방식의 에이전트였습니다. 개념 자체는 탄탄했지만, 지나고 보니 결함이 명백했습니다. 리허설과 실제 실행 사이의 환경이 변할 수 있다는 것이 문제였습니다. 에이전트가 이메일 발송을 리허설할 때는 아무 문제가 없었더라도, 정작 실제 실행에 들어가는 순간 받음 편지함 상태는 달라져 있을 수 있습니다. 시뮬레이션이 현실과 어긋나는 순간, 시스템 전체가 무너져 버립니다.

하지만 이런 문제에서 자유로운 영역이 하나 있었는데, 바로 적대적 퍼즈 테스트입니다. 에이전트가 시뮬레이션에서 실패하면 실제 상황에서도 실패할 수 있습니다.Gauntlet은 그렇게 탄생했습니다. 마감 48시간 전에 동일한 핵심 인사이트(검색을 사용하여 기억을 구축하고 창의성을 유지하는 에이전트)를 재사용하되, 이를 확률성이 걸림돌이 되지 않는 문제로 다시 조준한 것입니다.

해피 패스에서 에이전트를 테스트할 때의 문제점

많은 사람들이 화제가 되었던 개인용 AI 어시스턴트 OpenClaw에 대해 들어보셨을 겁니다. 광범위한 도구 접근 권한을 가진 에이전트형 AI 어시스턴트에 관한 논의를 지켜봐 오셨다면, 그와 관련된 보안상의 우려 또한 접해보셨을 것입니다. 에이전트는 해서는 안 될 일이 무엇인지 망각하거나, 아예 처음부터 그 사실을 알지 못하기도 합니다. 이유는 간단합니다. 해피 패스(Happy path)만을 테스트하기 때문입니다. 에이전트에게 기대되는 정상적인 동작이 이루어지는지만을 점검합니다. 하지만 누군가 에이전트에게 해서는 안 될 행동을 시도했을 때 어떤 일이 벌어지는지는 좀처럼 점검하지 않습니다.

적대적 테스트를 위한 샌드박스가 존재하긴 하지만,구축하기가 어렵습니다. 공격 벡터를 수동으로 설계하고, 적대적 데이터를 일일이 입력하며, 시나리오마다 테스트 인프라를 구해야 합니다. 과정은 느리고 확장성도 없으며, 결국 이미 예상했던 버그만 찾아낼 뿐입니다.

저는 환경 자체가 자동으로 적대적이며 시간이 지남에 따라 더욱 창의적으로 변하는 시스템을 원했습니다.

아이디어: 또 다른 에이전트를 활용해 샌드박스 모의

샌드박스를 만드는 대신, Gauntlet은 주 에이전트의 도구 호출을 가로채고 창의적인 방법으로 이를 무너뜨릴 창의적인 방법을 찾아내는 모의 에이전트를 사용합니다. 에이전트가 search_emails를 호출하면, 모의 에이전트는 그 결과를 지켜보다가 변조 여부를 결정합니다. 이메일 본문에 프롬프트 인젝션을 심거나, 미묘하게 틀린 데이터를 반환하고, 혹은 거짓 정보를 주입해 주 에이전트가 이를 포착하는지 시험합니다. 주 에이전트는 자신이 시뮬레이션 속에 있다는 절대 모릅니다.

인터페이스는 두 개의 데코레이터로 구성됩니다.

@function_tool
@gauntlet.query
def search_emails(folder: str = "inbox") -> str:
    """Search emails in the given folder."""
    return json.dumps(fetch_emails(folder))

읽기 동작에는 @gauntlet.query를, 쓰기 동작에는 @gauntlet.mutation 데코레이터를 사용합니다. 실행이 완료된 후에는 evaluate()가 진행 상황을 검토하여 실제 확인된 버그를 저장합니다.

사용법은 간단하지만 그 이면에는 두 가지 어려운 문제가 숨어 있습니다.

이를 결국 검색 문제로 만드는 두 가지 문제

첫째, 모의 에이전트는 대화 전반에 걸쳐 일관된 세계 모델을 유지해야 합니다. 만약 주 에이전트에게 어떤 이메일이 '앨리스'로부터 온 것이라고 말했다면, 나중에 이와 모순되어서는 안 됩니다. 명백히 가짜 같은 변조된 이메일로는 아무것도 배울 수 없습니다. 결국 개연성이 핵심입니다.

둘째, 모의 에이전트는 새로운 버그를 찾아야 합니다. 동일한 프롬프트 인젝션 패턴을 50번 재발견하는 것은 아무런 도움이 되지 않습니다. 에이전트는 이미 발견한 내용을 기억하고, 도구의 실제 기능에 기반을 두면서도 새로운 방향으로 탐색을 확장해야 합니다.

이 두 가지 모두 검색 문제입니다. 바로 이 지점에서 Elasticsearch가 시스템의 핵심 역할을 합니다.

두 개의 메모리 회로

모의 에이전트는 두 개의 메모리 회로를 기반으로 작동하며, 이 회로들은 모두 Elasticsearch 내에 존재합니다.

단기 기억은 현재 세션 내의 모든 것을 추적합니다. 즉, 가로챈 모든 도구 호출, 원래 결과, 그것이 어떤 형태로 변조되었는지, 그리고 이에 대응해 주 에이전트가 어떤 반응을 보였는지를 모두 기록합니다. 이것이 바로 일관성 계층입니다. 이를 통해 모의 에이전트는 자신의 최근 결정을 쿼리하며 내부적으로 공격적인 태도를 유지하면서도 논리적 일관성을 지킬 수 있습니다. 창의성과 일관성 사이의 균형을 맞추는 것이 이 프로젝트 전체에서 가장 어려운 설계 과제였습니다.

장기 기억은 창의성이 결합되는 곳입니다. 이곳에는 유사성 검색을 위한 임베딩 처리가 된 확정된 버그들, 에이전트가 실패 모드를 추론할 수 있도록 하는 전체 도구 구현, 과거 실행 결과가 저장됩니다. 모의 에이전트가 새로운 공격 아이디어가 필요할 때, 장기 기억을 검색하여 이전에 시도했던 방식을 확인하고, 그 사이의 공백을 찾아내어 새로운 가설을 세웁니다.

이 모든 과정은 하나의 폐쇄형 사이클로 연결됩니다. 즉, 어떤 버그가 존재할지 가설을 세우고, 이를 증명하기 위한 상황을 조성하며, 확인된 버그를 다시 인덱스에 저장하는 과정이 반복됩니다. 인벤토리는 확장되고, 공격은 더욱 창의적으로 진화합니다. 시간이 흐를수록 Gauntlet과 수동 샌드박스 설정 사이의 격차는 더욱 벌어집니다.

Elastic Agent Builder 내부에서 모든 것을 실행

모의 에이전트 전체가 Elastic Agent Builder 내에서 구축됩니다. 지침, 도구 바인딩, Amazon Bedrock Converse API를 통한 다회차 대화 상태 관리까지 모두 포함되며, 별도의 외부 오케스트레이션 도구는 필요하지 않습니다.

제가 가장 자부심을 느끼는 도구는 generate-hypothesis입니다. 이 도구는 단 하나의 ES|QL 문으로 기존 버그를 샘플링하고, MV_CONCAT으로 이를 집계한 뒤, 쿼리문 안에서 COMPLETION을 인라인으로 호출하여 새로운 공격 가설을 제안합니다. 샘플링, 집계, LLM 추론, 결과 생성에 이르는 모든 과정이 ES|QL 파이프라인을 전혀 벗어나지 않고 하나의 쿼리로 처리됩니다. 처음에는 Elasticsearch와 외부 스크립트 사이에서 데이터를 주고받아야 할 것으로 예상했지만, 그럴 필요가 없었습니다.

ES|QL의 COMPLETION 함수는 가장 놀라운 발견이었습니다. COMPLETION, STATS, MV_CONCAT, SAMPLE사이에서 단일 쿼리만으로 전체 추론 파이프라인을 구축할 수 있었습니다. 버그 저장소는 Kibana 워크플로우를 사용하며, 프로그래밍 방식으로 생성된 Kibana 대시보드를 통해 버그 수, 심각도 분류, 공격 패턴 히트맵을 실시간으로 파악할 수 있습니다.

Converse API는 제가 걱정했던 또 다른 문제를 해결했습니다. 모의 에이전트는 단일 실행에서 주 에이전트에게 이전에 어떤 말을 했는지 기억해야 합니다. 저는 매번 호출할 때마다 인덱스에서 대화 이력을 가져와 에이전트에 다시 로드해야 할 것이라고 생각했습니다. 그러나 Converse API가 다회차 대화 상태를 자체적으로 처리해 줍니다. 덕분에 대화 관리 로직을 전혀 작성할 필요가 없었습니다. converse를 계속 호출하기만 하면 대화의 일관성이 유지됩니다.

이를 통해 실제로 얻을 수 있는 이점

수동으로 적대적 샌드박스를 설정하려면 시나리오당 대략 한 시간 정도가 소요됩니다. Gauntlet을 사용하면 동일한 프로세스가 2~10분밖에 걸리지 않으며, 장기 기억 기능을 갖추고 있어 각 실행 단계가 이전의 모든 실행 결과로부터 학습된 상태로 진행됩니다. 사용하면 할수록 에이전트의 약점을 더 많이 파악하게 되며, 새로운 약점을 찾아내기 위해 더욱 열심히 파고듭니다.

이제 그다음은?

현재 Gauntlet은 1대1 방식으로 작동합니다. 즉, 하나의 모의 에이전트가 하나의 주 에이전트를 상대하는 구조입니다. 하지만 이 문제는 민망할 정도로 병렬화하기 쉽습니다. 별도의 아키텍처 변경 없이도 20개의 공격 세션을 동시에 실행할 수 있습니다. 이제 확장은 당연한 다음 단계입니다.

해결되지 않은 과제가 있으며 이는 더 흥미로운데, 바로 장기 기억에서의 탐색 대 탐취 문제입니다. 모의 에이전트는 성공적인 공격 사례로 알려진 기존 공격의 변형을 시도하는 것(탐취)과 완전히 새로운 가설을 검증하는 것(탐색) 사이에서 균형을 맞추어야 합니다. 이는 다른 분야에서는 이미 충분히 연구된 문제이지만, 이를 적대적 에이전트 테스트에 적용하는 시도는 미답의 영역으로 남아있는 듯합니다. 이 프로젝트를 완전히 벗어나, 아예 별개로 추진해 볼 만한 가치가 있는 무언가가 있을지도 모릅니다.

저는 또한 '리허설' 아이디어에 대해서도 계속 고민하고 있습니다. Gauntlet은 특별한 사례입니다. 시뮬레이션에서의 실패가 실제 상황에서의 실패 가능성을 의미하기 때문에 퍼즈 테스팅이 작동하는 것입니다. 하지만 리허설과 실제 실행 사이의 환경이 충분히 안정적인 다른 분야들도 존재하며, 그런 곳에서는 원래의 리허설 개념이 작동할 수 있습니다. 그게 어떤 분야인지는 아직 찾지 못했지만, 계속 찾고 있습니다.

핵심 사항

실제 도구에 액세스할 수 있는 에이전트를 구축고 있다면, 그러한 도구가 나를 공격해 올 때 어떤 일이 일어나는지 테스트해 보세요. 단순히 일회성 수동 테스트에 그치지 말고, 시도했던 내용을 기억하며 시간이 갈수록 더 창의적으로 변하는 시스템을 통해 지속적으로 테스트해야 합니다. 그것이 바로 Gauntlet이 하는 일입니다.

Kavish Sathia

학생, National University of Singapore

카비시 사티아(Kavish Sathia)는 싱가포르 국립대학교(NUS)에서 컴퓨터 과학을 전공하며 에이전트 시스템을 연구하고 있는 학생입니다.

GitHub · 시연 · 웹사이트 · LinkedIn

이 게시물에서 설명된 모든 기능이나 성능의 출시와 일정은 Elastic의 단독 재량에 따라 결정됩니다. 현재 제공되지 않는 기능이나 성능은 예정된 시간에 출시되지 않을 수도 있으며 아예 제공되지 않을 수도 있습니다.

이 블로그 게시물에서는 타사 생성형 AI 도구를 사용하거나 언급했을 수 있으며 이러한 도구는 각각의 소유자가 소유하고 운영합니다. Elastic은 이러한 타사 도구에 대해 어떠한 통제권도 없으며 해당 도구의 콘텐츠, 운영, 사용뿐 아니라 사용으로 인해 발생할 수 있는 손실이나 손해에 대해 어떠한 책임도 지지 않습니다. 개인 정보, 민감한 정보 또는 기밀 정보를 AI 도구와 함께 사용할 때는 주의하시기 바랍니다. 제출된 모든 데이터는 AI 학습이나 기타 목적으로 사용될 수 있습니다. 제공한 정보가 안전하게 보호되거나 비밀로 유지된다는 보장은 없습니다. 생성형 AI 도구를 사용하기 전에 해당 도구의 개인정보 보호 관행과 이용 약관을 숙지하시기 바랍니다. 

Elastic, Elasticsearch 및 관련 마크는 미국 및 기타 국가에서 Elasticsearch B.V.의 상표, 로고 또는 등록 상표입니다. 그 외의 모든 회사 및 제품 이름은 해당 소유자의 상표, 로고 또는 등록 상표입니다.