본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 23. 16:29

아무도 소유하지 않은 에이전트

요약

Sentry 인제스트 키를 악용한 코딩 에이전트 탈취 공격 사례를 통해 에이전트 운영의 보안 공백을 지적합니다. 에이전트를 단순한 모델이 아닌 프로덕션 서비스로 취급하고, SRE 관행을 적용한 운영 체계 구축이 필요함을 강조합니다.

핵심 포인트

  • 가짜 에러 리포트를 통한 에이전트 권한 탈취 공격 가능성 확인
  • 프롬프트 기반 방어책의 한계와 보안 책임 소재의 불분명함
  • 에이전트를 확률적 서비스(Probabilistic Service)로 정의하고 운영할 것
  • 잘못된 동작 예산(Wrong-action budget) 및 온콜 체계 도입 필요
  • 런타임 보안을 위해 결정론적 게이트와 SRE 관행 적용 권장

2026년 중반, 한 보안 회사에서 버그 리포트를 제출하는 것만으로 개발자의 코딩 에이전트를 장악할 수 있다는 것을 보여주었습니다. 일반적인 의미의 익스플로잇은 없습니다. 설계상 공개적이며 웹의 절반 전면에 자리 잡고 있는 Sentry 인제스트 키(Sentry ingest key)를 찾아내어, 누군가의 프로젝트에 가짜 에러를 POST합니다. 이는 다른 모든 크래시처럼 보입니다. 나중에 개발자가 자신의 에이전트에게 해결되지 않은 Sentry 이슈들을 고치라고 지시합니다. 에이전트는 Sentry 연결을 통해 사용자가 작성한 가짜 에러를 읽고, Sentry의 자체 조언과 정확히 똑같이 보이도록 마크다운으로 꾸민 '해결 단계(resolution steps)'를 찾아내어 개발자의 자격 증명(credentials) 내에서 npx 명령어를 실행합니다.

연구원인 Tenet Security는 이것을 100개 이상의 실제 조직에 대해 테스트했으며, 약 85%의 확률로 성공했습니다. 프롬프트 수준의 방어책은 아무런 효과가 없었습니다. 에이전트들은 시스템 프롬프트가 신뢰할 수 없는 데이터를 무시하라고 지시했을 때조차 페이로드(payload)를 실행했습니다. Sentry 측의 답변은 적절한 수정 방법은 '방어하기 어렵다'는 것이었기 때문에, 특정 데모 문자열만 차단하고 문제의 형태 자체는 그대로 남겨두었습니다.

당신은 이것을 Sentry의 이야기로 읽을 수 있습니다. 하지만 저는 그게 핵심을 놓치고 있다고 생각합니다. 흥미로운 질문은 나중에 나왔는데, 그것은 누가 이 문제를 막을 책임이 있었는지에 대한 것이었습니다. AppSec(애플리케이션 보안)은 애플리케이션을 소유하고, IT는 노트북을 소유합니다. 공격자의 텍스트를 읽고 사용자의 키로 코드를 실행할 수 있는 그 중간의 무언가를 아무도 소유하지 않았던 것입니다. 우리는 신뢰할 수 있는 에이전트를 구축하는 방법을 배우는 데 몇 년을 보냈습니다. 하지만 누가 그것들을 운영할지는 결정하지 못했습니다.

그 간극(gap)이 이 게시물의 주제입니다. 이것은 에이전트에 대한 반론도 아니며, 특정 도구에 대한 보안 권고문도 아닙니다. 이름표가 붙지 않은 상태로 확률적 시스템이 프로덕션 환경에 배포될 때 사라지는 규율(discipline)에 관한 것입니다.

