에이전트가 내 DB 를 지웠다 · 에이전트가 DB 를 지우기 전에 · 실습
에이전트가 DB 를 지우는 사고를 재현하고 막는다
목표
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_sql 로 DROP TABLE orders; 를 보내고, 그 응답 JSON 과 tables_after=<남은 테이블 목록> 한 줄을 /root/mcp/guard/incident.txt 에 남깁니다. orders 가 정말 사라져야 합니다.
4. DB 를 seed.sql 로 복구하고 /root/mcp/guard/postmortem.md 에 cause=, missing_control=, fix= 세 줄(각각 한 문장 이상)을 쓰세요.
5. /root/mcp/guard/server_v2.py — 환경변수 MCP_READ_ONLY=1 이면 DB 를 읽기 전용으로 열어서, run_sql 로 DROP 을 보내도 isError: true 로 거절되고 테이블이 남아야 합니다. SELECT 는 그대로 됩니다.
6. /root/mcp/guard/server_v3.py 와 /root/mcp/guard/allowlist.json — run_sql 을 없애고 list_customers·count_orders·delete_order 세 도구를 정의하되, 환경변수 MCP_ALLOWLIST(기본 allowlist.json)에 적힌 이름만 tools/list 에 내고 호출을 받습니다. allowlist.json 에는 읽기 도구 둘만 넣으세요. 목록 밖 도구 호출은 -32602 입니다.
7. v3 의 delete_order(인자 id, confirm)는 confirm 이 true 가 아니면 아무것도 지우지 않고 isError: true 로 무엇을 지우려 했는지 알려 주고, true 일 때만 지웁니다.
8. /root/mcp/guard/attack_v3.sh 로 같은 공격을 v3 에 보내 막히는 것을 확인하고, /root/mcp/guard/guard-report.txt 에 run_sql_removed=yes, readonly_blocks_drop=yes, delete_requires_confirm=yes, orders_rows=<현재 orders 행 수> 네 줄을 쓰세요.
참고
- 읽기 전용 연결:
sqlite3.connect(f"file:{경로}?mode=ro", uri=True). 쓰기를 시도하면 sqlite 가attempt to write a readonly database로 거부합니다 — SQL 문자열을 검사해 막는 방식은 따라가야 할 변형이 끝없이 늘어나니 엔진에서 막으세요. - 채점기는 파괴적 도구를 시험할 때 학생의 DB 를 임시 사본으로 복사해
MCP_DB로 넘깁니다. 서버가 그 환경변수를 읽지 않으면 채점이 실제 DB 를 지우거나 실패합니다. - 허용 목록은 "있는 도구 중 켤 것" 이 아니라 "목록에 없으면 없는 도구" 입니다.
tools/list에도 나오지 않고tools/call도 모르는 도구로 답해야 합니다. - 흔한 실수 1: confirm 을 문자열
"true"로 받아들이는 것. 스키마가 boolean 이면is True로 비교하세요. - 흔한 실수 2: 목록이 없을 때 모든 도구를 여는 것. 없으면 아무것도 열지 않는 쪽이 닫힌 기본값입니다.
단계 8개
- 가게 DB 를 만든다
- 만능 도구가 있는 서버
- 사고를 재현한다
- 복구하고 사고 기록을 쓴다
- 읽기 전용 모드
- 허용 목록으로 좁힌다
- 지우는 도구는 확인이 필요하다
- 같은 공격이 막히는 것을 증명한다