Claude Platform Docs
Messages도구

SDK 도구 세트를 사용한 브라우저 사용

Python 또는 TypeScript SDK에서 브라우저 사용 도구를 실행합니다. SDK는 루프와 사용자가 구성한 URL, 파일, 승인 검사를 실행하며, 브라우저는 사용자가 직접 제공합니다.

Python 및 TypeScript SDK에는 브라우저 사용 도구를 위한 클래스가 포함되어 있습니다. 이 클래스를 서브클래싱하고, navigate나 left_click 같은 "member tool"(멤버 도구)마다 하나의 메서드를 자체 브라우저 자동화에 맞춰 작성합니다. SDK는 각 호출을 라우팅하고, URL과 파일 경로를 검사하고, 승인 콜백에 확인을 요청하고, 각 tool_result를 빌드합니다.

SDK에는 브라우저, 기성 "driver"(드라이버), "denylist"(차단 목록)가 포함되어 있지 않습니다. Python 및 TypeScript로 작성된 Playwright와 Chrome DevTools Protocol용 예제 드라이버는 claude-quickstarts 저장소의 browser-toolset 폴더에 있습니다.

빠른 시작

드라이버는 BetaAbstractBrowserToolset20260801의 서브클래스입니다. 이 드라이버는 navigate, screenshot, left_click과 함께, 모든 드라이버에 필요한 상태 보고인 _browser_state(TypeScript에서는 browserState)를 구현합니다. 예제에서 backend는 Playwright 같은 브라우저 자동화 라이브러리를 감싸는 자체 래퍼를 나타냅니다.

from anthropic import Anthropic
from anthropic.tools.browser import (
    BetaAbstractBrowserToolset20260801,
    BetaBrowserNavigateResult,
    BetaBrowserScreenshotResult,
    BrowserState,
    ToolsetCallContext,
)
from anthropic.types.beta import (
    BetaBrowserLeftClickInput,
    BetaBrowserNavigateInput,
    BetaBrowserScreenshotInput,
    BetaBrowserStateTabEntryParam,
)


class MyBrowser(BetaAbstractBrowserToolset20260801):
    def __init__(self, backend, **options):
        super().__init__(**options)
        self.backend = backend

    def _browser_state(self, context: ToolsetCallContext) -> BrowserState:
        return BrowserState(
            tabs=[
                BetaBrowserStateTabEntryParam(
                    tab_id=tab.id,
                    title=tab.title,
                    url=tab.url,
                    active=tab.id == self.backend.active,
                )
                for tab in self.backend.tabs()
            ],
            state_changes=self.backend.drain_changes(),
        )

    def navigate(
        self, context: ToolsetCallContext, input: BetaBrowserNavigateInput
    ) -> BetaBrowserNavigateResult:
        # input.url은 URL 정책을 통과한 URL이거나 "back", "forward",
        # 또는 "reload"입니다. Claude가 스킴을 생략하면 SDK가 https://를 추가합니다.
        page = self.backend.goto(input.url, input.tab_id)
        return BetaBrowserNavigateResult(
            url=page.url, status=page.status, title=page.title
        )

    def screenshot(
        self, context: ToolsetCallContext, input: BetaBrowserScreenshotInput
    ) -> BetaBrowserScreenshotResult:
        data = self.backend.png_base64(input.tab_id)
        return BetaBrowserScreenshotResult(data=data, media_type="image/png")

    def left_click(
        self, context: ToolsetCallContext, input: BetaBrowserLeftClickInput
    ) -> None:
        # 반환할 값이 없습니다. Claude는 "Clicked."를 읽습니다.
        self.backend.click(input.target, input.tab_id)

    def close(self) -> None:
        super().close()  # first, so no call is still using the browser when it closes
        if not self.backend.closed:
            self.backend.close()


client = Anthropic()
with MyBrowser(backend, allowed_domains=["example.com", "iana.org"]) as browser:
    runner = client.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=1024,
        tools=[browser],
        messages=[
            {
                "role": "user",
                "content": "Open example.com and tell me the page heading.",
            }
        ],
    )
    for message in runner:
        print(message)