요약하자면 이렇습니다: 에이전트를 프롬프트를 입력하는 모델이 아니라, 하나의 프로덕션 서비스 (production service)로 취급하십시오. 에이전트에는 지정된 소유자(owner)가 한 명 있어야 하며, 가동 시간 SLO (uptime SLO) 대신 잘못된 동작 예산 (wrong-action budget)이 설정되어 있어야 합니다. 또한 "인간 참여 (human in the loop)"가 아닌 정확도 신호 (correctness signals)에 따라 호출되는 온콜 (on-call) 체계가 필요하며, 실패를 재현할 수 없는 상황에서도 유효한 사후 분석 (postmortem) 프로세스가 갖춰져야 합니다. 이 중 대부분은 이미 실행 중인 SRE 관행을 확률적 서비스 (probabilistic service)에 적용하는 것뿐입니다. 이 글의 나머지 부분은 각 요소가 어떻게 작동하는지 설명하며, 도움이 되는 곳에 흐름도를 그려 넣었습니다.

빌드 타임 (Build-time)은 해결되었습니다. 런타임 (Run-time)은 그렇지 않습니다.

빌드 타임의 플레이북 (playbook)은 이제 정립되었습니다. 모든 도구에 타입이 지정된 계약 (typed contract)을 부여하여 모델이 쓰레기 값으로 도구를 호출할 수 없게 만드십시오. 모델 외부에 결정론적 게이트 (deterministic gate)를 두어, LLM은 제안하고 여러분의 코드가 처리 (disposes)하게 하십시오. CI에서 평가 (evals)를 실행하여 성능이 저하되는 프롬프트 변경이 배포되기 전에 포착되도록 하십시오. 되돌릴 수 없는 작업 앞에는 인간을 배치하십시오. 이것은 훌륭한 엔지니어링이며 모두 실행해야 합니다. 또한 이것은 기본값(baseline)이며, 정확히 한 가지 질문에만 답합니다: 에이전트가 올바르게 구축되었는가.

그것은 쉬운 절반입니다.

그것은 나머지 질문에는 답하지 않습니다. 모델이 밑단에서 업데이트되었거나 검색 인덱스 (retrieval index)가 드리프트 (drift)되어, 잘 구축된 에이전트가 일요일 새벽 3시에 미묘하게 더 나쁜 답변을 내놓기 시작할 때, 누가 이를 발견하며 어떻게 발견할 것인가의 문제입니다. 제 견해로는 이것이 더 비용이 많이 드는 질문이며, 우리가 가장 적은 시간을 할애해 온 질문입니다. 프로덕션 환경의 에이전트에 관한 현재의 글들을 읽어보면 관심사가 어디에 있는지 알 수 있습니다. 바로 설계 타임 (design-time)의 절반인 아키텍처 (architecture)와 거버넌스 (governance)에 집중되어 있습니다. 배포 후 시스템을 운영하는 런타임 (run-time)의 절반은 보통 다음 내용으로 넘어가기 전 "모니터링하십시오"라는 한 문장으로 끝납니다.

런타임의 절반이 어려운 이유는 도구가 미성숙해서가 아닙니다. 구조적으로 그에 대한 책임을 지는 사람이 아무도 없기 때문입니다.

이음새: 네 명의 소유자, 하나의 동작, 책임의 부재

일반적인 서비스와 달리 에이전트의 소유권(ownership)이 왜 그렇게 어려운지에 대한 이유가 여기 있습니다. 네 개의 서로 다른 당사자가 에이전트 동작의 조각들을 소유하고 있습니다.

  • **모델 벤더 (model vendor)**는 가중치 (weights)를 소유하며, 사용자의 일정이 아닌 자신들의 일정에 따라 이를 변경합니다.
  • 귀하의 **프롬프트 엔지니어 (prompt engineers)**는 지침 (instructions)과 시스템 프롬프트 (system prompt)를 소유합니다.
  • 각 **연결된 도구 (connected tool)**를 유지 관리하는 주체가 해당 도구의 API와, 결정적으로 그 출력값 (output)을 소유합니다.
  • **플랫폼 팀 (platform team)**은 이 모든 것을 하나로 묶어 실행하는 하네스 (harness)를 소유합니다.

