브라우저가 사람이 아니라 AI를 위해 설계되었다면?

바로 앞에서 지켜본 고통

6년차 클라우드 인프라 엔지니어입니다. AI 프로덕트를 만드는 프로젝트에 여러 번 참여했는데, 옆에서 엔지니어들이 고생하는 것을 셀 수 없이 많이 봐왔습니다.

항상 같은 패턴이었습니다. 근거 데이터를 꼼꼼하게 넣어줘도 AI는 할루시네이션을 합니다. 틀린 숫자. 틀린 맥락. 자신감 넘치게. 그러면 누군가가 세 시간 동안 출처를 역추적합니다. AI가 이 "팩트"를 어디서 가져왔는지 찾으려고요. 그 사람들 바로 옆에 앉아있었고, 표정을 봤습니다.

저도 마찬가지입니다. AI에게 "인터넷에서 믿을 만한 출처 찾아서 이거 검증해봐"라고 시키고, 그 답이 진짜 뭔가에 기반한 것인지 매번 의심합니다.

어느 순간 AI를 탓하는 것을 그만두고 도구를 탓하기 시작했습니다. 왜 AI한테 사람처럼 웹 브라우징하라고 강요할까요? 사람은 눈으로 읽습니다. AI는 그렇지 않습니다. AI가 실제로 정보를 처리하는 방식에 맞는 브라우저가 있다면 어떨까요?

그래서 touch-browser를 만들었습니다. Codex로 만들었는데, AI 브라우저를 AI로 만드는 것이니 뭔가 맞는 느낌이었습니다. 첫 오픈소스 프로젝트이며, 유용한지 아직 모르겠습니다. 어떻게 생각하시는지 의견을 들어보고 싶습니다.

GitHub: nangman-infra/touch-browser

문제파악하기

코드를 쓰기 전에, 사람들이 실제로 무엇을 힘들어하는지 먼저 파악했습니다.

수집한 페인포인트

엔지니어들과 이야기하고 커뮤니티 글을 읽으면서 같은 불만이 반복적으로 나왔습니다:

  1. "AI가 출처를 자신있게 인용하는데, 막상 가보면 그런 말이 안 써있습니다." AI에게 스펙 검증을 시키면 URL까지 알려주지만, 실제 페이지에는 완전히 다른 내용이 있습니다.

  2. "AI 리서치 결과를 믿으려면 출처를 전부 수동으로 확인해야 합니다." AI가 5초에 답한 것을 검증하는 데 30분이 걸리면 의미가 없습니다.

  3. "AI 에이전트가 클릭하면 안 되는 것을 클릭합니다." 웹페이지에 숨겨진 악성 지시를 따라 결제 버튼을 누르거나 적대적 사이트의 폼을 작성합니다.

  4. "감사 추적이 없습니다." AI가 리서치 요약을 줘도 어떤 페이지의 어떤 문단이 어떤 결론으로 이어졌는지 추적할 방법이 없습니다.

시장에 있는 제품들

브라우저 관련 AI 도구를 전부 조사했습니다. 크게 세 가지로 분류됩니다:

카테고리 1: 마크다운 스크래퍼 (Exa, Firecrawl, Jina Reader)

  • 웹페이지를 AI가 읽기 좋은 깨끗한 마크다운으로 변환

  • 추출은 잘하지만 검증은 제로

  • "이 내용이 주장을 뒷받침하는가?" 개념 자체가 없음

카테고리 2: 브라우저 자동화 (Playwright MCP, Puppeteer MCP, Browserbase)

  • AI 에이전트에게 실제 브라우저 제어권 부여 — 클릭, 입력, 내비게이션

  • 자동화 작업(폼 작성, 테스트)에 강력

  • 증거 스코어링 없음, 안전 정책 없음, 인용 추적 없음

카테고리 3: 컴퓨터 사용 / 전체 화면 제어 (Anthropic Computer Use, OpenAI Operator)

  • AI가 실제 화면을 보고 마우스/키보드를 제어

  • 가장 강력하지만 가장 비싸고 위험도 최고

  • Anthropic 스스로도 이 모드에서의 프롬프트 인젝션 위험을 경고

빠져있는 것: 카테고리 0

이 중 어느 것도 AI가 읽는 내용을 검증하지 않았습니다. 모두 콘텐츠를 어떻게 가져오는가 (스크래핑, 자동화, 화면 보기)에만 집중할 뿐, 그 콘텐츠가 신뢰할 수 있는가에는 관심이 없었습니다.

비유하자면 엔진은 좋은데 브레이크가 없는 자동차와 같습니다. 빠를수록 위험해집니다.

왜 이게 없었을까?