드라이버 인스턴스 자체를 tools 항목으로 전달하세요. 구현하지 않은 멤버는 비활성화된 상태로 API에 전송됩니다. 그래도 Claude가 이를 호출하면 SDK가 오류를 반환하고 실행은 계속됩니다. execute를 재정의하면 비활성화 상태로 전송되는 멤버가 달라집니다(전후 훅 추가). 러너는 도구 세트를 닫지 않으므로 하나의 인스턴스로 여러 실행을 처리할 수 있습니다. 작업이 끝나면 닫으세요.

드라이버 사용자 지정

멤버 활성화 또는 비활성화

configs는 도구 세트 구성에 설명된 멤버별 설정을 받습니다. 변경하는 멤버만 나열하세요:

# read_console도 구현하는 MyBrowser
browser = MyBrowser(
    backend, configs={"read_console": {"enabled": True}, "navigate": {"enabled": False}}
)

SDK는 코드가 실행되기 전에 비활성화된 멤버에 대한 호출을 거부합니다. 클래스가 execute를 재정의하지 않는 한, 클래스가 구현하지 않은 멤버를 활성화하는 것은 구성 오류입니다.

전후 훅 추가

execute를 재정의하고 부모의 execute를 호출하세요. 해당 호출 이전의 코드는 URL 검사(URL 정책 설정)와 confirm(영향이 큰 멤버에 승인 요구) 이후에 실행되며, 입력을 변경할 수 있습니다. SDK는 변경된 입력을 다시 검사하지 않습니다. 호출 이후의 코드는 결과를 받으며, 결과를 변경할 수 있습니다. 호출을 거부하려면 ToolError를 raise하세요(TypeScript에서는 throw).

execute를 재정의하면 Claude에게 제공되는 멤버가 달라집니다. SDK는 모든 멤버를 구현된 것으로 간주하므로, 기본적으로 켜져 있는 모든 멤버가 Claude에게 제공됩니다. 빠른 시작의 MyBrowser는 세 개의 멤버를 처리하므로, 다음 TracedBrowser는 처리할 수 없는 멤버까지 Claude에게 제공합니다. 사용하기 전에 configs로 해당 멤버를 끄세요.

import time


class TracedBrowser(MyBrowser):
    def execute(self, context, name, input):
        started = time.monotonic()
        result = super().execute(context, name, input)
        elapsed_ms = (time.monotonic() - started) * 1000
        call_id = context.tool_use.id if context.tool_use else "-"
        log.info("%s %s %.0fms", call_id, name, elapsed_ms)
        return redact(result) if name == "get_page_text" else result

드라이버 구현

브라우저가 지원하는 멤버를 재정의하세요. 각 멤버는 호출 컨텍스트와 멤버의 입력을 BetaBrowserNavigateInput 같은 타입이 지정된 객체로 받습니다. 입력 타입은 anthropic.types.beta(TypeScript에서는 @anthropic-ai/sdk/resources/beta)에서 가져옵니다. 멤버 도구에 각 입력의 필드가 나열되어 있습니다.

TypeScript에서는 SDK가 프로토타입에서 멤버를 찾으므로, 멤버를 화살표 함수 필드가 아닌 메서드로 작성하세요. TypeScript에서는 type 멤버를 type_로 표기합니다. Python에서는 type입니다.

결과 반환

멤버가 반환하는 내용에 따라 Claude가 읽는 내용이 결정됩니다. 성공한 결과는 상태 보고로부터 빌드된 browser_state 블록으로 끝납니다. 오류 결과에는 블록이 포함되지 않습니다.

멤버반환 값Claude가 읽는 내용
screenshot, zoomBetaBrowserScreenshotResult이미지 블록 하나
navigateBetaBrowserNavigateResultNavigated to {url} — {title} (HTTP {status})
new_tab, switch_tab, list_tabs, close_tab탭 항목(new_tab, switch_tab), 탭 항목 목록(list_tabs), 또는 없음(close_tab)browser_state 블록만
read_page, get_page_text, find, read_console, read_network, javascript_exec문자열해당 문자열
그 외 모든 멤버없음, 또는 한 줄의 텍스트Clicked. 같은 짧은 확인 메시지와, 그 뒤에 별도의 텍스트 블록으로 표시되는 반환된 줄

