샌드박스

샌드박스 (Sandboxes)

샌드박스 백엔드로 격리된 환경에서 코드를 실행하세요.

에이전트는 코드를 생성하고, 파일 시스템과 상호작용하고, 셸 명령을 실행합니다. 에이전트가 무엇을 할지 예측할 수 없기 때문에, 그것의 환경이 격리되어 자격 증명, 파일, 네트워크에 접근할 수 없도록 하는 것이 중요합니다. 샌드박스는 에이전트의 실행 환경과 호스트 시스템 사이에 경계를 만들어 이 격리를 제공합니다.

Deep Agents에서 **샌드박스는 에이전트가 작동하는 환경을 정의하는 백엔드**입니다. 파일 연산만 노출하는 다른 백엔드(State, Filesystem, Store)와 달리, 샌드박스 백엔드는 에이전트에게 셸 명령을 실행하기 위한 execute 도구도 제공합니다. 샌드박스 백엔드를 구성하면 에이전트는 다음을 얻습니다:

  • 모든 표준 파일 시스템 도구(ls, read_file, write_file, edit_file, glob, grep)

  • 샌드박스에서 임의의 셸 명령을 실행하기 위한 execute 도구

  • 호스트 시스템을 보호하는 안전한 경계

graph LR
    subgraph Agent
        LLM --> Tools
        Tools --> LLM
    end

    Agent <-- backend protocol --> Sandbox

    subgraph Sandbox
        Filesystem
        Bash
        Dependencies
    end

    classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
    classDef output fill:#EBD0F0,stroke:#885270,stroke-width:2px,color:#441E33

    class LLM,Tools process
    class Filesystem,Bash,Dependencies output

샌드박스를 왜 사용하나요? (Why use sandboxes?)

샌드박스는 보안을 위해 사용됩니다. 에이전트가 자격 증명, 로컬 파일, 호스트 시스템을 손상시키지 않고 임의의 코드를 실행하고, 파일에 접근하고, 네트워크를 사용할 수 있게 해줍니다. 이 격리는 에이전트가 자율적으로 실행될 때 필수입니다.

샌드박스는 특히 다음에 유용합니다:

  • 코딩 에이전트: 자율적으로 실행되는 에이전트가 셸, git을 사용하고, 저장소를 클론하며(많은 프로바이더가 네이티브 git API를 제공합니다, 예: Daytona의 git 연산), 빌드 및 테스트 파이프라인을 위해 Docker-in-Docker를 실행할 수 있습니다
  • 데이터 분석 에이전트: 파일을 로드하고, 데이터 분석 라이브러리(pandas, numpy 등)를 설치하고, 통계 계산을 실행하고, PowerPoint 프레젠테이션 같은 출력물을 안전한 격리 환경에서 만들 수 있습니다
**Deep Agents Code를 쓰고 있나요?** Deep Agents Code는 `--sandbox` 플래그를 통해 샌드박스 지원이 내장되어 있습니다. Deep Agents Code 전용 설정, 플래그(`--sandbox-id`, `--sandbox-setup`), 예시는 [원격 샌드박스 사용](/oss/deepagents/code/remote-sandboxes)을 참조하세요. **LangSmith 샌드박스를 찾고 있다면:** LangSmith는 제3자 계정 없이 LangSmith UI나 SDK에서 직접 사용할 수 있는 일급 관리 샌드박스를 제공합니다. 관리 샌드박스 리소스, 스냅샷, 서비스 URL, 인증 프록시는 [LangSmith Sandboxes](/langsmith/sandboxes)를 참조하세요.

기본 사용법 (Basic usage)

이 예시들은 이미 프로바이더 SDK로 샌드박스/devbox를 만들었고 자격 증명을 설정했다고 가정합니다. 가입, 인증, 프로바이더별 수명 주기 세부 사항은 사용 가능한 프로바이더를 참조하세요.

import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { ChatAnthropic } from "@langchain/anthropic";
import { SandboxClient } from "langsmith/sandbox";

const client = new SandboxClient();
const lsSandbox = await client.createSandbox();

