AI 에이전트 관찰

GPTBot, ClaudeBot, PerplexityBot 같은 AI 크롤러는 매일 사이트를 읽지만, 대부분의 운영자는 이를 전혀 보지 못합니다. 관찰 모드는 SilentShield AI 에이전트 제어의 1단계입니다: 아무것도 차단하지 않고 사이트 동작을 바꾸지 않으면서, 어떤 AI 서비스가 어떤 페이지를 방문하는지 기록합니다.

제어보다 가시성이 먼저입니다: 누가 콘텐츠를 읽는지 알아야 누구를 허용하고, 제한하고, 차단할지 제대로 결정할 수 있습니다. 그래서 모든 방문 기록은 세 가지 카테고리 중 하나로 분류됩니다:

학습
크롤러가 향후 AI 모델 학습을 위해 콘텐츠를 수집합니다 (예: GPTBot, ClaudeBot, CCBot).
AI 검색
서비스가 사용자 질문에 답하기 위해 페이지를 읽으며, 이때 사이트를 출처로 인용할 수 있습니다 (예: OAI-SearchBot, PerplexityBot).
에이전트
봇이 특정 사용자를 대신해 실시간으로 작동합니다. 예를 들어 상품을 조사하거나 사이트의 폼을 작성합니다 (예: ChatGPT-User, Claude-User).

플랫폼별 설정

WordPress

내장 옵저버는 SilentShield 플러그인의 향후 업데이트와 함께 제공됩니다. 그때까지는 작은 must-use 플러그인으로 관찰을 활성화하세요 — 파일 하나, 설정 불필요:

wp-content/mu-plugins/silentshield-observer.php 파일을 아래 내용으로 만드세요(mu-plugin은 자동으로 로드됩니다. 비활성화하려면 파일을 삭제하세요):

wp-config.phpphp
<?php
/**
 * Plugin Name: SilentShield — AI Agent Observer
 * Description: Reports AI crawler visits to SilentShield (fire-and-forget;
 * IP/UA are hashed server-side, GDPR). Delete this file to disable.
 *
 * Install: save this file as wp-content/mu-plugins/silentshield-observer.php
 * (mu-plugins load automatically — no activation needed). To force it off
 * without deleting: define('SILENTSHIELD_OBSERVER', false) in wp-config.php.
 * An upcoming SilentShield plugin update will build this in.
 */
if (!defined('ABSPATH')) exit;
if (defined('SILENTSHIELD_OBSERVER') && !SILENTSHIELD_OBSERVER) return;

const SS_OBSERVE_URL = "https://api.silentshield.io/api/v1/agent/telemetry";
const SS_API_KEY = "YOUR_API_KEY"; // publishable site key
// For the always-fresh list, cache https://api.silentshield.io/api/v1/agent/bot-directory daily.
$ss_bots = ["GPTBot","OAI-SearchBot","ChatGPT-User","ClaudeBot","Claude-User",
  "PerplexityBot","Perplexity-User","Bingbot","Applebot","Amazonbot","CCBot",
  "Bytespider","meta-externalagent","Google-Agent"];

register_shutdown_function(function () use ($ss_bots) {
  $ua = $_SERVER["HTTP_USER_AGENT"] ?? "";
  $signed = !empty($_SERVER["HTTP_SIGNATURE"]);
  $isBot = $signed;
  foreach ($ss_bots as $b) { if (stripos($ua, $b) !== false) { $isBot = true; break; } }
  if (!$isBot) return;

  if (function_exists("fastcgi_finish_request")) { fastcgi_finish_request(); }

  wp_remote_post(SS_OBSERVE_URL, [
    "timeout"  => 3,
    "blocking" => false,
    "headers"  => ["Content-Type" => "application/json", "x-api-key" => SS_API_KEY],
    "body"     => wp_json_encode(["sightings" => [[
      "ua"     => $ua,
      "ip"     => $_SERVER["REMOTE_ADDR"] ?? "",
      "path"   => strtok($_SERVER["REQUEST_URI"] ?? "/", "?"),
      "method" => $_SERVER["REQUEST_METHOD"] ?? "GET",
    ]]]),
  ]);
});

Next.js

독립형 미들웨어입니다 — SDK가 필요 없습니다. 관찰은 event.waitUntil()을 통해 응답 경로 밖에서 실행되므로 페이지가 지연되지 않습니다:

middleware.tstypescript
// middleware.ts — self-contained observer, no SDK required.
// Sightings are reported via event.waitUntil(), off the response path,
// so your pages never wait. Best-effort and fail-open by design.

import { NextResponse, type NextRequest } from "next/server";

const OBSERVE_URL = "https://api.silentshield.io/api/v1/agent/telemetry";
const API_KEY = "YOUR_API_KEY"; // publishable site key

// Known AI-agent UA tokens. For the always-fresh list, fetch
// https://api.silentshield.io/api/v1/agent/bot-directory once a day and cache it.
const BOTS = ["GPTBot", "OAI-SearchBot", "ChatGPT-User", "ClaudeBot", "Claude-User",
  "PerplexityBot", "Perplexity-User", "Bingbot", "Applebot", "Amazonbot", "CCBot",
  "Bytespider", "meta-externalagent", "Google-Agent"];