브라우저 상태 보고

SDK는 거부되거나 실패한 호출을 포함하여 모든 호출 후에 _browser_state(TypeScript에서는 browserState 옵션)를 호출합니다. 열려 있는 모든 탭과 마지막 보고 이후 변경된 사항을 반환하세요:

  • 열린 탭과 다운로드 이벤트.
  • 요청 훅이 차단한 각 탐색에 대한 NavigationRefused.
  • 드라이버가 닫은 각 네이티브 대화 상자에 대한 DialogDismissed.

이 모든 항목을 state_changes에 넣으세요. Python에서 NavigationRefused(url=...)와 DialogDismissed(kind=..., message=...)는 anthropic.tools.browser에서 가져옵니다. TypeScript에서는 { type: "navigation_refused", url }와 { type: "dialog_dismissed", kind, message }입니다.

마지막 두 항목은 API 상태 변경이 아닙니다. SDK는 이를 browser_state 블록 외부의 텍스트로 Claude에게 보고합니다. 거부된 탐색은 모두 합쳐 한 줄로 보고하되 URL은 밝히지 않고, 닫힌 대화 상자는 처음 세 개에 대해 각각 한 줄씩 보고한 다음 나머지는 개수로 보고합니다.

탭이 하나라도 열려 있으면 정확히 하나의 탭이 활성 상태여야 합니다. tab_id를 받는 모든 멤버는 지정된 탭에서 동작해야 합니다. SDK는 보고를 사용하여 결과가 어느 페이지에서 왔는지 판단합니다. 보고에 대한 API의 제한은 browser_state로 탭 추적하기에 나열되어 있습니다.

오류 처리

멤버 또는 SDK가 발생시킨 항목Claude가 읽는 내용실행
ToolError오류 결과로 전달되는 해당 메시지계속됨
기타 모든 예외오류 결과로 전달되는 해당 텍스트계속됨
구성 실수, 호출 중 SDK 오용, 또는 close 이후의 호출에 대한 ToolsetUsageError없음중지됨

Claude가 멤버의 오류 텍스트, left_click 같은 작업이 반환하는 줄, 실패한 다운로드의 오류, 또는 닫힌 대화 상자의 메시지를 읽기 전에, SDK는 정책이 거부하는 각 URL을 (blocked)로 대체합니다. 파일 정책이 노출하지 않는 각 로컬 경로는 (path hidden)으로 대체합니다. 이 검사는 일부 URL과 경로를 놓칠 수 있습니다. URL 정책, 파일 정책 또는 confirm 호출 가능 객체에서 발생한 ToolError는 작성된 그대로 Claude에게 전달되므로, 해당 텍스트에 거부된 URL과 로컬 경로를 포함하지 마세요. 멤버에서 예외를 catch하고, 직접 작성한 텍스트로 ToolError를 raise하세요(TypeScript에서는 throw).

도구 러너 없이 실행

인스턴스를 tools에 전달하고(TypeScript에서는 browser.toJSON()), 각 멤버 호출에 tool_result(TypeScript에서는 toolResult)로 응답하세요. 브라우저 사용 도구는 첫 번째로 실패한 호출에서 중지하도록 요구합니다(배치 동작). 실패한 호출 이후, 이 루프는 해당 턴의 이후 호출을 실행하지 않고 응답합니다:

from anthropic.types.beta import BetaToolResultBlockParam

NOT_EXECUTED = "Not executed: an earlier action in this turn failed."
MAX_TURNS = 10