에이전트의 동작은 이 네 가지 요소 모두의 산물입니다. 따라서 에이전트가 오작동할 때, 실패의 책임은 어느 한 팀도 책임을 지지 않는 이들 사이의 틈새 (seam)에 떨어집니다. 프롬프트 작성자는 지침에 문제가 없었다고 말합니다. 플랫폼 팀은 하네스가 지시받은 대로 작동했다고 말합니다. 도구 소유자는 API가 유효한 응답을 반환했다고 말합니다. 벤더는 아무 말도 하지 않는데, 모델 업데이트에 대해서는 티켓 (ticket)을 발행할 수 없기 때문입니다. 모두가 옳지만, 에이전트는 여전히 잘못된 동작을 합니다.

정확히 네 개가 맞는 숫자인지는 확실하지 않으며, 검색 (retrieval)과 메모리 (memory)를 추가하면 귀하의 조직에서는 다섯 개나 여섯 개가 될 수도 있습니다. 하지만 그 형태는 유지됩니다. 조직도에서 인정하는 것보다 더 많은 소유자가 존재하며, 그 누구의 소유도 아닌 동작이 나타나는 구조 말입니다.

업계가 이 문제로부터 움찔하며 피하는 모습을 볼 수 있습니다. 많은 "에이전트 상태 (agent health)" 툴링은 에이전트가 올바른 일을 하는지 확인하기보다는 적절한 파일이 존재하는지를 확인합니다. 동작이 아닌 존재 여부입니다. 널리 공유되는 한 상태 확인 도구 (health checker)는 디스크에 death_detector.py가 있는지로 에이전트 점수를 매길 뿐, 실제로 죽음을 감지한 적이 있는지는 점수에 반영하지 않습니다. 이것이 바로 축소된 형태의 틈새입니다. 우리는 아티팩트 (artifact)가 빌드되었는지는 확인하는 법을 배웠지만, 누군가가 그 동작을 소유하고 있는지는 조용히 확인을 건너뛰는 법을 배웠습니다.

첫 번째 해결책은 지루하고 거의 비용이 들지 않습니다. 바로 누가 무엇을 소유하는지 적어두는 것입니다. 위원회나 추상적인 "플랫폼 팀"이 아니라, 에이전트당 책임 있는 실명 소유자를 지정하고 나머지에 대한 명시적인 지도를 만드는 것입니다. 단순한 RACI (책임 권한 매트릭스)만으로도 충분합니다:

표의 목적은 표 그 자체가 아닙니다. 모든 행의 Accountable (책임) 열에 나타나는 단 하나의 이름입니다. 에이전트재킹 (agentjacking) 공격은 새로운 종류의 익스플로잇 (exploit)을 필요로 하지 않았습니다. 그것은 에이전트가 무엇을 만질 수 있는지 한 번쯤 앉아서 목록을 작성하고, "임의의 에러 텍스트를 읽고 쉘 명령어를 실행함"이 목록에 있다는 사실을 인지한 소유자를 필요로 했을 뿐입니다.

SRE에서 빌려오되, 어디서 한계가 오는지 솔직해지십시오

신뢰할 수 없는 무언가를 운영하면서 이를 정직하게 유지해야 했던 것은 우리가 처음이 아닙니다. SRE (Site Reliability Engineering)는 20년 동안 이를 해왔습니다. SRE의 도구 중 세 가지는 잘 이식될 수 있으며, 비결정론적 서비스 (non-deterministic service)에 대해 각 도구가 어디에서 무너지는지 솔직하게 밝힐 가치가 있습니다.

업타임 SLO가 아닌, 잘못된 동작 예산 (wrong-action budget)

정확한 출력이 고정된 답이 아닌 분포 (distribution)인 서비스에는 99.9%라는 숫자를 부여할 수 없습니다. "가동 중 (up)"인 에이전트라도 꾸준히, 그리고 조용히 틀릴 수 있습니다. 그러므로 개념을 그대로 복사하지 말고 번역하여 적용하십시오.