export function middleware(req: NextRequest, event: { waitUntil(p: Promise<unknown>): void }) {
  const ua = req.headers.get("user-agent") ?? "";
  const signed = !!req.headers.get("signature");
  if (signed || BOTS.some((b) => ua.toLowerCase().includes(b.toLowerCase()))) {
    const sighting = {
      ua,
      path: req.nextUrl.pathname,
      method: req.method,
      ip: req.headers.get("x-forwarded-for")?.split(",")[0]?.trim(),
      ...(signed && {
        signature: req.headers.get("signature"),
        signature_input: req.headers.get("signature-input"),
        signature_agent: req.headers.get("signature-agent"),
        authority: req.nextUrl.host,
        scheme: req.nextUrl.protocol.replace(":", ""),
      }),
    };
    event.waitUntil(
      fetch(OBSERVE_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json", "x-api-key": API_KEY },
        body: JSON.stringify({ sightings: [sighting] }),
      }).catch(() => {}) // best-effort, fail-open
    );
  }
  return NextResponse.next();
}

Node.js (Express)

Express(또는 다른 Node 서버)에는 fire-and-forget 미들웨어를 추가하세요. User-Agent가 알려진 AI 에이전트와 일치하거나 Web Bot Auth 서명이 있는 요청만 보고하며 — 응답을 절대 차단하지 않습니다:

server.jsjavascript
import express from "express";
const app = express();

const OBSERVE_URL = "https://api.silentshield.io/api/v1/agent/telemetry";
const API_KEY = "YOUR_API_KEY";

// Known AI-agent UA tokens. For the always-fresh list, fetch
// https://api.silentshield.io/api/v1/agent/bot-directory once a day and cache it.
const BOTS = ["GPTBot", "OAI-SearchBot", "ChatGPT-User", "ClaudeBot", "Claude-User",
  "PerplexityBot", "Perplexity-User", "Bingbot", "Applebot", "Amazonbot", "CCBot",
  "Bytespider", "meta-externalagent", "Google-Agent"];

// Fire-and-forget observer — never blocks the response.
app.use((req, res, next) => {
  const ua = req.headers["user-agent"] || "";
  const signed = !!req.headers["signature"];
  if (signed || BOTS.some((b) => ua.toLowerCase().includes(b.toLowerCase()))) {
    const sighting = {
      ua, ip: req.ip, path: req.path, method: req.method,
      ...(signed && {
        signature: req.headers["signature"],
        signature_input: req.headers["signature-input"],
        signature_agent: req.headers["signature-agent"],
        authority: req.headers["host"], scheme: req.protocol,
      }),
    };
    fetch(OBSERVE_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json", "x-api-key": API_KEY },
      body: JSON.stringify({ sightings: [sighting] }),
    }).catch(() => {}); // best-effort, fail-open
  }
  next();
});

PHP

순수 PHP에서는 이 옵저버를 프런트 컨트롤러 맨 위에 포함하세요. 응답이 전달된 후에만(fastcgi_finish_request) 방문 기록을 전송하므로 페이지가 느려지지 않습니다:

observer.phpphp
<?php
// Drop-in observer: include at the very top of your front controller.
// It runs after the response is sent (fastcgi_finish_request) so it never
// slows down the page. Reuses the same publishable key.

const SS_OBSERVE_URL = "https://api.silentshield.io/api/v1/agent/telemetry";
const SS_API_KEY = "YOUR_API_KEY";
// For the always-fresh list, cache https://api.silentshield.io/api/v1/agent/bot-directory daily.
$ss_bots = ["GPTBot","OAI-SearchBot","ChatGPT-User","ClaudeBot","Claude-User",
  "PerplexityBot","Perplexity-User","Bingbot","Applebot","Amazonbot","CCBot",
  "Bytespider","meta-externalagent","Google-Agent"];

register_shutdown_function(function () use ($ss_bots) {
  $ua = $_SERVER["HTTP_USER_AGENT"] ?? "";
  $signed = !empty($_SERVER["HTTP_SIGNATURE"]);
  $isBot = $signed;
  foreach ($ss_bots as $b) { if (stripos($ua, $b) !== false) { $isBot = true; break; } }
  if (!$isBot) return;

  if (function_exists("fastcgi_finish_request")) { fastcgi_finish_request(); }

  $sighting = [
    "ua" => $ua,
    "ip" => $_SERVER["REMOTE_ADDR"] ?? "",
    "path" => strtok($_SERVER["REQUEST_URI"] ?? "/", "?"),
    "method" => $_SERVER["REQUEST_METHOD"] ?? "GET",
  ];
  $ch = curl_init(SS_OBSERVE_URL);
  curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 3,
    CURLOPT_HTTPHEADER => ["Content-Type: application/json", "x-api-key: " . SS_API_KEY],
    CURLOPT_POSTFIELDS => json_encode(["sightings" => [$sighting]]),
  ]);
  curl_exec($ch); // best-effort, fail-open
  curl_close($ch);
});