with MyBrowser(backend, allowed_domains=["example.com"]) as browser:
    messages = [{"role": "user", "content": "Open example.com"}]
    for _ in range(MAX_TURNS):
        response = client.beta.messages.create(
            model="claude-opus-5-5",
            max_tokens=1024,
            tools=[browser],
            messages=messages,
        )
        messages.append({"role": "assistant", "content": response.content})
        calls = [
            block
            for block in response.content
            if block.type == "tool_use" and block.toolset_name == browser.toolset_name
        ]
        if not calls:
            break
        results: list[BetaToolResultBlockParam] = []
        failed = False
        for call in calls:
            if failed:
                # 호출이 실패하면 해당 턴의 나머지 호출은 실행되지 않고 응답만 반환됩니다.
                results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": call.id,
                        "toolset_name": call.toolset_name,
                        "content": NOT_EXECUTED,
                        "is_error": True,
                    }
                )
                continue
            result = browser.tool_result(call)
            failed = bool(result.get("is_error"))
            results.append(result)
        messages.append({"role": "user", "content": results})

건너뛴 호출에 대한 각 응답에는 is_error, 호출의 toolset_name, 그리고 배치 동작에서 요구하는 정확한 텍스트가 포함됩니다. 도구 러너도 동일한 응답을 보냅니다.

도구 세트를 안전하게 실행

Claude의 다음 작업은 Claude가 읽는 페이지에 따라 달라집니다. 페이지 또는 페이지에 주입된 텍스트는 내부 서비스에 접근하거나 호스트에서 파일을 빼내려고 시도할 수 있습니다. 또한 실제 영향을 미치는 작업을 유발하려고 시도할 수도 있습니다. 일회용 브라우저가 아닌 다른 대상에 대해 드라이버를 실행하기 전에 다음 여섯 단계를 수행하세요:

  1. URL 정책 설정: allowed_domains, blocked_domains, 또는 자체 url_policy.
  2. 드라이버에서 요청을 가로채고 도구 세트에 판정을 요청하세요.
  3. 컨테이너에 egress 정책을 적용하여 드라이버가 볼 수 없는 것을 네트워크가 차단하도록 하세요.
  4. 업로드와 다운로드를 제한하거나, 업로드를 꺼 두세요.
  5. confirm으로 영향이 큰 멤버에 승인을 요구하세요.
  6. 각 세션마다 전용 컨테이너 또는 VM에서 브라우저 호스트를 격리하세요.

SDK는 구성한 대로 1, 4, 5단계를 적용합니다. 2, 3, 6단계는 드라이버와 배포 환경에 달려 있습니다. 보안 고려 사항의 주의 사항도 적용됩니다.

URL 정책 설정

allowed_domains(TypeScript에서는 allowedDomains)를 작업에 필요한 사이트로 설정하세요. 두 목록의 항목은 도메인(하위 도메인도 포함), IP 주소 또는 CIDR 네트워크입니다. allowed_domains가 설정되면 도구 세트는 다른 모든 호스트를 거부합니다.

browser = MyBrowser(backend, allowed_domains=["example.com", "iana.org"])

작업에 개방형 웹이 필요하다면 대신 blocked_domains(blockedDomains)를 설정하세요. 목록은 호스트 이름을 확인(resolve)하지 않고 비교합니다. 127.0.0.0/8이라는 차단 목록 항목은 localhost를 차단하지 않으므로, 네트워크뿐만 아니라 호스트 이름도 지정하세요. 두 목록을 모두 설정하면 blocked_domains가 우선합니다.

차단 목록은 사설 주소로 확인되는 공개 이름을 잡아낼 수 없습니다. 따라서 blocked_domains를 사용할 때는 컨테이너 egress 정책이 그러한 이름이 사설 주소에 도달하는 것을 막아 줍니다.

# 브라우저는 접근할 수 있지만 Claude는 접근해서는 안 되는 호스트와 네트워크를 나열하세요:
# IPv4 및 IPv6의 루프백, 링크 로컬, 사설 대역, 0.0.0.0/8, 사용 중인
# 클라우드의 메타데이터 주소 및 이름(예: metadata.google.internal),
# localhost, 그리고 내부 호스트 이름입니다.
browser = MyBrowser(backend, blocked_domains=internal_networks)

url_policy(urlPolicy)는 두 목록을 모두 대체하며, 어느 한 목록과 함께 전달하면 구성 오류가 됩니다. 정책은 URL을 허용하려면 아무것도 반환하지 않고, 거부하려면 ToolError를 raise(throw)합니다. 기본 규칙을 유지하려면 default_url_policy(defaultURLPolicy)로 기본 정책을 빌드하고, 자체 정책에서 이를 먼저 호출하세요:

from urllib.parse import urlsplit

from anthropic.tools.browser import ToolError, URLContext, default_url_policy

listed = default_url_policy(allowed_domains=["example.com"])


def url_policy(context: URLContext, url: str) -> None:
    listed(context, url)  # the default rules first
    if context.phase == "request" and urlsplit(url).scheme != "https":
        raise ToolError("Only https navigation is allowed.")


browser = MyBrowser(backend, url_policy=url_policy)

정책은 코드보다 먼저 모든 navigate URL에 대해 실행되고, 결과가 보고하는 URL과 상태 보고의 모든 탭 및 다운로드 URL에 대해서도 실행됩니다. 결과와 상태 보고에서는 원격 호스트를 지정하지 않는 주소, 즉 빈 탭, about:blank, 브라우저의 chrome-error: 페이지, data: 문서를 건너뜁니다.

어떤 정책에서든 도구 세트는 about:blank를 제외하고 http 또는 https 이외의 스킴으로의 navigate를 거부합니다. url_policy="allow_all"(urlPolicy: "allow_all")은 정책을 끄지만, 이 스킴 규칙은 끄지 않습니다.

url_policy=None(urlPolicy: null)을 전달하면 생성자가 구성 오류를 발생시키므로, 구성에서 읽어 온 None(null)으로 검사를 끌 수 없습니다. TypeScript에서 undefined는 옵션을 전달하지 않은 것과 마찬가지로 옵션을 설정되지 않은 상태로 둡니다.

페이지가 거부된 주소에 도달하면 Claude는 해당 콘텐츠가 보류되었다는 내용을 읽게 되며, 탭은 (blocked)로 표시됩니다. 탭이 다시 허용된 주소로 이동할 때까지 도구 세트는 해당 탭에 대한 호출을 거부합니다. 예외는 navigate("reload"는 제외), new_tab, list_tabs, switch_tab, close_tab입니다.

드라이버에서 요청 가로채기

URL 정책은 도구 세트가 보는 주소, 즉 탐색, 결과, 상태 보고만 판단합니다. 하위 리소스, fetch() 호출, WebSocket, 또는 호스트 이름이 어디로 확인되는지는 보지 못합니다.

도구 세트는 호출이 끝날 때에만 호출의 결과나 상태 보고에서 새 주소를 감지합니다. 따라서 그 전까지는 호출이 거부된 페이지에서 여전히 동작할 수 있으며, SDK는 기껏해야 해당 호출의 결과를 보류할 수 있을 뿐입니다. 드라이버가 리디렉션 홉을 포함한 모든 요청을 판단하지 않는 한, 거부된 주소로 리디렉션되는 페이지는 여전히 로드됩니다.

드라이버의 "request hook"(요청 훅, 자동화 라이브러리가 각 요청 전에 호출하는 함수)에서 check_url(checkURL)을 호출하세요. 이 함수는 도구 세트 자체의 정책을 적용하므로 규칙을 두 번 작성할 필요가 없습니다:

from anthropic.tools.browser import URLContext


class MyBrowser(BetaAbstractBrowserToolset20260801):
    ...

    # context.route("**/*", self._guard)로 Playwright 브라우저 컨텍스트에 등록됩니다
    def _guard(self, route):
        url_context = URLContext(
            member="navigate", phase="request", tab_id=self.backend.active
        )
        if not self.check_url(url_context, route.request.url).allowed:
            return route.abort("blockedbyclient")
        route.continue_()

check_url은 navigate와 동일한 스킴 규칙과 정책을 적용합니다. 이와 같은 요청 훅은 WebSocket 핸드셰이크, 서비스 워커 요청, 리디렉션 홉을 보지 못합니다. 서비스 워커를 차단하세요. WebSocket 핸드셰이크와 리디렉션 홉은 이를 볼 수 있는 훅으로 판단하세요.

요청 단계에서 check_url은 http, https, about:blank URL만 허용합니다. 따라서 WebSocket의 경우 호출하기 전에 ws://를 http://로, wss://를 https://로 변경하세요.