try {
  const agent = createDeepAgent({
    model: new ChatAnthropic({ model: "claude-opus-4-8" }),
    systemPrompt: "You are a coding assistant with sandbox access.",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });

  const result = await agent.invoke({
    messages: [
      {
        role: "user",
        content: "Create a hello world Python script and run it",
      },
    ],
  });
  void result;
} finally {
  await client.deleteSandbox(lsSandbox.name);
}
이 예시의 공개 LangSmith 실행을 엽니다. [LangSmith](https://smith.langchain.com?utm_source=docs\&utm_medium=cta\&utm_campaign=langsmith-signup\&utm_content=oss-deepagents-sandboxes) 추적은 샌드박스 안에서 어떤 셸 명령이 실행됐는지, 에이전트가 파일 시스템 도구를 어떻게 사용했는지 보여줍니다. [관찰 가능성 퀵스타트](/langsmith/observability-quickstart)를 따라 설정하세요. 관리형 샌드박스 호스팅은 [LangSmith Sandboxes](/langsmith/sandboxes)를 참조하세요.

또한 추적을 모니터링하고, 문제를 감지하고, 수정을 제안하는 LangSmith Engine도 설정하는 것을 권장합니다.

사용 가능한 프로바이더 (Available providers)

프로바이더별 설정, 인증, 수명 주기 세부 사항은 샌드박스 통합을 참조하세요.

수명 주기와 범위 지정 (Lifecycle and scoping)

대부분의 애플리케이션은 thread당 하나의 샌드박스(스레드 범위) 또는 같은 assistant의 모든 스레드에 대한 하나의 공유 샌드박스(어시스턴트 범위)를 선택합니다.

샌드박스는 종료될 때까지 리소스를 소비하고 비용이 듭니다. 더 이상 사용하지 않으면 샌드박스를 종료하세요.

전체 수명 주기 표, 비동기 graph factory 노트, TTL 동작, LangGraph Deployment 연결, 클라이언트 측 예시는 프로덕션 배포Sandbox 수명 주기를 참조하세요.

스레드 범위 (기본값) (Thread-scoped (default))

각 대화는 자신만의 샌드박스를 얻습니다. 첫 실행이 그것을 만들고, 같은 스레드의 후속 턴은 그것을 재사용합니다. 스레드가 끝나거나 샌드박스 TTL이 만료되면 환경은 사라집니다. 아래 예시처럼 샌드박스 이름이나 메타데이터로 매핑을 저장해서 각 실행이 같은 샌드박스로 해석되게 하세요.

사용자가 유휴 시간 후 돌아올 수 있다면 샌드박스에 TTL을 구성해서 프로바이더가 유휴 환경을 자동으로 삭제하거나 보관하게 하세요. ```ts Google theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} import { createDeepAgent, LangSmithSandbox } from "deepagents"; import { SandboxClient } from "langsmith/sandbox"; import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) { const threadId = config.configurable?.thread_id as string; // [!code highlight] const sandboxName = thread-${threadId}; const existing = (await client.listSandboxes()).filter( (sb) => sb.name === sandboxName, ); const lsSandbox = existing[0] ?? (await client.createSandbox({ name: sandboxName, idleTtlSeconds: 3600, // TTL: clean up when idle })); return createDeepAgent({ model: "google-genai:gemini-3.6-flash", backend: new LangSmithSandbox({ sandbox: lsSandbox }), }); }


```ts OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const threadId = config.configurable?.thread_id as string; // [!code highlight]
  const sandboxName = `thread-${threadId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
      idleTtlSeconds: 3600, // TTL: clean up when idle
    }));
  return createDeepAgent({
    model: "openai:gpt-5.5",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const threadId = config.configurable?.thread_id as string; // [!code highlight]
  const sandboxName = `thread-${threadId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
      idleTtlSeconds: 3600, // TTL: clean up when idle
    }));
  return createDeepAgent({
    model: "anthropic:claude-sonnet-5",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const threadId = config.configurable?.thread_id as string; // [!code highlight]
  const sandboxName = `thread-${threadId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
      idleTtlSeconds: 3600, // TTL: clean up when idle
    }));
  return createDeepAgent({
    model: "openrouter:z-ai/glm-5.2",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const threadId = config.configurable?.thread_id as string; // [!code highlight]
  const sandboxName = `thread-${threadId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
      idleTtlSeconds: 3600, // TTL: clean up when idle
    }));
  return createDeepAgent({
    model: "fireworks:accounts/fireworks/models/glm-5p2",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const threadId = config.configurable?.thread_id as string; // [!code highlight]
  const sandboxName = `thread-${threadId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
      idleTtlSeconds: 3600, // TTL: clean up when idle
    }));
  return createDeepAgent({
    model: "baseten:zai-org/GLM-5.2",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const threadId = config.configurable?.thread_id as string; // [!code highlight]
  const sandboxName = `thread-${threadId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
      idleTtlSeconds: 3600, // TTL: clean up when idle
    }));
  return createDeepAgent({
    model: "ollama:north-mini-code-1.0",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}

어시스턴트 범위 (Assistant-scoped)

같은 어시스턴트의 모든 스레드는 하나의 샌드박스를 재사용합니다. 파일, 설치된 패키지, 클론된 저장소가 대화를 넘어 지속됩니다.

어시스턴트 범위 샌드박스는 시간이 지나며 샌드박스 내부 상태가 축적됩니다. 샌드박스 프로바이더로 TTL을 구성하고, 스냅샷으로 주기적으로 리셋하거나, 정리 로직을 구현해서 디스크와 메모리가 무한정 늘어나지 않게 하세요. ```ts Google theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} import { createDeepAgent, LangSmithSandbox } from "deepagents"; import { SandboxClient } from "langsmith/sandbox"; import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) { const assistantId = config.configurable?.assistant_id as string; // [!code highlight] const sandboxName = assistant-${assistantId}; const existing = (await client.listSandboxes()).filter( (sb) => sb.name === sandboxName, ); const lsSandbox = existing[0] ?? (await client.createSandbox({ name: sandboxName, })); return createDeepAgent({ model: "google-genai:gemini-3.6-flash", backend: new LangSmithSandbox({ sandbox: lsSandbox }), }); }


```ts OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const assistantId = config.configurable?.assistant_id as string; // [!code highlight]
  const sandboxName = `assistant-${assistantId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
    }));
  return createDeepAgent({
    model: "openai:gpt-5.5",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const assistantId = config.configurable?.assistant_id as string; // [!code highlight]
  const sandboxName = `assistant-${assistantId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
    }));
  return createDeepAgent({
    model: "anthropic:claude-sonnet-5",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const assistantId = config.configurable?.assistant_id as string; // [!code highlight]
  const sandboxName = `assistant-${assistantId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
    }));
  return createDeepAgent({
    model: "openrouter:z-ai/glm-5.2",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const assistantId = config.configurable?.assistant_id as string; // [!code highlight]
  const sandboxName = `assistant-${assistantId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
    }));
  return createDeepAgent({
    model: "fireworks:accounts/fireworks/models/glm-5p2",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const assistantId = config.configurable?.assistant_id as string; // [!code highlight]
  const sandboxName = `assistant-${assistantId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
    }));
  return createDeepAgent({
    model: "baseten:zai-org/GLM-5.2",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";
import type { LangGraphRunnableConfig } from "@langchain/langgraph";

const client = new SandboxClient();

export async function agent(config: LangGraphRunnableConfig) {
  const assistantId = config.configurable?.assistant_id as string; // [!code highlight]
  const sandboxName = `assistant-${assistantId}`;
  const existing = (await client.listSandboxes()).filter(
    (sb) => sb.name === sandboxName,
  );
  const lsSandbox =
    existing[0] ??
    (await client.createSandbox({
      name: sandboxName,
    }));
  return createDeepAgent({
    model: "ollama:north-mini-code-1.0",
    backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  });
}

graph factory 밖에서 수동으로 만들고, 실행하고, 정리하려면 기본 사용법과 프로바이더별 API에 대한 샌드박스 통합을 참조하세요.

통합 패턴 (Integration patterns)

에이전트를 샌드박스와 통합하는 두 가지 아키텍처 패턴이 있으며, 에이전트가 어디서 실행되는지에 따라 달라집니다.

샌드박스 안의 에이전트 패턴 (Agent in sandbox pattern)

에이전트는 샌드박스 안에서 실행되고 네트워크를 통해 통신합니다. 에이전트 프레임워크가 사전 설치된 Docker나 VM 이미지를 만들고, 샌드박스 안에서 실행하고, 바깥에서 연결해 메시지를 보냅니다.

장점:

  • ✅ 로컬 개발을 밀접하게 반영합니다.
  • ✅ 에이전트와 환경 사이의 긴밀한 결합.

단점:

  • 🔴 API 키가 샌드박스 안에 있어야 합니다(보안 위험).
  • 🔴 업데이트하려면 이미지를 다시 빌드해야 합니다.
  • 🔴 통신을 위한 인프라(WebSocket 또는 HTTP 계층)가 필요합니다.

샌드박스 안에서 에이전트를 실행하려면 이미지를 만들고 그 위에 deepagents를 설치하세요.

FROM python:3.11
RUN pip install deepagents-code

그런 다음 샌드박스 안에서 에이전트를 실행하세요. 샌드박스 안의 에이전트를 사용하려면 애플리케이션과 샌드박스 안의 에이전트 사이의 통신을 처리할 추가 인프라를 구축해야 합니다.

도구로서의 샌드박스 패턴 (Sandbox as tool pattern)

에이전트는 여러분의 머신이나 서버에서 실행됩니다. 코드를 실행해야 할 때 샌드박스 도구(execute, read_file, write_file 등)를 호출하며, 이 도구들이 프로바이더의 API를 호출해 원격 샌드박스에서 연산을 실행합니다.

장점:

  • ✅ 이미지를 다시 빌드하지 않고 에이전트 코드를 즉시 업데이트합니다.
  • ✅ 에이전트 상태와 실행 사이의 더 깔끔한 분리.
    • API 키가 샌드박스 밖에 유지됩니다.
    • 샌드박스 실패가 에이전트 상태를 잃지 않게 합니다.
    • 여러 샌드박스에서 작업을 병렬로 실행하는 옵션.
  • ✅ 실행 시간에 대해서만 비용을 지불합니다.

단점:

  • 🔴 각 실행 호출에 네트워크 지연.
import "dotenv/config";
import { createDeepAgent, LangSmithSandbox } from "deepagents";
import { SandboxClient } from "langsmith/sandbox";

// Can also do this with Deno, Daytona, E2B, Modal, or Runloop
const client = new SandboxClient();
const lsSandbox = await client.createSandbox();

const agent = createDeepAgent({
  backend: new LangSmithSandbox({ sandbox: lsSandbox }),
  systemPrompt:
    "You are a coding assistant with sandbox access. You can create and run code in the sandbox.",
});

try {
  const result = await agent.invoke({
    messages: [
      {
        role: "user",
        content: "Create a hello world Python script and run it",
      },
    ],
  });
  const lastMessage = result.messages[result.messages.length - 1];
  console.log(
    typeof lastMessage.content === "string"
      ? lastMessage.content
      : String(lastMessage.content),
  );
} finally {
  await client.deleteSandbox(lsSandbox.name);
}
이 예시의 공개 LangSmith 실행을 엽니다.

이 문서의 예시들은 도구로서의 샌드박스 패턴을 사용합니다. 프로바이더의 SDK가 통신 계층을 처리하고 프로덕션에서 로컬 개발을 반영하고 싶다면 샌드박스 안의 에이전트 패턴을 선택하세요. 에이전트 로직을 빠르게 반복하고, API 키를 샌드박스 밖에 유지하거나, 관심사의 더 깔끔한 분리를 선호한다면 도구로서의 샌드박스 패턴을 선택하세요.

샌드박스는 어떻게 작동하나요? (How sandboxes work)

격리 경계 (Isolation boundaries)

모든 샌드박스 프로바이더는 에이전트의 파일 시스템과 셸 연산으로부터 호스트 시스템을 보호합니다. 에이전트는 로컬 파일을 읽거나, 머신의 환경 변수에 접근하거나, 다른 프로세스를 방해할 수 없습니다. 그러나 샌드박스만으로는 다음을 보호하지 합니다:

  • 컨텍스트 주입: 에이전트 입력의 일부를 제어하는 공격자는 샌드박스 안에서 임의의 명령을 실행하도록 지시할 수 있습니다. 샌드박스는 격리되어 있지만, 그 안에서 에이전트가 완전한 제어권을 가집니다.
  • 네트워크 유출: 네트워크 접근이 차단되지 않는 한, 컨텍스트 주입된 에이전트는 HTTP나 DNS를 통해 샌드박스 밖으로 데이터를 보낼 수 있습니다. 일부 프로바이더는 네트워크 접근 차단을 지원합니다(예: Modal의 blockNetwork: true).

비밀 처리와 이러한 위험 완화 방법은 보안 고려 사항을 참조하세요.

execute 메서드

샌드박스 백엔드는 단순한 아키텍처를 가집니다. 프로바이더가 구현해야 하는 유일한 메서드는 셸 명령을 실행하고 출력을 반환하는 execute()입니다.

다른 모든 파일 시스템 연산(read, write, edit, ls, glob, grep)은 스크립트를 구성하고 execute()로 샌드박스 안에서 실행하는 BaseSandbox 기본 클래스가 execute() 위에 구축합니다.

graph TB
    subgraph "Agent tools"
        Tools["ls, read_file, ..."]
        execute
    end

    BaseSandbox["BaseSandbox<br/>(uses execute)"] --> Tools
    execute_method["execute()"] --> BaseSandbox
    execute_method --> execute
    Provider["Provider SDK"] --> execute_method

    classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
    classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900

    class Tools,execute process
    class BaseSandbox,execute_method process
    class Provider trigger

이 설계는 다음을 의미합니다:

  • 새 프로바이더 추가가 간단합니다. execute()를 구현하면 됩니다. 기본 클래스가 나머지를 처리합니다.
  • execute 도구는 조건부로 사용 가능합니다. 매 모델 호출에서 harness는 백엔드가 SandboxBackendProtocol을 구현하는지 확인합니다. 구현하지 않으면 도구는 필터링되어 에이전트가 결코 보지 못합니다.

에이전트가 execute 도구를 호출하면 command 문자열을 제공하고, 결합된 stdout/stderr, 종료 코드, 그리고 출력이 너무 크면 잘림 알림을 돌려받습니다.

애플리케이션 코드에서도 백엔드 execute() 메서드를 직접 호출할 수 있습니다.

예:

4
[Command succeeded with exit code 0]
bash: foobar: command not found
[Command failed with exit code 127]

명령이 매우 큰 출력을 만들면 결과는 자동으로 파일에 저장되고 에이전트는 read_file를 사용해 점진적으로 접근하라는 지시를 받습니다. 이렇게 하면 컨텍스트 창 오버플로를 방지합니다.

두 가지 파일 접근 평면 (Two planes of file access)

파일이 샌드박스로 들어오고 나가는 두 가지 뚜렷한 방식이 있으며, 각각 언제 사용하는지 이해하는 것이 중요합니다:

에이전트 파일 시스템 도구: read_file, write_file, edit_file, ls, glob, grep, execute는 LLM이 실행 중 호출하는 도구입니다. 이들은 샌드박스 안에서 execute()를 통해 진행됩니다. 에이전트는 작업의 일부로 코드를 읽고, 파일을 쓰고, 명령을 실행하는 데 사용합니다.

파일 전송 API: 애플리케이션 코드가 호출하는 uploadFiles()downloadFiles() 메서드입니다. 이들은 프로바이더의 네이티브 파일 전송 API(셸 명령이 아님)를 사용하며 호스트 환경과 샌드박스 사이에서 파일을 옮기도록 설계되었습니다. 다음에 사용하세요:

  • 에이전트 실행 전에 샌드박스 시드 — 소스 코드, 구성, 데이터
  • 에이전트 완료 후 산출물 검색 — 생성된 코드, 빌드 출력, 보고서
  • 에이전트가 필요로 할 의존성 사전 채우기
graph LR
    subgraph "Your application"
        App[Application code]
    end

    subgraph "Agent"
        LLM --> Tools["read_file, write_file, ..."]
        Tools --> LLM
    end

    subgraph "Sandbox"
        FS[Filesystem]
    end

    App -- "Provider API" --> FS
    Tools -- "execute()" --> FS

    classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900
    classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
    classDef output fill:#EBD0F0,stroke:#885270,stroke-width:2px,color:#441E33

    class App trigger
    class LLM,Tools process
    class FS output

파일 작업 (Working with files)

샌드박스 시드 (Seeding the sandbox)

에이전트 실행 전에 uploadFiles()로 샌드박스를 채우세요. 파일 내용은 Uint8Array로 제공됩니다:

const encoder = new TextEncoder();
const responses = await sandbox.uploadFiles([
  ["src/index.js", encoder.encode("console.log('Hello')")],
  ["package.json", encoder.encode('{"name": "my-app"}')],
]);

// Each response indicates success or failure
for (const res of responses) {
  if (res.error) {
    console.error(`Failed to upload ${res.path}: ${res.error}`);
  }
}

산출물 검색 (Retrieving artifacts)

에이전트 완료 후 downloadFiles()로 샌드박스에서 파일을 검색하세요:

const results = await sandbox.downloadFiles(["src/index.js", "output.txt"]);

const decoder = new TextDecoder();
for (const result of results) {
  if (result.content) {
    console.log(`${result.path}: ${decoder.decode(result.content)}`);
  } else {
    console.error(`Failed to download ${result.path}: ${result.error}`);
  }
}
샌드박스 안에서 에이전트는 자체 파일 시스템 도구(`read_file`, `write_file`)를 사용합니다. `uploadFiles`나 `downloadFiles`를 사용하지 않습니다. 그 메서드들은 애플리케이션 코드가 호스트와 샌드박스 사이의 경계를 넘어 파일을 옮기기 위한 것입니다.

보안 고려 사항 (Security considerations)

샌드박스는 코드 실행을 호스트 시스템과 격리하지만 컨텍스트 주입으로부터는 보호하지 못합니다. 에이전트 입력의 일부를 제어하는 공격자는 샌드박스 안에서 파일을 읽고, 명령을 실행하거나, 데이터를 유출하도록 지시할 수 있습니다. 이 때문에 샌드박스 안의 자격 증명은 특히 위험합니다.

**샌드박스 안에 비밀을 넣지 마세요.** 샌드박스에 주입된 API 키, 토큰, 데이터베이스 자격 증명 및 기타 비밀(환경 변수, 마운트된 파일, 또는 `secrets` 옵션을 통해)은 컨텍스트 주입된 에이전트가 읽고 유출할 수 있습니다. 이것은 단기 또는 범위가 제한된 자격 증명에도 적용됩니다. 에이전트가 접근할 수 있다면 공격자도 접근할 수 있습니다.

비밀을 안전하게 처리하기 (Handling secrets safely)

에이전트가 인증된 API를 호출하거나 보호된 리소스에 접근해야 한다면 두 가지 옵션이 있습니다:

  1. 샌드박스 밖의 도구에 비밀을 유지하세요. 호스트 환경에서(샌드박스 안이 아니라) 실행되고 인증을 처리하는 도구를 정의하세요. 에이전트는 그 도구를 이름으로 호출하지만 자격 증명은 결코 보지 못합니다. 권장되는 접근 방식입니다.

  2. 자격 증명을 주입하는 네트워크 프록시를 사용하세요. 일부 샌드박스 프로바이더는 샌드박스에서 나가는 HTTP 요청을 가로채 요청을 전달하기 전에 자격 증명(예: Authorization 헤더)을 붙이는 프록시를 지원합니다. 에이전트는 비밀을 결코 보지 못합니다. 그냥 URL에 평범한 요청을 할 뿐입니다. 이 접근 방식은 아직 프로바이더 전반에서 널리 사용할 수 없습니다.

반드시 샌드박스에 비밀을 주입해야 한다면(권장하지 않음) 다음 예방 조치를 취하세요:
  • 모든 도구 호출에 대해(민감한 것만 아니라) human-in-the-loop 승인을 활성화하세요
  • 샌드박스에서 네트워크 접근을 차단하거나 제한해 유출 경로를 줄이세요
  • 가능한 가장 좁은 자격 증명 범위와 가장 짧은 수명을 사용하세요
  • 예상치 못한 나가는 요청이 없는지 샌드박스 네트워크 트래픽을 모니터링하세요

이러한 안전장치가 있어도 이것은 여전히 안전하지 않은 해결 방법입니다. 충분히 창의적인 컨텍스트 주입 공격은 출력 필터링과 HITL 검토를 우회할 수 있습니다.

일반적인 모범 사례 (General best practices)

  • 애플리케이션에서 샌드박스 출력을 사용하기 전에 검토하세요
  • 필요하지 않을 때 샌드박스 네트워크 접근을 차단하세요
  • 미들웨어를 사용해 도구 출력에서 민감한 패턴을 필터링하거나 삭제하세요
  • 샌드박스 안에서 생성된 모든 것을 신뢰할 수 없는 입력으로 취급하세요

더 알아보기