Go

몇 줄로 끝나는 독립형 옵저버입니다 — 표준 라이브러리만 사용, SDK 불필요. 감지는 fire-and-forget 방식으로 전송되므로 어떤 요청도 느려지지 않습니다:

main.gogo
// Self-contained observer — stdlib only, no SDK required.
// Reports AI-agent sightings fire-and-forget: never blocks a request,
// fail-open on any error. For the always-fresh bot list, cache
// https://api.silentshield.io/api/v1/agent/bot-directory daily.

const observeURL = "https://api.silentshield.io/api/v1/agent/telemetry"
const siteKey = "YOUR_API_KEY" // publishable — or os.Getenv("SILENTSHIELD_SITE_KEY")

var bots = []string{"GPTBot", "OAI-SearchBot", "ChatGPT-User", "ClaudeBot",
    "Claude-User", "PerplexityBot", "Perplexity-User", "Bingbot", "Applebot",
    "Amazonbot", "CCBot", "Bytespider", "meta-externalagent", "Google-Agent"}

func observe(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ua, signed := r.UserAgent(), r.Header.Get("Signature") != ""
        hit := signed
        for _, b := range bots {
            if strings.Contains(strings.ToLower(ua), strings.ToLower(b)) {
                hit = true
                break
            }
        }
        if hit {
            ip, _, _ := net.SplitHostPort(r.RemoteAddr)
            s := map[string]any{"ua": ua, "ip": ip, "path": r.URL.Path, "method": r.Method}
            if signed { // Web Bot Auth (RFC 9421) — forward for verification
                s["signature"] = r.Header.Get("Signature")
                s["signature_input"] = r.Header.Get("Signature-Input")
                s["signature_agent"] = r.Header.Get("Signature-Agent")
                s["authority"] = r.Host
            }
            body, _ := json.Marshal(map[string]any{"sightings": []any{s}})
            go func() { // best-effort, fail-open
                req, _ := http.NewRequest("POST", observeURL, bytes.NewReader(body))
                req.Header.Set("Content-Type", "application/json")
                req.Header.Set("x-api-key", siteKey)
                if resp, err := (&http.Client{Timeout: 3 * time.Second}).Do(req); err == nil {
                    resp.Body.Close()
                }
            }()
        }
        next.ServeHTTP(w, r)
    })
}

// net/http:  http.ListenAndServe(":8080", observe(mux))
// Gin:       srv := &http.Server{Addr: ":8080", Handler: observe(r)}
//            srv.ListenAndServe()

연결 테스트

통합이 작동하는지 확인하려고 첫 번째 실제 AI 크롤러를 기다릴 필요가 없습니다. 빈 배치를 보내 보세요 — 가짜 방문 기록을 남기지 않으면서 키에 하트비트를 등록합니다:

Terminalbash
curl -X POST https://api.silentshield.io/api/v1/agent/telemetry \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  --data '{"sightings":[]}'

그러면 API 키의 에이전트 허브(API 키 → 키 선택 → 에이전트)가 "연결되지 않음"에서 "연결됨 — 수신 대기 중"으로 바뀝니다. 실제 에이전트가 방문할 때까지 리포트는 비어 있습니다: SilentShield는 데이터를 지어내지 않습니다.

GDPR

관찰은 정당한 이익(GDPR Art. 6(1)(f))에 근거합니다. IP 주소는 매일 바뀌는 솔트로 서버 측에서 해시되며 — 원본 IP는 절대 저장되지 않습니다. 방문 기록은 14일간 보관되고, 알려진 AI 에이전트의 요청만 저장됩니다: 일반 방문자 트래픽은 절대 기록되지 않습니다.

FAQ

사이트가 느려지나요?

아니요. 모든 통합은 fire-and-forget 방식입니다: Next.js는 event.waitUntil()을 사용하고, PHP는 응답 전송 후에만 보고하며, Node와 Go는 백그라운드에서 전송합니다. SilentShield API에 연결할 수 없더라도 사이트에는 영향이 없습니다 — 모든 것이 fail-open으로 동작합니다.

무엇이 AI 에이전트로 집계되나요?

SilentShield의 알려진 AI 크롤러·에이전트 봇 디렉터리(GPTBot, ClaudeBot, PerplexityBot 등)와 일치하거나 Web Bot Auth 서명이 있는 요청만 해당합니다. 항상 최신 목록은 https://api.silentshield.io/api/v1/agent/bot-directory 에서 확인할 수 있습니다.

왜 아직 데이터가 보이지 않나요?

AI 크롤러는 자체 일정에 따라 방문합니다. 대부분의 사이트에서는 24–72시간 안에 첫 방문 기록이 나타나며, 작은 사이트는 더 오래 걸릴 수 있습니다. 그동안에는 에이전트 허브의 하트비트가 통합 자체가 작동하고 있음을 확인해 줍니다.

다음: 에이전트 규칙 적용 (2단계) →