컨테이너에 egress 정책 적용

가로채기로는 브라우저가 보내는 모든 요청을 볼 수 없으며, URL 정책은 이름이 어디로 확인되는지 보지 못합니다. 컨테이너의 네트워크가 적용하는 "egress policy"(송신 정책)는 두 가지를 모두 처리합니다:

  • IPv4와 IPv6의 루프백, 링크 로컬, 사설 주소 범위와 0.0.0.0/8을 차단하세요. 여기에는 클라우드 메타데이터 주소 169.254.169.254가 포함됩니다.
  • 작업에 필요한 호스트로의 아웃바운드 연결만 허용하세요. 규칙이 IP 주소를 기준으로 일치시킨다면, 컨테이너가 시작될 때 허용하는 호스트 이름을 확인(resolve)하세요.
  • DNS는 컨테이너의 리졸버로만 허용하세요.
  • 드라이버가 로컬 DevTools 포트를 통해 브라우저에 접근한다면, 해당 포트에서만 루프백을 허용하세요. 루프백 전체에 대한 규칙은 모든 로컬 서비스를 페이지에 노출하게 됩니다.

업로드 및 다운로드 제한

file_upload는 기본적으로 꺼져 있습니다. file_policy가 없으면 SDK는 경로나 문서 ID를 지정하는 모든 업로드를 거부합니다. 업로드를 활성화하려면 작업의 파일만 담고 있는 업로드 디렉터리 하나와 함께 LocalFilePolicy(TypeScript에서는 NodeFilePolicy)를 전달하세요:

from anthropic.tools.browser import LocalFilePolicy

# file_upload도 구현하는 MyBrowser입니다
browser = MyBrowser(
    backend,
    configs={"file_upload": {"enabled": True}},
    confirm=make_confirm(),  # required for file_upload; see Gate consequential members
    file_policy=LocalFilePolicy(
        upload_roots=["/task/uploads"],
        download_dir="/task/downloads",
        expose_download_paths=False,
    ),
)

SDK는 심볼릭 링크를 따라가며 각 업로드 경로를 확인하고, 업로드 루트 외부의 모든 경로를 거부합니다. 파일 정책은 업로드 루트 내부에 있는 다운로드 디렉터리를 거부합니다. 다운로드 경로는 expose_download_paths(exposeDownloadPaths)가 true이고 파일이 다운로드 디렉터리 내부에 있을 때에만 Claude에게 전달됩니다.

기본 제공되는 경로 검사는 SDK를 실행하는 프로세스의 파일 시스템에서 경로를 확인합니다. 따라서 해당 파일 시스템을 공유하는 브라우저만 보호합니다. 원격 브라우저의 경우 대신 원격 및 호스팅 브라우저를 따르세요.

다운로드는 다음과 같이 설정하세요:

  • 다운로드 디렉터리를 모드 0700으로 직접 생성하고, noexec,nosuid,nodev로 마운트하세요.
  • 셸이나 파일 도구처럼 Claude가 호출할 수 있는 다른 도구가 디렉터리에 접근할 수 없도록 하세요.
  • download_failed 상태 변경에서 error는 고정된 문구로 작성하세요. 예외의 텍스트에는 경로나 URL이 포함될 수 있습니다.
  • 사람이 결정하기 전까지는 다운로드한 파일을 대화로 읽어 들이거나 실행하지 마세요.

영향이 큰 멤버에 승인 요구

javascript_exec와 file_upload는 기본적으로 꺼져 있습니다. confirm 호출 가능 객체 없이 둘 중 하나를 활성화하면 생성자가 구성 오류를 발생시킵니다. confirm 호출 가능 객체가 있으면 SDK는 실행될 모든 호출 전에 이를 호출합니다. 없으면 아무것도 묻지 않습니다.

호출을 실행하려면 True를, 거부하려면 False를 반환하세요(TypeScript에서는 true와 false). 호출 가능 객체가 사람에게 묻는 경우, 멤버, 페이지의 URL, 호출의 입력을 보여 주세요. 입력에는 페이지의 텍스트가 포함될 수 있으므로, 먼저 입력에서 출력 가능한 ASCII 범위를 벗어나는 모든 문자를 이스케이프하세요.

