본문 바로가기

AI Development

AI 에이전트 트래픽 7,851% 폭증 — 바뀌어야 하는 서버 설계, 방어 전략

반응형

2025년, 조용히 인터넷이 바뀌었어요.

HUMAN Security가 1경(10^15) 개 이상의 디지털 상호작용을 분석한 결과:

2025년 트래픽 증가율:
AI 에이전트 브라우저 트래픽: +7,851%
AI 봇 전체:                  +187%
사람:                         +3.1%

자동화 트래픽 성장속도 = 사람의 8배

Cloudflare CEO 매튜 프린스가 2026년 3월 SXSW에서 한 말:

"2027년이면 봇 트래픽이 사람 트래픽을 추월한다.
COVID 때처럼 반짝 스파이크가 아니다.
멈출 기미가 없다."

2024년 이미 자동화 트래픽이 전체 웹 트래픽의 51%를 넘겼어요. 인터넷 역사상 처음이에요.

문제는 대부분의 서버와 API가 여전히 사람 트래픽 기준으로 설계되어 있다는 거예요.


무엇이 달라졌나

예전 봇 vs 지금 AI 에이전트

예전 봇:
→ 정해진 패턴으로 반복 요청
→ User-Agent 보면 바로 티남
→ robots.txt 대부분 준수
→ 주로 크롤링/스크래핑

지금 AI 에이전트:
→ 사람처럼 브라우저 열고 클릭
→ User-Agent 위장 가능
→ robots.txt 13% 무시
→ 쇼핑, 예약, 폼 제출, 결제까지

진짜 무서운 숫자:

악성 자동화 vs 정상 자동화의 행동 차이: 0.5%

즉, 합법적인 AI 쇼핑 에이전트와
자동화 사기 봇의 행동이 99.5% 같음

실제로 벌어지는 일

이커머스:
→ 고객의 AI 에이전트가 빠르게 상품 탐색 + 결제
→ 사기봇도 똑같이 빠르게 탐색 + 결제 시도
→ 구분 불가

계정 탈취:
→ 2025년 조직당 평균 402,000건의 로그인 후 계정 침해 시도
→ 전년 대비 4배 증가

스크래핑:
→ 전체 사이트 방문의 약 20%가 스크래핑 시도
→ 2022년 대비 2배

AWS/Azure 청구서:
→ AI 크롤러에게 콘텐츠 서빙하느라 예상치 못한 폭탄 청구

기존 방어 전략이 왜 안 통하나

문제 1 — IP 기반 Rate Limiting

기존 방어:
"IP당 분당 100 요청 초과 시 차단"

AI 에이전트 공격:
→ IP 1,000개에 분산 배치
→ 각 IP는 분당 10 요청만 전송
→ 한 IP도 임계값 미달
→ 총합: 분당 10,000 요청

결과: 차단 안 됨

문제 2 — User-Agent 필터링

기존 User-Agent 허용 목록:
"Googlebot, Bingbot은 허용"

현실:
→ AI 에이전트가 Chrome/Safari 위장 가능
→ 정상 브라우저와 구분 불가
→ User-Agent 필터링 무의미

문제 3 — CAPTCHA

GPT-5.4, Claude Opus 4.7 비전 능력:
→ 이미지 인식 능력이 사람과 동등
→ reCAPTCHA v2 통과율 95%+
→ CAPTCHA는 AI 에이전트에 무력화

문제 4 — 요청 수 기반 API Rate Limiting

기존:
"API 키당 분당 100 요청"

AI 에이전트 요청의 실제 비용 차이:
→ 간단한 조회: 50 토큰
→ 복잡한 추론: 50,000 토큰
→ 같은 "1 요청"이지만 비용은 1,000배 차이

결과:
→ 가벼운 요청 1,000개 허용 = 문제없음
→ 무거운 요청 100개 허용 = 서버 폭발
→ "요청 수"는 의미 없는 지표

2026년 실전 방어 전략

전략 1 — 토큰 기반 Rate Limiting

요청 수가 아니라 실제 리소스 소비를 기준으로 제한해요.

from fastapi import FastAPI, HTTPException, Request
from collections import defaultdict
import time

app = FastAPI()

# 토큰 버킷 구현
class TokenBucket:
    def __init__(self, capacity: int, refill_rate: float):
        self.capacity = capacity      # 최대 토큰 수
        self.tokens = capacity        # 현재 토큰 수
        self.refill_rate = refill_rate  # 초당 보충 토큰
        self.last_refill = time.time()

    def consume(self, tokens: int) -> bool:
        now = time.time()
        elapsed = now - self.last_refill

        # 시간 경과에 따라 토큰 보충
        self.tokens = min(
            self.capacity,
            self.tokens + elapsed * self.refill_rate
        )
        self.last_refill = now

        if self.tokens >= tokens:
            self.tokens -= tokens
            return True  # 허용
        return False  # 거부

