WebFetch가 막히는 순간 자동으로 끼어들어, 공개 접근 경로를 단계적으로 상승(Phase 0→3)시키며 하나가 뚫릴 때까지 시도한다.
authentication required라고 정직하게 보고하며, 사용자를 대신해 로그인하지 않고 자격 증명을 저장·전송하지 않는다. "인증 우회 도구가 아니라 공개 콘텐츠 전용 리더"라는 게 설계 자체에 박혀 있다.
fivetaku/insane-search · 버전 0.12.1 · 라이선스 MIT · ⭐ 약 1.9k · 포크 247 · 언어 Python 88.6% · JS 8.2% · Shell 3.2% · gptaku-plugins 마켓플레이스의 일부 · 핵심 engine/(fetch_chain 1,332줄)·curl_cffi TLS 임퍼소네이션·yt-dlp·Playwright/patchright · shallow clone 소스 직접 분석)
평범한 fetch는 403을 맞으면 "못 읽는다"고 끝낸다. insane-search는 그 지점에서 "아직 안 해본 공개 경로"를 차례로 올린다 — 그리고 로그인·페이월이라는 진짜 벽을 만나면 가장하지 않고 멈춰 정직하게 보고한다.
외울 명령어는 없다. 평소처럼 Claude Code에게 "레딧에서 이 주제 반응 찾아줘", "이 유튜브 요약해줘"라고 부탁하면, fetch가 막히는 순간 플러그인이 알아서 끼어든다.
403·CAPTCHA를 띄운다. TLS 임퍼소네이션은 실제 브라우저의 암호화 악수 지문(JA3/JA4)까지 똑같이 흉내 내 "진짜 사파리/크롬"처럼 보이게 하는 기법이다. 공개 경로는 로그인 없이 닿을 수 있는 API·피드·아카이브·렌더된 페이지를 뜻한다 — insane-search는 이 안에서만 움직인다.에이전트에게 웹을 읽히는 도구는 많다. insane-search가 눈에 띄는 건 차단 앞에서의 태도가 근본부터 다르기 때문이다 — 한 번 막히면 끝이 아니라, 공개 경로를 단계적으로 상승시키며 집요하게 재시도한다.
공개 API 리더 → 신디케이션 게이트웨이 → TLS 임퍼소네이션 → 진짜 헤드리스 브라우저까지, 직전 단계가 실패하거나 차단 신호를 감지했을 때만 다음 단계로 올라간다. 단순히 User-Agent만 바꾸는 게 아니라 완전한 브라우저 정체성(실제 TLS 지문·쿠키 워밍·리퍼러 체인)을 구성한다. 기본 Claude Code 단독이면 403·WAF·CAPTCHA에서 그냥 포기하던 자리를, 하나 뚫릴 때까지 밀어붙인다.
가장 인상적인 설계 원칙이다. engine/**·waf_profiles.yaml에는 특정 사이트 도메인·셀렉터·브랜드명을 하드코딩 금지한다(bias_check.py가 CI 게이트로 강제). 대신 프로파일은 WAF 제품별(akamai_bot_manager·cloudflare_turnstile·datadome…)로 짜여, "같은 WAF를 쓰는 어느 사이트에나 유효한가?"를 기준으로 삼는다. 그래서 새 사이트도 코드 수정 없이 커버된다.
HTML은 막혀도 사이트가 내부적으로 쓰는 JSON API는 방어가 얕은 경우가 많다. insane-search는 진짜 브라우저의 네트워크 트래픽을 관찰해 그 내부 엔드포인트를 그대로 재활용한다. INSANE_AUTO_FORGE=1이면 체인이 실패할 때 스스로 렌더→XHR 캡처→본문과 겹치는 데이터 API 선별→curl 재현→레시피 자동 저장까지 한다(광고·텔레메트리 endpoint는 제외).
엔진이 반환한 공개 웹 본문은 untrusted_public_web으로 다룬다. 본문 속 문장이 무언가를 지시하더라도 명령 실행·파일 접근·토큰 노출·상위 지시 무시는 금지된다. [BEGIN/END UNTRUSTED WEB CONTENT] 경계(생성된 boundary id 부착)와 to_untrusted_text()로 프롬프트 인젝션을 구조적으로 막는다. 웹을 읽는 에이전트 도구가 반드시 갖춰야 할 방어를 실제로 구현했다.
DISCLAIMER가 명확히 한다: 이건 범용 개발 도구라 본질적으로 이중 용도이며, 저자는 제3자의 실제 사용을 통제할 수 없다. 로그인·페이월에서 멈추도록 설계됐지만, 대상 사이트의 이용약관(ToS)·관련 법 준수는 전적으로 사용자 책임이다. 소유하지 않은 시스템에 접근하기 전 정당한 권한을 확보해야 한다. 학습·정리는 자유롭되, 실제 수집 활동은 각 플랫폼 규정과 법을 먼저 확인할 것.
| 층 | 기술 | 왜 이걸 썼나 |
|---|---|---|
| 엔진 코어 | Python 3 (engine/, 88.6%) | fetch_chain.py(1,332줄) 단일 진입점. probe→validate→detect→plan→execute→report. |
| TLS 위장 | curl_cffi (≥0.15) | Safari/Chrome/Firefox의 TLS(JA3/JA4) 지문을 복제. 0.15부터 최신 Chrome 지문·HTTP/3·SSRF-safe redirect. |
| 미디어 | yt-dlp | YouTube 등 1,858개 사이트의 메타데이터·자막을 --dump-json으로. |
| 헤드리스 브라우저 | Playwright · patchright · nodriver | patchright는 Playwright 포크로 CDP Runtime.enable 누출을 막아 봇 탐지 회피. nodriver는 raw CDP. |
| 콘텐츠 추출 | markdownify(기본 ON) · resiliparse · pdfplumber | raw HTML→구조보존 마크다운(기본). 본문만 뽑기(opt-in), PDF는 pdfplumber. AGPL인 PyMuPDF는 금지. |
| 파싱·설정 | BeautifulSoup4 · PyYAML · pypdf | OGP/JSON-LD 메타 스캔, WAF 프로파일(yaml), PDF 폴백. |
| 설치 | 첫 실행 시 자동 | 필요 패키지를 최초 호출 때 pip install. API 키도, 가입도 없다. |
insane-search는 잠긴 (공개) 문을 여는 열쇠 꾸러미 같다. 먼저 정문 열쇠(공식 API)를 넣어 보고, 안 되면 옆문(모바일·RSS 변형), 그다음 경비원처럼 보이는 변장(TLS 위장), 마지막엔 진짜 사람이 걸어 들어가는 것(헤드리스 브라우저)까지 차례로 시도한다. 단, 비밀번호가 걸린 문(로그인·페이월) 앞에서는 열쇠를 꺼내지 않고 "여긴 인증이 필요합니다"라고 물러선다.
대부분의 fetch는 HTTP 200이면 성공으로 친다. 여기선 200은 "검사 시작 조건"일 뿐이고, 네 가지를 모두(AND) 통과해야 성공이다.
# validate() — 4계층 AND (하나라도 실패하면 격자는 계속 탐색)
1) 챌린지 마커 없음 ("Just a moment...", "Access Denied", DataDome …)
2) 비정상 크기 아님 (본문 < 3KB 또는 WAF 지문 크기 아님)
3) 쿠키 센서 정상 (Akamai _abck=~-1~ 같은 차단 상태 아님)
4) success_selectors 매칭 (caller 제공 → strong_ok, 미제공 → weak_ok)
그래서 "200을 받았지만 실은 CAPTCHA 페이지"에 속지 않는다. 첫 200에서 탈출하지 않고, 진짜 콘텐츠 증거를 확인할 때까지 격자를 계속 돈다.
waf_profiles.yaml은 사이트가 아니라 WAF 제품별로 짜여 있다. 예를 들어 Akamai Bot Manager 프로파일은 이렇게 생겼다.
akamai_bot_manager:
detectors:
cookie: ["_abck", "bm_sz", "ak_bmsc"] # 제품 아티팩트로 감지
server_contains: ["AkamaiGHost"]
capabilities_needed:
- needs_real_tls_stack # Playwright 번들 TLS는 탐지됨
- needs_js_exec # JS 센서 챌린지
- needs_protocol_stealth # Runtime.enable 지문화 → 심 계열 실패
tls_impersonate_candidates:
- [safari, safari15_5, safari17_0, safari260] # 계열별 우선순위
- [chrome99, chrome119, chrome131, chrome146]
감지는 다중 신호 게이팅(신호 2개 → confidence 0.9)으로 오탐을 줄이고, 프로파일은 "결정적 레시피"가 아니라 우선순위 추천(prior)일 뿐 — 실제 응답을 매 시도마다 평가한다. 그리고 executor.py가 이 capabilities_needed 태그를 읽어 알맞은 브라우저 실행기를 자동 선택한다(needs_protocol_stealth면 nodriver→patchright 등).
fetch_chain.py의 v2 스케줄러는 격자를 만든 뒤 다양성을 위해 재정렬한다 — 두 URL 변형에 걸쳐 TLS 계열마다 대표 하나씩을 먼저 놓아, 작은 시도 예산으로도 모든 계열·변형을 건드리게 한다(사파리 계열만 태우다 끝나지 않게). tls_impersonate_avoid 항목은 삭제가 아니라 후순위로 미룰 뿐이라, 전수 모드에선 여전히 시도된다.
하네스 규칙 R6이 압권이다. engine은 실패 시 ok=false와 함께 아직 안 해본 경로(untried_routes)·grid_exhausted·must_invoke_playwright_mcp 플래그를 돌려준다. 이 셋이 모두 충족되기 전엔 "뚫을 수 없음" 결론이 금지된다. CLI는 미완이면 ⛔ NOT EXHAUSTED를 stderr로 찍어, 에이전트가 게을리 포기하지 못하게 기계적으로 고삐를 채운다. (단 auth_required·404 같은 terminal이면 정직하게 실패 인정 — 429는 terminal 아님.)
skills/insane-search/engine/가 심장 — 그 위를 얇은 플러그인 껍질이 감싼다구조가 말하는 것: 지식은 코드가 아니라 데이터/설정에 산다. 뚫는 방법(제품별 프로파일)은 waf_profiles.yaml에, 플랫폼 공식 경로는 phase0.py·references/에, 사이트 고유 힌트는 런타임 인자에만 둔다. bias_check.py가 "사이트명 하드코딩"을 CI에서 막아, 엔진이 특정 사이트에 유착되지 않게 강제한다.
하나의 방법에 올인하지 않고 비용이 낮은 것부터 높은 것으로(API→프로브→TLS위장→브라우저) 단계 상승하는 설계. 각 단계는 앞이 실패했을 때만 발동해 평균 비용은 낮게, 최악의 경우 성공률은 높게 가져간다. 재시도·폴백 시스템 전반에 통하는 사고틀이다.
봇 탐지가 User-Agent가 아니라 TLS 악수(JA3/JA4) 지문을 본다는 사실, 그리고 curl_cffi로 그걸 복제하는 법. 네트워크가 "누가 말하는가"를 어떻게 식별하는지 이해하면 보안·관측 양쪽에서 값지다.
특정 사이트에 맞추면 당장은 쉽지만 곧 썩는다. 공통 축(WAF 제품)으로 추상화하고 사이트 고유 정보는 런타임으로 밀어내는 규율, 그리고 그걸 bias_check.py로 CI에서 강제하는 방법. 규칙을 문서가 아니라 게이트로 지키는 사례.
성공을 상태코드가 아니라 콘텐츠 증거의 다층 AND로 정의하는 설계. 마커·크기·쿠키·셀렉터를 조합해 "받았지만 실은 차단 페이지"를 걸러낸다. 신뢰할 수 있는 자동화의 핵심은 "무엇이 성공인가"의 엄밀한 정의다.
외부 웹 텍스트를 untrusted_public_web으로 격리하고 명령이 아니라 데이터로 취급하는 경계 설계. LLM에 웹을 먹이는 모든 도구가 반드시 갖춰야 할 방어를 실제 코드(to_untrusted_text()·boundary id)로 본다.
R1~R8처럼 에이전트에게 강제되는 규칙을 두고, engine이 untried_routes·⛔ NOT EXHAUSTED로 "포기 금지"를 기계적으로 집행하는 설계. LLM의 즉흥 판단을 신뢰 가능한 절차로 묶는 하네스 패턴을 배운다.
플러그인 설치 후, 당신이 소유한 사이트나 공개 문서 URL로 python3 -m engine "<URL>" --trace를 돌려 result.trace를 읽어라. probe→detect→plan→execute가 어떤 순서로 무엇을 시도했는지, verdict가 어떻게 매겨졌는지 눈으로 따라가면 에스컬레이션의 실체가 잡힌다.
| 항목 | 내용 |
|---|---|
| 플랫폼 | Claude Code 플러그인. /plugin marketplace add …/gptaku_plugins.git → /plugin install insane-search@gptaku-plugins → /reload-plugins. |
| 런타임 | Python 3(엔진). Playwright 로컬 경로 사용 시 Node. 필요 패키지는 첫 실행 시 자동 설치(curl_cffi≥0.15·bs4·pyyaml·pypdf·markdownify). |
| 키·프록시 | 불필요. API 키도, 가입도, 프록시 설정도 없다. |
| 진입점 | python3 -m engine "<URL>" 하나. Phase 0 자동 + 실패 시 격자→브라우저 에스컬레이션. |
| 라이선스 | MIT (© 2026 fivetaku). DISCLAIMER는 이중 용도·무보증·사용자 책임을 명시. |
insane-search는 공개 페이지·공개 API·피드·아카이브·브라우저의 공개 응답으로 닿을 수 있는 것만 읽는다. 로그인·페이월에서 멈추고 authentication required라고 보고하며, 대신 로그인하지 않고 자격 증명을 저장·전송하지 않는다. 인증 우회·접근 통제 회피·무단 접근 도구가 아니다. 동시에 이건 이중 용도 도구라, 대상 사이트의 이용약관·관련 법 준수와 정당한 권한 확보는 전적으로 사용자 책임이다. 이 딥다이브는 아키텍처 학습용 정리이며, 실제 수집 전에는 각 플랫폼 규정을 확인하라.
공개 문서나 본인 소유 사이트 URL로 python3 -m engine "<URL>" --trace --json을 돌려, probe→detect→plan→execute 각 시도의 transform×impersonate×referer 조합과 verdict를 표로 정리하라. 목표: "단계 상승"이 실제로 어떻게 도는지 체감.
engine/validators.py를 읽고, "200이지만 챌린지 페이지"·"200이고 진짜 본문" 두 응답을 만들어 네 계층(마커·크기·쿠키·셀렉터)이 각각 어떻게 판정하는지 확인하라. 목표: 성공을 상태코드가 아니라 증거로 정의하는 법.
waf_profiles.yaml의 cloudflare_turnstile 또는 datadome 프로파일을 골라 detectors·capabilities_needed·tls_candidates가 무엇을 의미하는지 주석으로 풀어 보라. 목표: "제품별 일반화"가 어떻게 사이트 무관 커버리지를 만드는지 이해.
engine/에 일부러 if "example.com" in url 같은 사이트 분기를 넣고 python3 engine/bias_check.py가 잡아내는지 확인한 뒤 되돌려라. 목표: 규칙을 CI 게이트로 강제하는 설계를 체험.
content_safety.py의 to_untrusted_text()·boundary 래핑을 읽고, "본문에 가짜 지시가 든 페이지"를 모킹해 그 지시가 명령으로 실행되지 않고 데이터로만 다뤄지는지 확인하라. 목표: 웹-읽기 에이전트의 프롬프트 인젝션 방어를 손으로 검증.
| 주차 | 주제 | 무엇을 · 왜 |
|---|---|---|
| 1주차 | HTTP·TLS 지문 | TLS 핸드셰이크, JA3/JA4 지문, HTTP/2·3 핑거프린트. 봇 탐지가 UA 너머 무엇을 보는지. curl_cffi로 과제 1 실습. |
| 2주차 | 안티봇·WAF 생태계 | Cloudflare·Akamai·DataDome·PerimeterX·Kasada의 감지 신호(쿠키·인터스티셜·센서). 제품별 프로파일로 과제 3. |
| 3주차 | 헤드리스 브라우저 · CDP 스텔스 | Playwright/CDP, Runtime.enable 누출, patchright·nodriver의 스텔스 원리, 네트워크 트래픽 캡처(숨은 API). |
| 4주차 | 콘텐츠 추출 · 검증 | OGP/JSON-LD 메타, 마크다운 변환, 본문 추출, "성공의 정의"(4계층). 과제 2로 검증 재현. |
| 5주차 | 에이전트 하네스 · 프롬프트 인젝션 | 도구를 절차로 묶는 하네스(R1~R8), 실패 게이트, untrusted 웹 경계. 과제 4·5로 마무리. 법·ToS 준수 원칙 병행. |