모든 오답의 비용이 동일하지는 않으므로, 에이전트의 행동을 영향 범위 (blast radius)에 따라 분류하는 것부터 시작하십시오:

이제 실제로 예산을 책정할 수 있는 무언가가 생겼습니다. 클래스당 목표율을 정하고, 라벨링된 세트 (labelled set)를 기준으로 실제 비율을 측정하며, 그 차이를 소진해 나가는 예산으로 취급하십시오. 되돌릴 수 없는 동작 클래스가 예산을 다 소진하면, SRE 팀이 장애로 인해 에러 예산 (error budget)을 다 썼을 때 배포를 중단하는 것과 마찬가지로, 해당 에이전트에 대한 변경 사항 배포를 중단하고 원인을 찾아야 합니다. 당신이 선택한 숫자가 무엇인지는 중요하지 않습니다. 사고가 발생했을 때 발견하는 것이 아니라, 사전에 의도적으로 선택했다는 사실이 더 중요합니다.

솔직한 주의사항을 말씀드리자면, 실제 오작동률 (wrong-action rate)을 측정하려면 에이전트 출력값에 대한 라벨링된 스트림 (labelled stream)이 필요하며, 라벨링은 노동력을 필요로 하는 작업입니다. 만약 런타임 인프라 (run-time infrastructure) 중 단 하나에만 투자해야 한다면, 반드시 이것에 투자하십시오. 왜냐하면 다른 모든 제어 장치는 현재 상황이 얼마나 잘못되었는지 말할 수 있는 능력에 의존하기 때문입니다.

'인간 참여형 (human in the loop)'이 아닌 온콜 (On-call)

이것은 리스트 형식의 글들이 '인간 참여형 (human in the loop)'이라는 말로 축소해버리는 바로 그 도구입니다. 높은 이해관계가 걸린 행동을 승인하는 인간과, 호출(page)을 받는 인간은 다릅니다. 온콜 (On-call)이란 특정 신호에 의해 특정 인물이 깨어나는 것을 의미하며, 에이전트에게 있어 그 신호는 CPU나 지연 시간 (latency)이 아닙니다. 그것은 정확성 신호 (correctness signals)입니다. 우선적으로 구축할 가치가 있는 세 가지는 다음과 같습니다:

  • 골든 트랜잭션 (Golden transactions): 정기적으로 재실행하는, 검증된 출력값이 포함된 소규모의 수동 큐레이션 입력 세트입니다. 골든 입력 (golden input)에 대한 에이전트의 답변이 변한다면, 무언가 변했다는 뜻입니다. 이는 세 가지 중 구축 비용이 가장 저렴하며, 스모크 테스트 (smoke test)에 가장 가까운 방식입니다.
  • 섀도 디핑 (Shadow diffing): 실제 트래픽을 대상으로 기존 설정과 후보 설정 (새로운 프롬프트, 새로운 모델)을 병렬로 실행하되, 섀도 (shadow) 환경에서는 아무런 조치도 취하지 않고 출력값의 차이 (diff)를 비교합니다. 불일치 (disagreement)가 급증한다면, 무언가를 프로모션 (promote)하기 전의 조기 경보가 됩니다.
  • 액션 분포 드리프트 (Action-distribution drift): 에이전트가 선택하는 도구와 행동의 히스토그램 (histogram)을 추적합니다. 에이전트는 성공 여부가 변하기 전에, 자신이 무엇을 하는지를 조용히 바꿈으로써 실패합니다. 만약 "이메일 전송"이 하룻밤 사이에 전체 행동의 2%에서 15%로 급증한다면, 개별 행동이 모두 유효해 보이더라도 누군가에게 온콜 (page)을 보내십시오.

이 세 가지 신호는 서로 연결되어 하나의 페이저 (pager)로 전달됩니다.