이 예제는 자체 ask_user 함수(TypeScript에서는 askUser)를 통해 승인이 필요한 두 멤버에 대해 묻고, 나머지는 승인합니다:

import json
from collections.abc import Callable

from anthropic.tools.browser import ConfirmContext

GATED = {"javascript_exec", "file_upload"}


def shown(context: ConfirmContext) -> str:
    """The call's input as JSON, with every character outside printable
    ASCII escaped."""
    return json.dumps(context.input.to_dict(), ensure_ascii=True, indent=2)


def make_confirm() -> Callable[[ConfirmContext], bool]:
    granted: set[tuple[str, str, str]] = set()

    def confirm(context: ConfirmContext) -> bool:
        name = context.member
        if name not in GATED:
            return True
        detail = shown(context)
        page = context.tab_url
        origin = context.origin
        if page is None or origin is None or origin.startswith("chrome-error:"):
            # origin이 없거나 오류 페이지인 경우: 매번 확인을 요청합니다.
            return ask_user(
                f"Allow {name} on {page or 'a page with no origin'}?\n{detail}"
            )
        # 승인은 이 페이지의 정확히 이 입력에만 적용됩니다.
        key = (name, page, detail)
        if key not in granted and ask_user(f"Allow {name} on {page}?\n{detail}"):
            granted.add(key)
        return key in granted

    return confirm


# javascript_exec 및 file_upload도 구현하는 MyBrowser
browser = MyBrowser(
    backend,
    configs={"javascript_exec": {"enabled": True}, "file_upload": {"enabled": True}},
    confirm=make_confirm(),
)

make_confirm()(TypeScript에서는 makeConfirm())을 호출할 때마다 승인 내역이 없는 호출 가능 객체가 반환됩니다. 도구 세트마다 한 번씩 호출하고, 각 사용자에게 별도의 도구 세트를 제공하세요.

승인은 마지막 상태 보고에 나타난 페이지를 대상으로 하며, 호출이 실행되기 전에 페이지가 변경될 수 있습니다. 구매, 메시지 전송, 약관 동의는 left_click과 type 같은 일반 멤버를 통해 이루어지므로, confirm은 이름으로 이를 구별할 수 없습니다. 사람이 이를 승인하도록 하려면 해당 멤버에 대해서도 물어보세요.

요청을 가로채지 않는 드라이버에서는 javascript_exec를 활성화하지 마세요. 거부된 페이지에서 실행되는 스크립트는 해당 콘텐츠를 나중에 읽기 작업이 반환하게 될 위치로 복사할 수 있습니다.

브라우저 호스트 격리

각 세션마다 최소 권한의 전용 컨테이너 또는 VM에서 브라우저를 실행하세요:

  • root가 아닌 사용자로 실행하고, 브라우저가 허용하는 경우 읽기 전용 루트 파일 시스템을 사용하세요.
  • 구성한 업로드 및 다운로드 디렉터리(있는 경우) 외에는 호스트에서 아무것도 마운트하지 마세요.
  • 자격 증명을 환경에 두지 말고, 새 브라우저 프로필로 시작하세요.
  • Claude가 호출할 수 있는 다른 도구와 파일 시스템을 공유하지 마세요.

API를 호출하는 코드는 API 키와 대화를 보유하므로 브라우저의 컨테이너 외부에서 실행하세요. 도구 러너와 tool_result는 모두 해당 코드의 프로세스에서 도구 세트를 실행하므로, 브라우저는 도구 세트의 파일 시스템을 공유하지 않습니다. 브라우저를 원격으로 취급하세요. 원격 및 호스팅 브라우저가 적용됩니다.

페이지 텍스트, 스크린샷, 콘솔 및 네트워크 항목, 탭 제목, 다운로드 이름을 포함하여 페이지가 반환하는 모든 것을 신뢰할 수 없는 것으로 취급하세요.

원격 및 호스팅 브라우저