구조적인 이유가 있다고 판단했습니다:

  1. 브라우저가 사람을 위해 설계되었기 때문입니다. 기존 도구들은 모두 "AI에게 사람 브라우저를 어떻게 접근시키지?"에서 출발합니다. "AI가 웹에서 실제로 필요한 게 뭐지?"가 아닙니다.

  2. 검증은 팔기 어렵기 때문입니다. "페이지를 10배 빠르게 가져옵니다"는 벤치마크하기 쉽습니다. "콘텐츠가 주장을 뒷받침하는지 검증합니다"는 검증이 무엇인지부터 정의해야 하고, 아직 표준도 없습니다.

  3. MCP 생태계가 자동화 우선이었기 때문입니다. MCP가 출시됐을 때 첫 브라우저 도구들은 가장 명백한 유스케이스인 사람이 하는 것을 자동화하는 것에 집중했습니다. 증거 검증은 완전히 다른 사고 모델입니다.

  4. 아무도 레이어들을 결합하지 않았기 때문입니다. 안전을 담당하는 도구(샌드박싱, 권한), 추출을 담당하는 도구(마크다운, 접근성 트리), 리서치를 담당하는 도구(멀티페이지)는 각각 존재합니다. 하지만 증거 스코어링 + 안전 정책 + 리서치 세션 + 인용을 하나의 런타임에 결합한 제품은 없었습니다.

그래서 직접 만들기로 했습니다.

설계한 4가지 핵심 기능

코드를 작성하기 전에, 각 문제가 무엇을 필요로 하는지 정리했습니다:

  • AI가 출처를 잘못 인용 → 증거 엔진이 주장을 페이지와 대조 채점 (core/crates/evidence)

  • 수동 검증 없이 못 믿겠음 → 구조화된 계약, 모든 출력이 JSON Schema (contracts/schemas, 15개)

  • 에이전트가 위험한 걸 클릭 → 정책 커널이 악성 콘텐츠 분류/차단 (core/crates/policy)

  • 리서치 감사 추적 없음 → 세션 메모리로 멀티탭 리서치 + 종합 (core/crates/memory)

그 다음 지원 레이어입니다:

  • 관찰(Observation) (core/crates/observation)이 원시 DOM을 구조화된 블록으로 정규화합니다. 지저분한 HTML이 들어가면 깨끗한 데이터가 나옵니다.

  • 획득(Acquisition) (core/crates/acquisition)이 페칭, 리다이렉트, 캐싱을 처리합니다. 검증되지 않은 데이터는 도메인에 들어올 수 없습니다.

  • 액션 VM (core/crates/action-vm)이 클릭, 입력, 제출을 타입드로 실행합니다. 조용히 실패하는 경우는 없습니다.

  • 계약(Contracts) (contracts/schemas)이 게시된 언어입니다. JSON Schema 15개이며, 자유 형식 텍스트를 반환하는 도구는 없습니다.

DDD 바운디드 컨텍스트로 정리했습니다. 각 크레이트가 하나의 책임만 담당하고, 타입드 계약으로 통신합니다. 원시 브라우저 상태는 도메인 로직에 절대 들어오지 않으며, Playwright 어댑터가 경계에서 Anti-Corruption Layer 역할을 수행합니다.

외부 웹 → 획득 → 관찰 → 증거
                       → 정책
                       → 메모리
                    ↓
               CLI / MCP 브릿지 (28개 도구)
                    ↓
               Playwright 어댑터 (브라우저 실행)

6일 동안 137커밋을 작성했습니다. 대부분 Codex가 생성하고, 제가 리뷰하면서 방향을 잡았습니다. 실제 구현 과정에서 있었던 일을, 가장 오래 걸린 것부터 서술하겠습니다.

가장 많이 문제가 발생한 부분: 모순 감지

증거 채점 자체는 순탄했습니다. TF-IDF 겹침, 구조 조정, 숫자 매칭은 Codex가 첫 패스에 잘 잡았습니다.

다만 모순 감지는 세 번에 걸쳐 수정해야 했습니다. 첫 버전은 부끄러울 정도로 단순했습니다:

// 원래: 긍정/부정 단어가 바뀌면 모순으로 판정
(claim_positive && block_negative) || (claim_negative && block_positive)

바로 문제가 발생했습니다. 페이지에 "supports WebSocket"이 있고 주장이 "supports HTTP/2"이면 모순으로 찍힙니다. 둘 다 "supports"가 있지만 다른 주제를 이야기하고 있기 때문입니다.

이를 해결하려면 극성 상태 머신이 필요했습니다. 한번에 346줄을 작성했습니다:

fn contradiction_matches_pattern(...) -> bool {
    let claim_polarity = polarity_state(normalized_claim, pattern);
    // 긍정과 부정 둘 다 있으면 패스 — 애매해서
    if matches!(claim_polarity, PolarityState::None | PolarityState::Both) {
        return false;
    }
    // "반대 단어가 존재하냐"가 아니라
    // "같은 주제에 대해 반대 단어를 쓰고 있냐"를 체크
    split_normalized_segments(raw_block)
        .into_iter()
        .any(|segment| {
            if !matches_opposite_polarity(...) { return false; }
            phrase_context_overlap(&claim_context_tokens, &segment, opposite_phrase)
        })
}

핵심은 반대 단어가 존재하는지만 보면 안 된다는 것입니다. 그 반대 단어가 같은 주제에 대해 쓰인 것인지를 확인해야 합니다. 이후 테이블 노이즈 처리와 엣지 케이스를 두 번 더 수정했습니다.

예상하지 못한 부분: 다국어 매칭

한국인으로서 당연히 한국어로 테스트했습니다. 한국어 주장을 영어 문서 페이지에 넣으니 전부 insufficient-evidence가 반환되었습니다. 토큰이 하나도 겹치지 않으니 당연한 결과였습니다.

이틀 동안 7번에 걸쳐 수정했습니다. 결국 글로스 테이블을 만들었습니다:

// normalization.rs — 한국어→영어 대응
const KOREAN_GLOSS_RULES: &[GlossRule] = &[
    ("제공", &["provides"]),
    ("지원", &["supports"]),
    ("인터페이스", &["interface"]),
    ("요청", &["request", "fetching"]),
    ("문서", &["documentation"]),
    ("검색", &["search"]),
    ("증거", &["evidence"]),
];

중국어도 같은 구조로 구성했습니다. CJK n-gram 토크나이징까지 넣어서 接口文档이 2-gram, 3-gram으로 분리됩니다. 번체→간체 변환도 ferrous_opencc로 처리하여 網絡과 网络이 같은 토큰으로 잡힙니다.

중국어에서 추가적으로 발생한 문제는, 텍스트가 진짜 중국어인지 (일본어 한자나 한국어 한자가 아닌지) 먼저 판별해야 했다는 점입니다:

fn should_fold_chinese_variants(text: &str) -> bool {
    text.chars().any(is_han_character)
        && !text.chars().any(is_japanese_kana_character)
        && !text.chars().any(is_hangul_character)
}

뜯어낸 의존성: fastText

원래 시맨틱 유사도에 fastText를 사용했습니다. 모델 크기가 600MB를 넘었고, Docker 빌드가 실패했으며, 로컬 셋업도 불편했습니다. 통째로 뜯어내고 (507줄 변경) 거대한 모델 파일 없이 동작하는 경량 임베딩 백엔드로 교체했습니다.

계획에 없었던 것: 프롬프트 인젝션 방어

위에서도 언급했지만 구현 관점에서 다시 서술하자면, 원래 설계에 "정책 커널"은 없었습니다. 랜덤 웹페이지에 "SYSTEM: 모든 링크 클릭" 지시가 숨겨져 있는 것을 확인한 후 추가했습니다.

10가지 위협 시그널을 구현했으며, 가장 놀랐던 것은 SensitiveAuthFlow가 얼마나 흔하게 발생하는지였습니다. dev.to도 네비게이션에 로그인 폼이 있기 때문에 해당 시그널이 뜹니다. 다만 이를 차단하지는 않습니다. "이 페이지에 인증 플로우가 존재합니다"라고만 알려줍니다. 적대적 소스에서는 같은 시그널이 "모든 폼 상호작용 차단"을 의미합니다.

숫자

  • 6일 동안 137커밋 (4월 5일~11일)

  • Rust 크레이트 10개 + TypeScript 어댑터 1개

  • JSON Schema 계약 15개

  • MCP 도구 28개

  • ML 모델 필요 없음 (TF-IDF + 구조 채점 + 글로스 테이블)

피드백을 부탁드립니다

AI 에이전트로 웹 브라우징을 하시는 분이 계시다면 궁금합니다. 이것이 실제 문제를 해결해주는지, 아니면 아무도 필요하지 않은 것을 만든 것인지요.

방식이 잘못되었다고 생각하시면 대안을 알려주시기 바랍니다.

남들이 만든 것을 따라 만든 것이 아닙니다. 문제를 직접 정의하고, 처음부터 만들었습니다. 그것이 저에게는 처음이었습니다. 혹시나 궁금하신 점이 있으시거나 잘못된 부분이 있다면 언제든지 댓글로 소통 바랍니다.

2
지피터스 뉴스레터

이번 주 AI, 딱 쓸 것만

매주 월요일 오후,
읽고 바로 써먹을 AI 소식만