이 중 그 어떤 것도 스스로 페이저(pager)를 울리지는 않습니다. 여러분은 이러한 신호들을 다른 모든 운영 항목과 동일한 알림 경로(alerting path)에 연결해야 합니다. 그리고 기대치를 냉정하게 유지하십시오. 현재의 자동화된 SRE 에이전트들은 일곱 가지 시나리오 중 약 하나만을 스스로 해결합니다. 당분간, 소유자는 사람입니다.

네 가지 축을 통한 변경 관리 (Change management across four axes)

일반적인 배포는 하나의 바이너리(binary)를 변경합니다. 하지만 에이전트 배포는 프롬프트(prompt), 모델(model), 도구(tool), 또는 검색 인덱스(retrieval index)를 변경할 수 있으며, 이들은 각각 독립적으로 버전이 관리됩니다. 이를 네 가지 버전 축(version axes)으로 취급하고 모든 릴리스마다 기록하십시오:

agent: support-triage prompt: v14 (changed) model: opus-4.8 (pinned) tools: sentry@2.1, github@3.0 retrieval: kb-2026-06-18 (reindexed)

이 규율은 간단하지만 거의 아무도 실천하지 않습니다. 회귀(regression)의 원인을 파악할 수 있는 능력을 잃지 않으려면, 한 번에 하나의 축 이상을 절대 변경하지 마십시오. 만약 프롬프트와 검색 인덱스가 동시에 변경되었는데 품질이 떨어졌다면, 어떤 것이 원인인지 알 수 없으며 다시 추측의 단계로 돌아가게 됩니다.

여러분이 제어할 수 없는 축은 모델이며, 바로 이 지점에서 블루-그린 배포(blue-green deployment)가 조용히 작동을 멈춥니다. 블루-그린 방식은 이전 버전으로 되돌릴(fallback) 준비가 되어 있다고 가정합니다. 하지만 벤더(vendor)의 지원이 중단된(deprecated) 모델을 영원히 준비해 둘 수는 없습니다. 모델이 서비스 종료(sunset)되는 날에 대한 깔끔한 정답은 저에게도 없으며, 정답이 있다고 말하는 사람은 경계해야 합니다. 정직한 대응은 교체가 강제되기 전에 미리 연습하는 것입니다. 모델의 지원 중단이 소방 훈련(fire drill)이 아닌 계획된 승격(promotion)이 될 수 있도록, 항상 여러분의 골든 트랜잭션(golden transactions)에 대해 검증된 두 번째 모델을 유지하십시오.

재현할 수 없는 사후 분석 (The postmortem you can’t reproduce)

가장 어려운 이식은 SRE가 가장 중요하게 생각하는 것, 즉 비난 없는 사후 분석(blameless postmortem)입니다. 에이전트는 이를 망가뜨리는데, 왜냐하면 사고를 재현할 수 없는 경우가 많기 때문입니다. 온도가 0이 아니었거나, 모델이 조용히 업데이트되었거나, 잘못된 출력을 생성한 컨텍스트(context)가 스크롤되어 사라져 버렸을 수도 있습니다. 여러분은 다시는 발생시킬 수 없는 실패에 대해 보고서를 작성하라는 요구를 받게 됩니다.

해결 방법은 컨텍스트 (context)를 일회용으로 취급하는 것을 중단하는 것입니다. 실패가 발생한 시점의 전체 입력 (input), 검색된 문서 (retrieved documents), 도구 출력 (tool outputs), 그리고 모델 버전 (model version)을 코어 덤프 (core dump)를 보관하는 것과 마찬가지로 일급 아티팩트 (first-class artifact)로서 캡처하십시오. 다른 것은 아무것도 기록하지 않더라도, "모델이 정확히 무엇을 보았고, 무엇을 했는가"라는 질문에 답할 수 있을 만큼은 기록하십시오. 실행 가능한 템플릿은 다음과 같습니다:

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0