# API 키별 버킷 관리
buckets = defaultdict(lambda: TokenBucket(
    capacity=100_000,   # 최대 10만 토큰
    refill_rate=1_000   # 초당 1,000 토큰 보충
))

@app.post("/api/llm")
async def llm_endpoint(request: Request, payload: dict):
    api_key = request.headers.get("X-API-Key")
    if not api_key:
        raise HTTPException(status_code=401)

    # 요청에서 예상 토큰 수 추정
    estimated_tokens = estimate_tokens(payload.get("prompt", ""))

    bucket = buckets[api_key]
    if not bucket.consume(estimated_tokens):
        raise HTTPException(
            status_code=429,
            headers={
                "X-RateLimit-Tokens-Remaining": str(int(bucket.tokens)),
                "X-RateLimit-Reset": "60",
                "Retry-After": "60"
            },
            detail="토큰 한도 초과"
        )

    # 실제 처리
    response = await process_llm_request(payload)

    # 실제 사용 토큰을 헤더로 반환 (에이전트가 자가 조절 가능)
    actual_tokens = response.usage.total_tokens
    return {
        "result": response.content,
        "tokens_used": actual_tokens
    }

def estimate_tokens(text: str) -> int:
    # 대략 4자 = 1토큰
    return max(len(text) // 4, 1)

계층형 Rate Limit:

# 짧은 기간 + 긴 기간 조합
RATE_LIMITS = {
    "free": {
        "per_minute": 10_000,   # 분당 1만 토큰
        "per_day": 1_000_000,   # 일당 100만 토큰
    },
    "pro": {
        "per_minute": 100_000,
        "per_day": 10_000_000,
    },
    "enterprise": {
        "per_minute": 1_000_000,
        "per_day": 100_000_000,
    }
}

전략 2 — 에이전트 신원 검증

이제 API를 두 가지로 분리해서 관리해야 해요.

from enum import Enum
from dataclasses import dataclass

class ClientType(Enum):
    HUMAN = "human"
    AGENT = "agent"
    CRAWLER = "crawler"
    UNKNOWN = "unknown"

@dataclass
class ClientProfile:
    type: ClientType
    api_key: str
    agent_id: str | None  # 에이전트 식별자
    owner_id: str | None  # 에이전트 소유 사용자

def classify_client(request: Request) -> ClientProfile:
    ua = request.headers.get("User-Agent", "")
    api_key = request.headers.get("X-API-Key", "")

    # 에이전트 자가 신고 헤더 (새 표준)
    agent_id = request.headers.get("X-Agent-ID")
    owner_id = request.headers.get("X-Agent-Owner")

    # 알려진 AI 에이전트 패턴
    agent_patterns = [
        "claude-code", "openai-codex", "cursor",
        "python-httpx", "python-requests",
        "anthropic-sdk", "openai-sdk"
    ]

    if agent_id:
        return ClientProfile(
            type=ClientType.AGENT,
            api_key=api_key,
            agent_id=agent_id,
            owner_id=owner_id
        )
    elif any(p in ua.lower() for p in agent_patterns):
        return ClientProfile(
            type=ClientType.AGENT,
            api_key=api_key,
            agent_id=None,
            owner_id=None
        )
    else:
        return ClientProfile(
            type=ClientType.HUMAN,
            api_key=api_key,
            agent_id=None,
            owner_id=None
        )

# 클라이언트 타입에 따라 다른 정책 적용
async def apply_policy(profile: ClientProfile, request: Request):
    if profile.type == ClientType.AGENT:
        # 에이전트: 토큰 기반 제한 + 비용 추적
        return await agent_policy(profile, request)
    elif profile.type == ClientType.HUMAN:
        # 사람: 요청 수 기반 제한
        return await human_policy(profile, request)
    else:
        # 미확인: 보수적 제한
        return await unknown_policy(profile, request)

전략 3 — 행동 기반 이상 탐지

0.5% 차이밖에 없는 정상/악성 에이전트를 구분하는 방법이에요.

import hashlib
from collections import deque

class AgentBehaviorTracker:
    def __init__(self, window_size: int = 100):
        self.window_size = window_size
        # API 키별 최근 요청 패턴 추적
        self.request_history = defaultdict(lambda: deque(maxlen=window_size))

    def record(self, api_key: str, endpoint: str, tokens: int, latency_ms: float):
        self.request_history[api_key].append({
            "endpoint": endpoint,
            "tokens": tokens,
            "latency_ms": latency_ms,
            "timestamp": time.time()
        })

    def is_anomalous(self, api_key: str) -> dict:
        history = list(self.request_history[api_key])
        if len(history) < 10:
            return {"anomalous": False}

        recent = history[-10:]

        # 이상 패턴 감지
        checks = {
            # 1. 동일 엔드포인트 반복 (루프 감지)
            "loop": len(set(r["endpoint"] for r in recent)) == 1,

            # 2. 토큰 급증 (폭주 에이전트)
            "token_spike": (
                recent[-1]["tokens"] >
                sum(r["tokens"] for r in recent[:-1]) / len(recent[:-1]) * 10
            ),

            # 3. 비정상적으로 빠른 요청 (밀리초 단위)
            "too_fast": all(
                r["latency_ms"] < 10 for r in recent
            ),

            # 4. 비용 폭발 (최근 10분간 급증)
            "cost_explosion": self._check_cost_explosion(api_key)
        }

        is_anomalous = any(checks.values())
        return {
            "anomalous": is_anomalous,
            "reasons": [k for k, v in checks.items() if v]
        }

    def _check_cost_explosion(self, api_key: str) -> bool:
        history = list(self.request_history[api_key])
        if len(history) < 20:
            return False

        # 앞 절반 vs 뒷 절반 토큰 사용량 비교
        mid = len(history) // 2
        first_half_avg = sum(r["tokens"] for r in history[:mid]) / mid
        second_half_avg = sum(r["tokens"] for r in history[mid:]) / mid

        return second_half_avg > first_half_avg * 5  # 5배 초과 시 이상

전략 4 — robots.txt → llms.txt

robots.txt는 이미 13%의 AI 봇이 무시해요. 새로운 표준이 등장했어요.

# robots.txt (기존)
User-agent: *
Disallow: /private/
Crawl-delay: 10

# llms.txt (새 표준 — AI 에이전트 전용)
# 위치: https://yoursite.com/llms.txt

# AI 훈련 크롤러 허용 여부
AI-Training: disallowed

# AI 에이전트 접근 정책
AI-Agent-Access: allowed
AI-Agent-Rate-Limit: 1000 tokens/minute
AI-Agent-Contact: api@yoursite.com

# 허용 에이전트
Allow-Agent: Claude/*
Allow-Agent: GPT/*
Allow-Agent: Gemini/*

# 차단 에이전트
Disallow-Agent: *DataScraper*
Disallow-Agent: *TrainingCrawler*

전략 5 — 인프라 비용 방어

AI 크롤러가 서버 비용을 올리는 걸 막아야 해요.

# Nginx 설정
# AI 크롤러 User-Agent 감지 후 차별화 처리

geo $ai_traffic {
    default 0;
    # 알려진 AI 크롤러 IP 대역
    66.249.0.0/16 1;    # Googlebot
    157.55.0.0/16 1;    # Bingbot
}

map $http_user_agent $is_ai_agent {
    default 0;
    ~*"ClaudeBot" 1;
    ~*"GPTBot" 1;
    ~*"anthropic-ai" 1;
    ~*"PerplexityBot" 1;
    ~*"Python-httpx" 1;
}

server {
    # AI 에이전트 전용 제한
    limit_req_zone $binary_remote_addr
        zone=ai_agents:10m rate=10r/m;

    location / {
        if ($is_ai_agent) {
            limit_req zone=ai_agents burst=5;
            # 정적 캐시 서빙 (DB 히트 없음)
            add_header X-Served-By "cache";
        }
    }

    # 크롤러 전용 엔드포인트
    # 비용 없는 정적 콘텐츠만 서빙
    location /ai-feed/ {
        alias /var/www/static-feed/;
        # 여기서만 크롤러 허용
    }
}

지금 당장 해야 할 것

즉시 (이번 주):
□ 서버 로그에서 AI 에이전트 트래픽 비중 측정
□ API에 X-Agent-ID 헤더 지원 추가
□ llms.txt 파일 작성해서 배포

단기 (이번 달):
□ 요청 수 → 토큰 수 기반 Rate Limit 전환
□ 에이전트 행동 이상 탐지 모니터링 추가
□ AI 크롤러 전용 정적 캐시 레이어 구축

중기 (다음 분기):
□ 에이전트 신원 검증 시스템 구축
□ 인간 트래픽 vs 에이전트 트래픽 별도 analytics 파이프라인
□ 에이전트 전용 API 티어 설계

 

반응형