일부 브라우저는 SDK를 실행하는 프로세스와 파일 시스템을 공유하지 않습니다. 다른 컨테이너에 있는 브라우저, DevTools URL로 접근하는 브라우저, 호스팅 브라우저 서비스의 브라우저가 그 예입니다. 이러한 경우에도 URL 정책, 요청 가로채기, confirm은 여전히 사용자의 프로세스에서 실행됩니다.

호스팅 브라우저의 경우 제공업체가 egress와 호스트 격리를 제어합니다. 자체 egress 정책은 그곳에 적용되지 않으므로, 드라이버의 요청 훅이 브라우저 요청에 대한 유일한 검사 수단입니다. 브라우저의 네트워크가 어디에 접근할 수 있는지 확인하세요.

기본 제공되는 경로 검사는 원격 브라우저를 보호하지 않습니다. LocalFilePolicy(TypeScript에서는 NodeFilePolicy)는 SDK를 실행하는 프로세스의 파일 시스템에서 경로를 검사하지만, 브라우저는 자체 파일 시스템을 읽고 씁니다.

SDK는 브라우저가 원격인지 감지할 수 없습니다. 따라서 원격 브라우저의 경우, 드라이버가 자체 FilePolicy로 브라우저가 실행되는 곳에서 업로드 경로를 검사하지 않는 한 file_upload를 꺼 두세요. FilePolicy는 각 업로드의 경로와 문서 ID를 검증하고, Claude가 다운로드 경로를 볼 수 있는지 결정합니다.

원격 브라우저의 호스트에 업로드 및 다운로드 제한의 다운로드 설정이 되어 있지 않다면, 원격 브라우저가 다운로드를 거부하도록 하세요.

예제 드라이버는 이 규칙을 따릅니다. 원격 브라우저에서는 file_policy(filePolicy)나 활성화된 file_upload에 대해 구성 오류를 발생시키고, 브라우저가 다운로드를 거부하도록 설정합니다.

제공업체의 API 키와, 키가 포함될 수 있는 세션의 연결 URL이 로그, 도구 결과, 오류 텍스트에 포함되지 않도록 하세요. 제공업체가 세션을 녹화하는 경우, 녹화본은 Claude가 보고 입력한 모든 것의 또 다른 사본이며, 제공업체의 보존 약관이 적용됩니다.

참조

생성자 옵션은 두 SDK에서 동일한 의미를 갖습니다:

PythonTypeScript설정 대상
configsconfigs활성화되는 멤버
confirmconfirm각 호출을 승인하거나 거부하는 호출 가능 객체
allowed_domains, blocked_domainsallowedDomains, blockedDomains기본 URL 정책의 목록
url_policyurlPolicy자체 URL 정책
file_policyfilePolicy업로드 루트 및 다운로드 경로 노출
tool_configstoolConfigscache_control 같은 tools 항목의 필드
_browser_state 메서드browserState상태 보고

생성 후에는 옵션을 변경할 수 없습니다. 기본값, 오류, 컨텍스트 필드, 비동기 Python 클래스(BetaAsyncAbstractBrowserToolset20260801)는 Python SDK와 TypeScript SDK에 문서화되어 있습니다.

제한 사항

  • URL 정책은 모든 요청이 아닌 탐색을 검사합니다: 드라이버에서 요청 가로채기를 참조하세요.
  • 승인은 마지막 상태 보고를 기반으로 합니다: 해당 보고 이후 페이지가 변경될 수 있습니다. SDK는 호출이 실행되기 전에 페이지를 다시 검사하지 않습니다.
  • 하나의 도구 세트에 대한 호출은 한 번에 하나씩 실행됩니다: 이 동작은 끌 수 없습니다.
  • SDK는 탭 ID가 고유한지, 하나의 탭이 활성 상태인지, 탭이 몇 개인지 검사하지 않습니다: API는 이러한 규칙을 위반하는 보고를 거부합니다.

다음 단계

멤버 도구, browser_state 블록, 그리고 도구의 보안 고려 사항.

SDK가 루프를 실행하는 방식과 SDK가 보내는 메시지를 변경하는 방법.

신뢰할 수 없는 콘텐츠를 읽는 모든 애플리케이션을 위한 가드레일.

Was this page helpful?