LabHub
배우기 러닝패스 코스

에이전트가 내 DB 를 지웠다 · 에이전트가 DB 를 지우기 전에 · 실습

에이전트가 DB 를 지우는 사고를 재현하고 막는다

LabHub 에서 이어서 보기

목표

run_sql 하나로 무엇이든 하는 MCP 서버가 DROP TABLE 을 그대로 실행하는 것을
재현한 뒤, 같은 서버를 세 겹으로 고칩니다 — 읽기 전용 모드, 허용 목록, 파괴적
도구의 확인 단계.

왜 중요한가

제목의 사고는 모델이 나빠서 나는 것이 아닙니다. "테스트 데이터를 정리해 줘"
라는 말을 받은 모델이 가장 짧은 길인 DROP TABLE 을 고른 것뿐이고, 그것을
막을 장치가 서버에 하나도 없었던 것이 원인입니다. 스펙은 도구를 **모델이
고르는 것(model-controlled)** 이라 부르고, 그래서 사람이 거부할 수 있어야
하며 서버는 접근 제어를 구현해야 한다고 적습니다. 그 문장을 코드로 옮기면
세 가지가 됩니다. 도구를 좁게 쪼개고 목록으로 켜고 끈다, 쓰기가 필요 없는
자리는 연결 자체를 읽기 전용으로 연다, 지우는 도구는 확인 없이는 돌지
않는다. 이 실습에서 셋을 전부 손으로 넣습니다.

단계

1. /root/mcp/guard/seed.sql 을 저장하고 /root/mcp/guard/shop.db 로 적재하세요. customers 5행, orders 8행이어야 합니다.
2. /root/mcp/guard/server_v1.py 를 만드세요. 도구는 run_sql(인자 sql) 하나이고, SELECT 는 결과 행을, 그 밖의 SQL 은 실행한 뒤 ok 를 돌려줍니다. DB 경로는 환경변수 MCP_DB(기본 /root/mcp/guard/shop.db)입니다.
3. /root/mcp/guard/attack.sh 로 사고를 재현하세요. v1 서버에 run_sqlDROP TABLE orders; 를 보내고, 그 응답 JSON 과 tables_after=<남은 테이블 목록> 한 줄을 /root/mcp/guard/incident.txt 에 남깁니다. orders 가 정말 사라져야 합니다.
4. DB 를 seed.sql 로 복구하고 /root/mcp/guard/postmortem.mdcause=, missing_control=, fix= 세 줄(각각 한 문장 이상)을 쓰세요.
5. /root/mcp/guard/server_v2.py — 환경변수 MCP_READ_ONLY=1 이면 DB 를 읽기 전용으로 열어서, run_sqlDROP 을 보내도 isError: true 로 거절되고 테이블이 남아야 합니다. SELECT 는 그대로 됩니다.
6. /root/mcp/guard/server_v3.py/root/mcp/guard/allowlist.jsonrun_sql 을 없애고 list_customers·count_orders·delete_order 세 도구를 정의하되, 환경변수 MCP_ALLOWLIST(기본 allowlist.json)에 적힌 이름만 tools/list 에 내고 호출을 받습니다. allowlist.json 에는 읽기 도구 둘만 넣으세요. 목록 밖 도구 호출은 -32602 입니다.
7. v3 의 delete_order(인자 id, confirm)는 confirmtrue 가 아니면 아무것도 지우지 않고 isError: true 로 무엇을 지우려 했는지 알려 주고, true 일 때만 지웁니다.
8. /root/mcp/guard/attack_v3.sh 로 같은 공격을 v3 에 보내 막히는 것을 확인하고, /root/mcp/guard/guard-report.txtrun_sql_removed=yes, readonly_blocks_drop=yes, delete_requires_confirm=yes, orders_rows=<현재 orders 행 수> 네 줄을 쓰세요.

참고

단계 8개

  1. 가게 DB 를 만든다
  2. 만능 도구가 있는 서버
  3. 사고를 재현한다
  4. 복구하고 사고 기록을 쓴다
  5. 읽기 전용 모드
  6. 허용 목록으로 좁힌다
  7. 지우는 도구는 확인이 필요하다
  8. 같은 공격이 막히는 것을 증명한다