내 MCP 도구는 정수 하나만으로 라이브 게시글을 무엇이든 덮어쓸 수 있습니다. 차이점(Diff)도, 로그(Log)도, 경고(Warning)도
요약
MCP(Model Context Protocol) 도구 설계 시 에이전트의 자율적 실행에 따른 위험성을 경고합니다. 신원 확인, 변경 사항 비교(Diff), 실행 로그 부재가 초래할 수 있는 데이터 덮어쓰기 문제를 다룹니다.
핵심 포인트
- 에이전트 도구 설계 시 대상 무결성 검증(Sanity-check) 필수
- 변경 전후를 비교하는 Diff 기능의 중요성
- 자율 실행되는 에이전트를 위한 쓰기 작업 로그 기록 필요
- 단순 API 호출을 넘어선 에이전트 전용 사고 모델 적용 필요
에이전트 세션 내부에서 나의 GitHub 프로필과 dev.to 게시를 실행하기 위해 구축한 MCP 서버인 server.py의 도구 목록을 살펴보던 중, 이 부분에서 멈췄습니다:
@mcp.tool()
def update_article(article_id: int, title: str = None, body_markdown: str = None, published: bool = None) -> dict:
"""ID를 통해 기존 DEV.to 기사를 업데이트합니다."""
...
본문 6줄, PUT 요청 한 번이면 API 키가 접근할 수 있는 어떤 기사의 제목, 본문, 또는 게시 상태든 기꺼이 다시 작성합니다. dev.to가 API 키를 하나의 계정으로 제한하기 때문에, 이는 제가 지금까지 게시한 모든 기사를 의미합니다. 필요한 것은 오직 정수(integer) 하나뿐입니다.
최근 dev.to에서 이와 유사한 이야기가 트렌드로 올라오는 것을 계속 보고 있습니다. 누군가 에이전트에게 도구를 부여하고, 도구의 권한 확인(permission check)은 기술적으로 통과하지만, 결과는 여전히 나쁜 경우입니다. 왜냐하면
- 신원 확인 불가 (No confirmation of identity). 이 도구는
article_id: int를 받지만, 이름이나 제목의 에코(echo), 즉 무결성을 검증(sanity-check)할 수 있는 수단이 전혀 없습니다. 만약 에이전트가 컨텍스트 내에 두 개의 숫자—예를 들어 기사 ID와 GitHub 이슈 번호, 혹은 이번 주 기사 ID 대신 지난주 기사 ID—를 가지고 있다면, 도구의 규약(contract) 내에는 이러한 뒤바뀜을 잡아낼 수 있는 장치가 없습니다. 도구는 그저 PUT 요청을 수행할 뿐입니다. - 차이점(Diff) 없음. 기존의 제목, 본문, 그리고 게시 상태(publish state)를 전혀 가져오거나 비교하지 않습니다. 호출자는 무엇이 변경되었는지... 알 수 없음을 통해 깨닫게 됩니다. 응답은 오직 새로운 상태만을 다시 보여줄 뿐입니다.
- 로그(Log) 없음. 쓰기 작업이 발생했다는 사실을 기록하는 것이 아무것도 없습니다. 만약 라이브 기사의 제목이 바뀌었는데 제가 일주일 후에 이를 알아차린다면, 이 도구나 이 실행, 혹은 이 기사 ID와 연결되는 어떠한 흔적도 찾을 수 없을 것입니다.
이것들은 결코 생소한 실패 모드(failure modes)가 아닙니다. 무차별적인 덮어쓰기(blind overwrite)를 수행하는 모든 엔드포인트에서 요구되는 세 가지 요소—대상 확인, 차이점(diff) 표시, 쓰기 로그 기록—와 동일합니다. 제가 이 도구에 그것들을 넣지 않았던 이유는, 작성 당시의 사고 모델(mental model)이 "나를 위한 작은 보조 도구"였지, "에이전트가 하루에도 수십 번의 예약된 실행(scheduled runs)을 통해 자율적으로 호출하는 도구"가 아니었기 때문입니다.
실제 위험성, 솔직하게 말하자면
이 서버는 하루에 두 번, 관리자 없이(unattended) 실행되며 새로운 기사를 게시하고 가끔 기존 기사를 수정합니다. 만약 이 파이프라인의 향후 버전이 환각(hallucination)된 ID나 오래된 ID로 update_article을 호출하게 된다면—에이전트가 동일한 컨텍스트 창(context window) 내에서 "방금 게시한 기사"와 "트렌딩 목록에서 읽은 기사"를 동시에 다루고 있다면 충분히 상상 가능한 일입니다—그 ID가 실제로 가리키는 것이 무엇이든 조용히 덮어써 버립니다. 이미 게시된 라이브 포스트의 제목이나 본문이 오류도, 확인도, (지금까지는) 기록도 없이 교체되어 버리는 것입니다.
이것은 "다른 누군가가 내 콘텐츠를 편집할 수 있다"는 의미에서의 보안 취약점(Security hole)은 아닙니다. dev.to API는 이미 쓰기 권한을 해당 키의 계정으로 제한(Scope)하고 있으므로, 외부 공격자가 이를 이용해 침입할 수는 없습니다. 이것은 폭발 반경(Blast-radius)의 문제입니다. 이 도구를 호출할 수 있는 유일한 행위자(나의 에이전트)에게 "정확한 호출"과 "우연히 틀렸지만 정확해 보이는 호출" 사이의 가드레일(Guardrail)이 없으며, 함수 내부에서는 이 두 가지가 동일하게 보입니다.
해결책 (the fix)
저는 사람의 개입(Human-in-the-loop)을 통한 확인 단계를 추가하고 싶지 않았습니다. 이 서버의 핵심 목적은 무인(Unattended)으로 실행되는 것이기 때문입니다. 따라서 해결책은 "쓰기 전에 묻기"가 아니라, "쓰기 작업이 흔적을 남기게 하고, 호출자가 두 번째 왕복(Round trip) 없이도 차이점(Diff)을 볼 수 있게 만들기"입니다:
_ARTICLE_UPDATE_LOG = "logs/article_updates.jsonl"
def _log_article_update(article_id, before, fields_changed, after):
...
비용이 적게 드는 두 가지 변경 사항입니다:
- 쓰기 전 가져오기 (Fetch before write). PUT 요청을 하기 전에 동일한 기사 ID로 GET 요청을 한 번 더 수행합니다. 이제 "이전(before)" 상태가 에이전트가 세 번 전의 도구 호출에서 기억하고 있는 정보가 아니라, 쓰기 시점의 메모리에 존재하게 됩니다.
- 차이점(Diff) 반환 및 쓰기 로그 기록 (Return the diff, log the write). 이제 도구의 반환 값은 호출자에게 무엇이 변경되었는지 정확히 알려줍니다. 만약
title.before가 에이전트가 예상했던 것과 다르다면, 그것은 잘못된 ID를 가리키고 있다는 신호가 됩니다. 그리고 모든 쓰기 작업은 누군가 그 순간 응답을 지켜보고 있는지 여부와 관계없이logs/article_updates.jsonl에 한 줄의 기록을 남깁니다.
저는 가짜 이전/이후 상태(Fake before/after states)를 대상으로 차이점/로그 기록 함수를 직접 단위 테스트(Unit-test)했습니다(실제 API 호출은 하지 않았습니다. 제가 직접 게시한 기사 중 하나를 실제로 망가뜨리면서 테스트할 수는 없으니까요). 이를 통해 JSONL 항목과 차이점(Diff) 모두 올바른 이전/이후 쌍을 포함하고 있음을 확인했습니다. GET 후 PUT을 수행하는 시퀀싱(Sequencing)은 업데이트당 한 번의 추가 요청 비용이 발생하지만, 이는 조용히 잘못된 ID로 덮어쓰기가 발생했을 때의 비용에 비하면 무시할 수 있는 수준입니다.
일반화하고 싶은 부분 (the part I'd generalize)
이 교훈은 "모든 것에 로깅을 추가하라"는 것이 아닙니다. 더 좁은 범위의 교훈입니다. 입력값이 단순한 ID(ID)이고 출력값이 전체 덮어쓰기(full overwrite)인 모든 도구는 다시 한번 살펴볼 가치가 있는 지점입니다. 특히 함수 내부에서 올바르게 보이는 호출과 잘못된 호출을 구분할 수 있는지 여부를 확인해야 합니다. create_article은 이런 문제가 없습니다. 이 함수는 오직 추가만 할 수 있으며, 이미 존재하는 것을 조용히 교체할 수는 없기 때문입니다. 따라서 잘못된 호출이 발생하더라도 단순히 추가적인 초안(draft)을 만들 뿐이며, 이는 발견하고 삭제하기가 매우 쉽습니다. 이 서버에서 그릇된 호출과 그럴듯해 보이는 호출이 동일한 코드 경로(code path)를 생성하는 도구는 update_article 하나뿐이었습니다. 우리가 찾아내야 할 패턴은 "이 도구가 위험해 보이는가"가 아니라, 바로 이러한 패턴을 grep으로 검색하는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기