웹의 방어 체계는 단순한 구분을 기준으로 구축되었다: 한쪽에는 사람, 다른 쪽에는 자동화. AI 에이전트는 어느 쪽에도 속하지 않는다. 사람의 요청에 따라 동작하지만 서버에서 실행되고, 기계 속도로 읽으며, 종종 클라우드 IP에서 접속한다. 웹을 사용하는 AI 에이전트를 구축하는 팀에게 그 결과는 누구도 예상하지 못한 실패다. 에이전트가 벽에 부딪히고, 그다음 일어나는 일은 대부분의 도구 계층이 무시하는 세부 사항에 달려 있다.
이 가이드는 에이전트가 왜 기존 범주 사이에 끼이는지, 도구 호출 내부에서 그 벽이 어떻게 보이는지, 그리고 이를 정직하게 처리하는 에이전트를 어떻게 구축하는지 다룬다.
크롤러도 아니고 사람도 아니다
벤더들은 이제 이런 에이전트를 별도의 범주로 설명한다. OpenAI는 ChatGPT-User가 “자동화된 방식으로 웹을 크롤링하는 데 사용되지 않는다”고 말한다. Google은 Google-Agent가 “사용자 요청에 따라 웹을 탐색하고 작업을 수행하는 데” 사용된다고 말한다. Anthropic은 사람들이 Claude에게 질문할 때를 위한 Claude-User 에이전트를 두고 있다.
규칙은 아직 이를 따라잡지 못했고, 벤더들도 서로 의견이 다르다:
| 벤더 | 사용자 트리거 요청과 robots.txt |
|---|---|
| OpenAI | ”robots.txt 규칙이 적용되지 않을 수 있음” |
| ”일반적으로 robots.txt 규칙을 무시함” | |
| Anthropic | 해당 봇은 robots.txt 지시사항을 준수함 |
RFC 9309는 robots.txt 규칙이 “접근 권한 부여의 한 형태가 아니다”라고 덧붙이는데, 애초에 이것만으로는 문제를 해결할 수 없었다. 한편 트래픽은 늘어나고 있다: Cloudflare는 AI “사용자 작업” 크롤링이 2025년에 15배 넘게 증가했다고 보고하며, Thales의 2026 Bad Bot Report는 2025년 전체 트래픽의 53% 이상을 봇으로 집계한다. 사이트들은 그 어느 때보다 자동화를 걸러낼 이유가 많고, 에이전트는 그 필터에 걸린다.
도구 호출에서 벽이 어떻게 보이는가
| 돌아오는 응답 | 의미 | 에이전트가 해야 할 일 |
|---|---|---|
| 챌린지 페이지가 포함된 200 | 봇 검사이지, 페이지가 아님 | 페이지를 볼 수 없었다고 보고 |
| 401 또는 로그인 페이지 | 콘텐츠에 계정이 필요함 | 사용자에게 로그인을 요청 |
| 402 | 사이트가 결제를 요구함 | 결제 전에 확인을 요청 |
| 403 | 서버가 거부함 | 보고하고, 페이지에 계속 요청하지 않음 |
| 429 | 요청이 너무 많음 | Retry-After를 준수하거나 중단 |
| 404 또는 410 | 페이지가 존재하지 않음 | 재시도하지 않음 |
첫 번째 행이 위험한 경우다. 403은 적어도 실패처럼 보인다. 챌린지 페이지는 종종 형식이 잘 갖춰진 HTML 본문을 가진 200으로 도착하므로, 단순한 fetch 도구는 이를 마치 기사인 것처럼 모델에 넘긴다.
이는 모델이 잘못된 도구 출력에 대해 보이는 행동 때문에 중요하다. 2026년 9월 도구 보강 에이전트에 관한 사전 공개 논문은 도구가 status:error를 반환했을 때 부정직함이 전혀 없었지만(0.0%), 편집되거나 손상되거나 오래되었거나 형식이 잘못되었거나 비어 있거나 잘린 값과 함께 status:ok를 반환했을 때는 45.3%에 달했다는 것을 발견했다. 이 연구는 웹 페이지가 아닌 내부 시스템을 테스트한 것이므로, 이를 차단 페이지에 적용하는 것은 우리의 추론이다. 그럼에도 교훈은 그대로 적용된다: 정직한 오류가 쓸모없는 것을 담은 성공보다 훨씬 더 안전하다. 우리는 챌린지 페이지가 AI 도구의 결과물을 어떻게 오염시키는지도 다룬 바 있다.
진실을 말하는 fetch 도구
해결책은 도구 계층에 있다. 아래 함수는 모델이 응답을 보기 전에 각 응답을 분류하고, 페이지가 진짜일 때만 페이지 텍스트를 반환한다. 필요한 것은 requests뿐이다. 각 사례를 반환하는 로컬 테스트 서버를 대상으로 Python 3.9에서 실행했다.
from __future__ import annotations
from dataclasses import asdict, dataclass
import requests
# Text that marks a page as a wall, not content. Extend it from your own logs.
WALL_MARKERS = ("verify you are human", "unusual traffic", "enable javascript and cookies",
"attention required", "access denied", "captcha")
LOGIN_MARKERS = ("sign in to continue", "log in to continue")
@dataclass
class FetchResult:
outcome: str # ok, wall, login, payment, rate_limited, not_found, error
status: int | None
text: str # only ever page content, empty unless outcome is ok
retry_after: str | None
note: str # what the model should be told
NOTES = {
"wall": "The site showed a bot check. You did not see this page. Say so, do not guess its content.",
"login": "This page needs a login. Ask the user to sign in or to provide the content.",
"payment": "The site asks for payment (HTTP 402). Ask the user before paying anything.",
"rate_limited": "The site asked you to slow down. Wait, or tell the user it is unavailable now.",
"not_found": "The page does not exist. Do not retry it.",
"error": "The fetch failed. You did not see this page. Say so, do not guess its content.",
"ok": "",
}
def classify(status, body):
low = body.lower()
if status == 402:
return "payment"
if status == 429:
return "rate_limited"
if status in (404, 410):
return "not_found"
if any(m in low for m in WALL_MARKERS): # walls often arrive with a 200
return "wall"
if status in (401, 403):
return "login" if status == 401 or any(m in low for m in LOGIN_MARKERS) else "wall"
if status == 200 and any(m in low for m in LOGIN_MARKERS):
return "login"
return "ok" if status == 200 else "error"
def agent_fetch(url, agent_name="example-agent/1.0 (+https://example.com/agent)"):
"""A fetch tool that never hands the model a block page as if it were content."""
try:
r = requests.get(url, headers={"User-Agent": agent_name}, timeout=(10, 30))
except requests.RequestException as exc:
return FetchResult("error", None, "", None, f"{NOTES['error']} ({type(exc).__name__})")
outcome = classify(r.status_code, r.text)
return FetchResult(outcome, r.status_code, r.text if outcome == "ok" else "",
r.headers.get("Retry-After"), NOTES[outcome])
출력은 상태, 결과, 모델이 페이지 텍스트를 받는지 여부, Retry-After로 구성된다:
/article 200 ok content
/challenge 200 wall -
/blocked 403 wall -
/members 200 login -
/priced 402 payment -
/busy 429 rate_limited - 120
/gone 404 not_found -
/broken 503 error -
(no server)None error -
아홉 개 사례 중 단 하나만이 모델에게 페이지 텍스트를 제공한다. /challenge 행은 단순한 fetch가 잘못 처리하는 행인데, 이는 200을 반환했기 때문이다. 마커 목록은 출발점으로 간주하고 자신의 로그에서 늘려 나가야 한다. 어떤 목록도 모든 벽을 포착하지는 못하기 때문이다. MCP 서버를 통해 도구를 제공한다면, 사양에는 이를 위한 자리가 있다: 실행 오류는 isError: true와 함께 보고되며, 클라이언트는 “도구 실행 오류를 언어 모델에 제공해야 한다(SHOULD)”.
CAPTCHA를 풀지 마라
이 점에서는 벤더들이 유난히 일치한다. OpenAI는 Operator가 로그인, 결제 정보, CAPTCHA에 대해 사용자에게 처리를 넘기도록 훈련되었다고 설명했다. Anthropic의 Claude in Chrome 문서는 로그인 페이지나 CAPTCHA에서 “일시 중지하고 수동으로 처리할 것을 요청한다”고 말한다. Google의 권장 컴퓨터 사용 지침은 “어떤 종류의 CAPTCHA든” 포함된 목록에 대해 “절대 풀거나 우회를 시도해서는 안 된다(MUST NEVER)“고 말한다.
CAPTCHA는 사이트가 사람을 요구하는 것이므로, 올바른 대응은 사람에게 넘기는 것이다. 이는 또한 더 나은 제품을 만든다: 사람은 무슨 일이 일어났는지 알고 결정을 내릴 수 있다.
신원 확인이 통과의 길이다
더 장기적인 답은 에이전트가 사이트가 검증할 수 있는 방식으로 자신이 누구인지 밝히는 것이다. Web Bot Auth는 클라이언트가 HTTP Message Signatures로 자신의 요청에 암호화 서명을 할 수 있게 한다. OpenAI는 이미 클라우드 브라우저의 요청에 https://chatgpt.com으로 설정된 Signature-Agent 헤더로 서명하고 있으며, Google은 이 프로토콜을 실험 중이라고 밝혔다. Cloudflare는 2025년 8월에 서명된 에이전트 범주를 도입했고, 2026년 7월부터는 검증된 봇과의 차이를 직접 접근과 중개 접근으로 구분해 추적한다. IETF 작업은 아직 초안 단계이므로 변경을 예상해야 한다.
오늘 에이전트를 구축하고 있다면, 연락처 정보가 담긴 설명적인 User-Agent를 보내고, 에이전트가 무엇을 하는지 공개하며, 서명을 계획에 넣어라.
법적 상황은 아직 정리되지 않았다
첫 번째 주요 사건은 이것이 얼마나 미결 상태인지 보여준다. Amazon은 2025년 11월에 Perplexity의 에이전트형 브라우저에 대해 미국 컴퓨터 사기 및 남용법(Computer Fraud and Abuse Act)과 캘리포니아의 유사 법률 위반을 주장하며 소송을 제기했고, 판결문은 분쟁의 핵심이 Perplexity가 User-Agent 문자열을 사용하지 않기로 한 결정이었다고 밝힌다. 2026년 8월 제9순회항소법원은 예비 금지명령을 취소하며, Amazon이 Perplexity가 해당 법률 아래 자사 컴퓨터에 “접근”했음을 입증할 가능성이 낮다고 판단했다.
법원은 이것이 어디까지 적용되는지에 대해 신중했다: “우리는 에이전트형 AI를 규율하는 새로운 법적 체계를 수립하지 않는다.” 또한 이 결과가 Amazon이 서비스 약관을 통해 접근을 규제할 능력을 저해하지 않는다고도 밝혔다. 따라서 이는 에이전트가 사이트의 의사를 무시해도 된다는 면허가 아니었다. 이는 일반 정보이며 법률 자문이 아니고, 사건은 지방법원으로 환송되었다.
벽에 대비한 구축
- 벽을 정상으로 간주하라. 에이전트의 계획을 도달할 수 없는 페이지를 염두에 두고 설계하고, 다른 출처를 찾거나 사용자에게 묻는 등의 대체 방안을 마련하라.
- 모델이 보기 전에 분류하라. 위와 같은 도구를 사용하고, 어떤 사이트가 실패하는지 볼 수 있도록 결과를 기록하라.
- 공식 경로를 먼저 제공하라. 사이트에 API나 데이터 제품이 있다면 그것을 사용하라. 브라우저 세션보다 더 신뢰할 수 있다.
- 세션을 일관되게 유지하라. 다단계 작업은 중간에 IP가 바뀌면 깨진다. 웹을 탐색하는 AI 에이전트를 위한 프록시 가이드는 로테이션과 고정 세션을 다룬다.
- 프록시가 무엇을 위한 것인지 알아두라. 레지덴셜 프록시는 에이전트가 올바른 국가에서 일관된 주소로, 공개 페이지를 현지 방문자처럼 볼 수 있도록 돕는다. 사이트가 허용하는 범위를 바꾸지는 않으며, 거부 의사를 밝힌 사이트를 우회하는 수단도 아니다. 사이트가 로그인, 결제, 또는 사람을 요구한다면 그것을 존중하라.
결론
에이전트는 웹에 범주가 생기기도 전에 웹에 도달했고, 지금으로서는 벽에 부딪히는 것이 업무의 일부다. 이를 정상적인 결과로 다루어라: 도구 계층에서 분류하고, 모델과 사용자에게 진실을 알리고, CAPTCHA는 사람에게 돌려주고, 에이전트의 신원을 밝혀라. “그 페이지를 볼 수 없었다”고 말하는 에이전트가 추측하는 에이전트보다 훨씬 가치 있다.
출처 및 참고 자료
- OpenAI, 봇 문서: developers.openai.com
- OpenAI, ChatGPT Work의 클라우드 브라우저 허용 목록: help.openai.com
- OpenAI, Operator 소개 (2025년 1월 23일): openai.com
- Google, 사용자 트리거 fetcher: developers.google.com
- Google, Gemini computer use: ai.google.dev
- Anthropic, 크롤링과 사이트 소유자에 관하여: support.claude.com
- Anthropic, Claude in Chrome: code.claude.com
- Cloudflare, 서명된 에이전트 (2025년 8월 28일, 문서는 2026년 11월 업데이트): blog.cloudflare.com, developers.cloudflare.com
- Cloudflare Radar 2025 연간 리뷰: blog.cloudflare.com
- Thales, Bad Bot Report 2026: cpl.thalesgroup.com
- IETF, Web Bot Auth 프로토콜 초안: datatracker.ietf.org
- RFC 9309, Robots Exclusion Protocol: rfc-editor.org
- RFC 9110 및 RFC 6585, HTTP 상태 코드: rfc-editor.org, rfc-editor.org
- Model Context Protocol, 도구 사양: modelcontextprotocol.io
- Sethi et al., Fabrication After Tool Failure (사전 공개, 2026년 9월): arxiv.org/abs/2609.14758
- 제9순회항소법원, Amazon.com Services v. Perplexity AI, No. 26-1444 (2026년 8월 4일): uscourts.gov