Skip to content
LangSmith 文档版本 跟随上游 源快照 ce5f16f6 更新于 2026-10-18

沙箱 auth 代理

把凭据注入出站请求,并控制沙箱能抵达哪些目的地。

auth 代理让沙箱代码能调用外部 API(OpenAI、Anthropic、GitHub 等)而无需硬编凭据。在沙箱上配置后,一个在沙箱外运行的出口代理会自动把身份验证标头用你的工作区机密或你在代理配置中提供的只写凭据,注入匹配的出站请求。

WARNING

在创建引用它们的沙箱之前,你必须先在 LangSmith 工作区设置中配好你的机密(例如 OPENAI_API_KEY)。

出口与网络访问控制

注入凭据的同一个 proxy_config 也控制沙箱能抵达哪些目的地。访问控制沙箱主机上按连接执行,所以 access_control 的更改立即生效。

出口如何工作

  • 访问控制适用于每个出站 TCP 连接,无论是否 HTTP。
  • 对被某规则或回调匹配的 host 的 HTTPS 会被代理解密,以便它能注入标头;沙箱信任代理的 CA。对被不匹配 host 的 HTTPS,以及每个非 HTTP 连接(包括 PostgreSQL、SSH 和 Redis),原样直连。端口 80 和 443 为 HTTP 和 TLS 保留;任一端口上的非 HTTP 协议不工作。
  • 按主机名寻址目的地。 对字面 IP 地址在非 HTTP 端口上的直接裸 TCP 连接会被丢弃,即使该 IP 在 allow_list 上。对字面 IP 的 HTTPS 也会被丢弃,因为代理需要 TLS 握手中一个主机名。只有端口 80 上的明文 HTTP 对字面 IP 工作。
  • 只有 TCP 离开沙箱。 UDP(包括 QUIC)和 ICMP 被丢弃。

默认出口姿态

没有 access_control 时,每个主机名在每个 TCP 端口上都可抵达,除非你的组织处于受限出口。唯一的例外是解析到私有、回环或云元数据地址的主机,代理总是拒绝拨号。添加一个 access_control 以限制这一行为。

要向任意主机允许 HTTP 和 HTTPS 同时阻止每个其他端口,用一个端口限定的允许列表。* 匹配每个主机名:

json
{
  "access_control": {
    "allow_list": ["*:80", "*:443"]
  }
}

在其上添加 host:PORT 条目以开启特定的裸 TCP 目的地,例如 db.example.com:5432。

允许与拒绝列表

向 proxy_config 添加一个 access_control 对象,带其一:一个 allow_list 或一个 deny_list(不是两者都——若两者都设,请求会被拒绝):

模式行为
allow_list默认拒绝。 只有列出的目的地可抵达,任何协议。列出沙箱需要的每个主机,包括你的 rules 和 callbacks 所指向的 HTTP(S) 主机。
deny_list默认允许。 除列出的以外,每个目的地都可抵达,任何协议。

两个列表对 HTTP、HTTPS 和裸 TCP 一视同仁。两种模式都不区分协议;用一个端口后缀把条目限制到某个端口。

WARNING

deny_list 只阻塞你列出的主机。{"deny_list": ["example.com"]} 在每个端口上阻塞 example.com,并在每个其他端口上保留每个其他主机可达,包括 DNS、SSH 和数据库端口。要到处关闭裸 TCP,用一个如 ["*:80", "*:443"] 的 allow_list。

模式语法

每个 allow_list/deny_list 条目使用以下形式:

模式含义
host裸主机 → 每个端口。
host:PORT恰好在 PORT 上的主机。db.example.com:5432 只覆盖 5432;为任何其他端口另加一条。
*.example.comGlob(RFC 1034 风格)。不包含顶点(example.com)。可带一个端口。
~regex对主机名匹配的正则,每个端口。不解析端口后缀。
1.2.3.4 / [::1]字面 IP。可带一个端口:1.2.3.4:443、[::1]:22。
10.0.0.0/8CIDR。不能带端口。

匹配基于沙箱寻址的目的地原样。一个主机名条目匹配向该主机名发出的请求;一个 IP 或 CIDR 条目匹配向字面 IP 地址发出的请求。两者都不被解析:deny_list: ["203.0.113.0/24"] 不会阻塞 foo.example.com,即使它解析进那个范围;allow_list: ["203.0.113.7"] 也不允许它。因为裸 TCP 和对字面 IP 的 HTTPS 在访问控制运行前就被丢弃(见出口如何工作),IP 和 CIDR 条目只会影响直接以 IP 命名的、端口 80 上的明文 HTTP 请求。

无法解析的条目——example.com:abc、example.com:99999,或一个带端口的 CIDR——在创建或更新沙箱时被拒绝。

组织级受限出口

受限出口把沙箱连接限制为一个 LangSmith 管理的主机与端口允许列表。它默认适用于非 Enterprise 组织,以及那些申请它的组织。拥有已批准豁免的组织可用不受限出口。

该允许列表支持常见开发任务,包括安装软件包、访问源码仓库和调用模型 API。它包含 PyPI、npm、GitHub、OpenAI、Anthropic 和 LangSmith,主要通过 443 端口上的 HTTPS。选定的软件包仓库也允许 80 端口上的 HTTP。

当受限出口为你的组织启用时,LangSmith 会阻塞该允许列表之外的目的地。组织策略取代调用方提供的 access_control 设置并禁用 no_proxy 绕过。往你沙箱的 allow_list 添加一个目的地、或为它配置凭据,都不会授予超出组织策略的访问。

一次组织策略更改不会自动更新现有沙箱。启动或唤醒一个现有沙箱会复用它的已存网络配置。

请求不受限访问

如果你的沙箱需要管理允许列表之外的目的地,提交一个支持工单为你的组织请求不受限出口。包含以下详情:

  • 组织:你的组织或工作区 ID 及部署区域。
  • 网络需求:目的地主机名、端口,以及你用例的描述。
  • 受影响沙箱:对一个现有沙箱,它的 ID 和连接错误。

一个已批准的豁免移除组织管理的允许列表要求。你沙箱自己的访问控制,以及针对访问私有或保留 IP 地址的平台防护仍然适用。一个豁免不会自动更改现有沙箱配置。

连接到一个数据库(裸 TCP)

如果你的组织有受限出口,在连接到管理允许列表之外的数据库前,请求不受限访问。

要让沙箱代码用 psql、dbt 或任何驱动抵达一个外部 PostgreSQL 数据库,在其端口上允许列表该主机。因为 allow_list 是默认拒绝,也列出沙箱需要的任何 HTTP(S) 主机。除非你需要其他端口,把它们钉到 :443:

bash
curl -X POST "$LANGSMITH_ENDPOINT/v2/sandboxes/boxes" \
  -H "x-api-key: $LANGSMITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "db-sandbox",
    "proxy_config": {
      "access_control": {
        "allow_list": [
          "db.example.com:5432",
          "api.openai.com:443"
        ]
      }
    }
  }'

对 db.example.com:5432 的连接在 TCP 层原样直连、无拦截,所以 PostgreSQL 线路协议——以及其上的 TLS、主机密钥校验和任何其他端到端协议——原样工作。

NOTE

创建一个沙箱会启动它并在它报告 ready 后返回,所以没有要添加的等待步骤。GET /api/v2/sandboxes/boxes/{name}/status 在你以后需要重新检查时报告当前状态。

通过 SDK 配置

python
from langsmith.sandbox import SandboxClient

client = SandboxClient()

client.create_sandbox(
    name="db-sandbox",
    proxy_config={
        "access_control": {
            "allow_list": ["db.example.com:5432", "api.openai.com:443"]
        }
    },
)
ts
import { SandboxClient } from "langsmith/sandbox";

const client = new SandboxClient();

await client.createSandbox({
  name: "db-sandbox",
  proxyConfig: {
    access_control: {
      allow_list: ["db.example.com:5432", "api.openai.com:443"],
    },
  },
});

配置 auth 代理规则

在创建沙箱时添加一个 proxy_config,或通过 patch 其 proxy_config 更新一个现有沙箱。一个 proxy_config 有:

字段描述
rules标头注入和提供商 auth 规则。已启用的标头规则按列表顺序首匹配获胜;aws 和 gcp 规则无论位置都匹配其提供商的主机
callbacks动态凭据查找;见回调凭据示例
access_controlallow_list 或 deny_list;见允许与拒绝列表
description可选,最多 1024 个字符。这个配置让沙箱能抵达什么,用于交给一个 agent

每条规则指定:

字段描述
name必需。规则的标识符
type标头注入则省略;aws 或 gcp 用于提供商 auth
match_hosts标头规则必需;在 aws 和 gcp 规则上被拒绝。裸主机名,或一个可注册域前面的 *. 通配符(*.github.com,而非 *.com 或 *)。无协议、路径或端口。通配符不匹配顶点域
match_paths要匹配的路径(空 = 所有路径)。仅标头规则
headers要注入的标头,各有 name、type 和 value。仅标头规则
aws / gcp提供商凭据;见认证 AWS 请求和认证 GCP 请求
env_vars在该规则启用期间在沙箱中设置的环境变量
enabled默认为 true
description可选,最多 1024 个字符。这条规则让沙箱能抵达什么

标头类型

每个标头有一个必需的 type,控制它的值如何存储和显示:

类型描述
workspace_secret用 {KEY} 语法引用一个工作区机密。在代理配置被应用时解析。
plaintext值原样存储并返回。用于非敏感标头。
opaque只写。值静态加密且从不会通过 API 返回。

从一条规则设置环境变量

一条规则的 env_vars 是在该规则启用期间,为沙箱中每个命令设置的明文环境变量。把它们用于那些除非有一个凭据变量存在就拒绝运行的工具,即使代理在链路上注入真正的凭据:给那个变量一个占位值以使命令启动,代理提供真正的凭据。

值是明文且会被 API 返回,所以绝不要把机密放进 env_vars。改用一个带 workspace_secret 或 opaque 类型的标头。

环境变量按以下顺序解析,从低优先级到高优先级:

  1. 快照镜像的 ENV,当沙箱选择加入 apply_image_config 时。
  2. 已启用的代理规则:当两条已启用规则声明同名时,rules 中靠后的规则获胜。
  3. 沙箱自己的 env_vars:显式的按沙箱值覆盖来自规则的值。

由一条已启用的 AWS 或 GCP auth 规则管理的变量(AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_EC2_METADATA_DISABLED、AWS_CA_BUNDLE;CLOUDSDK_AUTH_ACCESS_TOKEN、CLOUDSDK_CORE_CUSTOM_CA_CERTS_FILE)处于这个顺序之外:一条规则或一个沙箱 env_vars 条目若声明其中之一,则在匹配的那个 auth 规则启用时被拒绝。

每个沙箱也有把通用 CA 包环境变量指向系统信任库,其中包含代理的 CA,所以钉自己包的代理注入主机仍能验证的工具照常工作。自己设置其中任一以覆盖。

json
{
  "name": "github-api",
  "match_hosts": ["api.github.com"],
  "headers": [
    {"name": "Authorization", "type": "opaque", "value": "Bearer <github-token>"}
  ],
  "env_vars": {"GH_TOKEN": "proxy-injected"}
}

认证 AWS 请求

当沙箱代码需要用 AWS SDK 或 CLI 调用 AWS 服务时,用一个 AWS auth 规则。代理把真正的 AWS 凭据保持在沙箱之外,然后用 AWS SigV4 签名向 *.amazonaws.com 端点的出站 HTTPS 请求。

当 agent 代码需要检查 S3 对象、调用 Bedrock,或使用另一个 AWS 端点而不把长命 AWS 访问密钥暴露在沙箱文件、环境变量、shell 历史或日志中时,这很有用。沙箱收到占位的 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY 值(加 AWS_EC2_METADATA_DISABLED=true 和 AWS_CA_BUNDLE)以便 SDK 凭据检测工作,而代理用一个真正的 SigV4 签名替换请求所携带的任何东西。只有 service.region.amazonaws.com 主机、S3 virtual-hosted 和 path 风格主机,以及少量全局端点(如 iam、sts 和 s3)会被签名;到匹配的 AWS 主机的明文 HTTP 会被以 403 拒绝。

WARNING

不要把真正的 AWS 访问密钥设为沙箱环境变量。把它们配置为 workspace_secret 或 opaque 代理值。明文 AWS 凭据值会被拒绝。

AWS auth 规则不同于标头注入规则:

  • 把 type 设为 aws。
  • 把凭据放在 aws 对象下。
  • 不要设 match_hosts、match_paths 或 headers;AWS 主机匹配内置在代理中。
  • 每个沙箱最多配一条 AWS auth 规则。这个上限计入每条 AWS 规则,含被禁用的。
bash
curl -X POST "$LANGSMITH_ENDPOINT/v2/sandboxes/boxes" \
  -H "x-api-key: $LANGSMITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "aws-sandbox",
    "proxy_config": {
      "rules": [
        {
          "name": "aws",
          "type": "aws",
          "enabled": true,
          "aws": {
            "access_key_id": {
              "type": "workspace_secret",
              "value": "{AWS_ACCESS_KEY_ID}"
            },
            "secret_access_key": {
              "type": "workspace_secret",
              "value": "{AWS_SECRET_ACCESS_KEY}"
            }
          }
        }
      ]
    }
  }'

通过 SDK 配置 AWS auth

python
from langsmith.sandbox import (
    SandboxClient,
    aws_auth,
    proxy_config,
    workspace_secret,
)

client = SandboxClient()

client.create_sandbox(
    name="aws-sandbox",
    proxy_config=proxy_config(
        rules=[
            aws_auth(
                access_key_id=workspace_secret("AWS_ACCESS_KEY_ID"),
                secret_access_key=workspace_secret("AWS_SECRET_ACCESS_KEY"),
            )
        ]
    ),
)
ts
import {
  SandboxClient,
  awsAuth,
  proxyConfig,
  workspaceSecret,
} from "langsmith/sandbox";

const client = new SandboxClient();

await client.createSandbox({
  name: "aws-sandbox",
  proxyConfig: proxyConfig({
    rules: [
      awsAuth({
        accessKeyId: workspaceSecret("AWS_ACCESS_KEY_ID"),
        secretAccessKey: workspaceSecret("AWS_SECRET_ACCESS_KEY"),
      }),
    ],
  }),
});

沙箱就绪后,在沙箱内正常使用 AWS SDK 或 CLI。SDK 或 CLI 发现占位的 AWS 环境变量,代理对出站 AWS 请求应用真正的 SigV4 签名。代理不设区域:通过沙箱的 env_vars 或规则的 env_vars 设 AWS_REGION,否则多数 SDK 和 CLI 调用会在抵达代理前失败。

NOTE

静态 AWS auth 接受一个访问密钥 ID 和秘密访问密钥,但不接受调用方提供的 session 令牌。启用了 AWS 代理角色 auth 的部署也支持一个 IAM 角色,如下所述。

用一个 IAM 角色身份验证

一个 AWS 代理角色让 LangSmith 承担一个客户 IAM 角色,并用可续的新临时凭据签名受支持的 AWS HTTPS 请求。它配合或不配合 S3 挂载都能工作,不向沙箱代码暴露真正凭据。

这个选项需要部署对 AWS 代理角色 auth 的支持。ECR 仓库角色 auth 是一项独立功能。在 Create sandbox > Network 中,启用 AWS auth 并选择 AWS IAM role。表单会显示客户角色必须信任的确切 LangSmith principal 和工作区 External ID。遵循链接的设置说明配置信任与权限策略,然后在 AWS role ARN 中输入客户角色。

NOTE

下面的 SDK 例子需要一个在 Python 里支持 aws_auth(role_arn=...) 或在 TypeScript 里支持 awsAuth({ roleArn }) 的版本。仅有后端支持不会把这些助手加到一个较旧的 SDK 版本。

要在没有挂载的情况下配一个角色,在代理配置中传入它:

python
from langsmith.sandbox import SandboxClient, aws_auth, proxy_config

client = SandboxClient()

sandbox = client.create_sandbox(
    name="aws-role-sandbox",
    proxy_config=proxy_config(
        rules=[
            aws_auth(role_arn="arn:aws:iam::123456789012:role/LangSmithSandbox")
        ]
    ),
)
ts
import { SandboxClient, awsAuth, proxyConfig } from "langsmith/sandbox";

const client = new SandboxClient();

const sandbox = await client.createSandbox({
  name: "aws-role-sandbox",
  proxyConfig: proxyConfig({
    rules: [
      awsAuth({ roleArn: "arn:aws:iam::123456789012:role/LangSmithSandbox" }),
    ],
  }),
});

对于 HTTP API,提供 aws.role_arn 而非静态凭据字段:

bash
curl -X POST "$LANGSMITH_ENDPOINT/v2/sandboxes/boxes" \
  -H "x-api-key: $LANGSMITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "aws-role-sandbox",
    "proxy_config": {
      "rules": [
        {
          "name": "aws",
          "type": "aws",
          "enabled": true,
          "aws": {
            "role_arn": "arn:aws:iam::123456789012:role/LangSmithSandbox"
          }
        }
      ]
    }
  }'

用一个你配好的客户角色替换示例 ARN。单规则上限仍然适用。角色 auth 在创建时配置:一次更新可以保留现有角色,但不能添加、移除、更改或禁用它。创建一个新沙箱以更改角色 auth。

角色的有效 AWS 权限适用于所有受支持的请求。LangSmith 不会给这条规则加一个从挂载派生的 session 策略。一个只读 S3 挂载会阻塞文件系统写入,而非 IAM 允许的直连 S3 API 写入。把客户角色限制到沙箱需要的服务、资源和动作。

LangSmith 按需续新临时凭据,并在 stop/start 后重新获取它们。如果续新暂时不可用,缓存的凭据只在过期前仍可用。授权拒绝或过期会以失败关闭,不回退到静态密钥。

认证 GCP 请求

当沙箱代码需要用 Google SDK 或 CLI 调用 Google API 时,用一个 GCP auth 规则。代理把服务账户 JSON 保持在沙箱之外,然后向 googleapis.com 及其子域的出站 HTTPS 请求身份验证。

当 agent 代码需要检查 GCS 对象或调用另一个 Google API 而不把服务账户 JSON 暴露在沙箱文件、环境变量、shell 历史或日志中时,这很有用。沙箱收到一个占位的 CLOUDSDK_AUTH_ACCESS_TOKEN(加 CLOUDSDK_CORE_CUSTOM_CA_CERTS_FILE)以便 gcloud 运行,而代理用一个从配好的服务账户铸造的令牌替换请求所携带的任何授权。通过 Application Default Credentials 发现凭据的 Google 客户端库不读这些变量、会找不到凭据而失败;只支持 gcloud 和直接 HTTPS 调用。

WARNING

不要把真正的服务账户 JSON 设为沙箱环境变量。把它配置为 workspace_secret 或 opaque 代理值。明文 GCP 凭据值会被拒绝。

GCP auth 规则不同于标头注入规则:

  • 把 type 设为 gcp。
  • 把凭据放在 gcp.service_account_json 下。
  • 把 gcp.scopes 设为一个非空的 OAuth 作用域列表。
  • 代理自动匹配 googleapis.com 及其子域,并用配好的服务账户为那些请求身份验证。google.com 下的主机不被匹配。到匹配主机的明文 HTTP 会被以 403 拒绝。
  • 每个沙箱最多配一条 GCP auth 规则。这个上限计入每条 GCP 规则,含被禁用的。

SDK 的 gcp_auth 和 gcpAuth 助手构建同样的这个规则形状。

bash
curl -X POST "$LANGSMITH_ENDPOINT/v2/sandboxes/boxes" \
  -H "x-api-key: $LANGSMITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "gcp-sandbox",
    "proxy_config": {
      "rules": [
        {
          "name": "gcp",
          "type": "gcp",
          "enabled": true,
          "gcp": {
            "service_account_json": {
              "type": "workspace_secret",
              "value": "{GCP_SERVICE_ACCOUNT_JSON}"
            },
            "scopes": [
              "https://www.googleapis.com/auth/devstorage.read_only"
            ]
          }
        }
      ]
    }
  }'

通过 SDK 配置 GCP auth

python
from langsmith.sandbox import (
    SandboxClient,
    gcp_auth,
    proxy_config,
    workspace_secret,
)

client = SandboxClient()

client.create_sandbox(
    name="gcp-sandbox",
    proxy_config=proxy_config(
        rules=[
            gcp_auth(
                service_account_json=workspace_secret("GCP_SERVICE_ACCOUNT_JSON"),
                scopes=["https://www.googleapis.com/auth/devstorage.read_only"],
            )
        ]
    ),
)
ts
import {
  SandboxClient,
  gcpAuth,
  proxyConfig,
  workspaceSecret,
} from "langsmith/sandbox";

const client = new SandboxClient();

await client.createSandbox({
  name: "gcp-sandbox",
  proxyConfig: proxyConfig({
    rules: [
      gcpAuth({
        serviceAccountJson: workspaceSecret("GCP_SERVICE_ACCOUNT_JSON"),
        scopes: ["https://www.googleapis.com/auth/devstorage.read_only"],
      }),
    ],
  }),
});

沙箱就绪后,在沙箱内正常使用 gcloud 或向 googleapis.com 主机的直接 HTTPS 调用。代理应用真正的 GCP 身份验证,而不在沙箱内暴露服务账户 JSON。

单个 API 示例

创建一个会自动把一个 OpenAI API 密钥注入出站请求的沙箱:

bash
curl -X POST "$LANGSMITH_ENDPOINT/v2/sandboxes/boxes" \
  -H "x-api-key: $LANGSMITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "openai-sandbox",
    "proxy_config": {
      "rules": [
        {
          "name": "openai-api",
          "match_hosts": ["api.openai.com"],
          "headers": [
            {
              "name": "Authorization",
              "type": "workspace_secret",
              "value": "Bearer {OPENAI_API_KEY}"
            }
          ]
        }
      ]
    }
  }'

对 api.openai.com 的请求现在由代理身份验证;沙箱中不存真正的密钥。拒绝在没 API 密钥时启动的 SDK 仍需一个占位——用规则的 env_vars 设一个,正如 GitHub 示例。

多个 API 示例

添加多条规则以同时用多个服务身份验证:

bash
curl -X POST "$LANGSMITH_ENDPOINT/v2/sandboxes/boxes" \
  -H "x-api-key: $LANGSMITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "multi-api-sandbox",
    "proxy_config": {
      "rules": [
        {
          "name": "openai-api",
          "match_hosts": ["api.openai.com"],
          "headers": [
            {
              "name": "Authorization",
              "type": "workspace_secret",
              "value": "Bearer {OPENAI_API_KEY}"
            }
          ]
        },
        {
          "name": "anthropic-api",
          "match_hosts": ["api.anthropic.com"],
          "headers": [
            {
              "name": "x-api-key",
              "type": "workspace_secret",
              "value": "{ANTHROPIC_API_KEY}"
            },
            {
              "name": "anthropic-version",
              "type": "plaintext",
              "value": "2023-06-01"
            }
          ]
        },
        {
          "name": "github-api",
          "match_hosts": ["api.github.com"],
          "match_paths": ["/repos/*", "/user"],
          "headers": [
            {
              "name": "Authorization",
              "type": "workspace_secret",
              "value": "Bearer {GITHUB_TOKEN}"
            }
          ]
        }
      ]
    }
  }'

GitHub 示例

Open SWE 通过在沙箱外铸造一个短命 GitHub App 安装令牌、然后用只写 opaque 代理规则 patch 沙箱来为 GitHub 访问身份验证。这把短命 GitHub 访问令牌保持在沙箱文件系统和部署环境变量之外。

配置两条规则:

主机标头
api.github.com对 gh 和 REST API 调用的 Authorization: Bearer <github-token>
github.com、*.github.com对 clone、fetch 和 push 等经 HTTPS 的 Git 操作的 Authorization: Basic <base64("x-access-token:<github-token>")>
python
import base64
import os
from typing import Any

import httpx

def github_proxy_rules(github_token: str) -> list[dict[str, Any]]:
    basic_auth = base64.b64encode(
        f"x-access-token:{github_token}".encode()
    ).decode()

    return [
        {
            "name": "github-api",
            "match_hosts": ["api.github.com"],
            "headers": [
                {
                    "name": "Authorization",
                    "type": "opaque",
                    "value": f"Bearer {github_token}",
                }
            ],
            "env_vars": {"GH_TOKEN": "proxy-injected"},
        },
        {
            "name": "github",
            "match_hosts": ["github.com", "*.github.com"],
            "headers": [
                {
                    "name": "Authorization",
                    "type": "opaque",
                    "value": f"Basic {basic_auth}",
                }
            ],
        },
    ]

def configure_github_proxy(sandbox_name: str, github_token: str) -> None:
    endpoint = os.environ.get(
        "LANGSMITH_ENDPOINT", "https://api.smith.langchain.com"
    )
    response = httpx.patch(
        f"{endpoint}/v2/sandboxes/boxes/{sandbox_name}",
        headers={"x-api-key": os.environ["LANGSMITH_API_KEY"]},
        json={"proxy_config": {"rules": github_proxy_rules(github_token)}},
        timeout=180.0,
    )
    response.raise_for_status()

在创建或重新附着一个沙箱之后调用 configure_github_proxy。GitHub App 安装令牌会过期,所以每当你复用一个沙箱做新运行时刷新代理配置。

WARNING

PATCH 会整体替换所存的 proxy_config。如果沙箱也用 access_control 或 callbacks,在每次更新中都包含它们,否则它们会被丢弃。你留空的 opaque 标头值会从当前配置沿用。

github-api 规则把 GH_TOKEN 设为一个非机密占位,满足 gh CLI 的本地凭据检查。此后命令无需逐命令前缀就能运行:

bash
gh repo view langchain-ai/langchain
gh pr list --repo langchain-ai/langchain
gh repo clone langchain-ai/langchain

这个占位从不离开沙箱。代理把真正的 Authorization 标头注入出站请求。

通过 SDK 配置

python
from langsmith.sandbox import SandboxClient

client = SandboxClient()

client.create_sandbox(
    name="openai-sandbox",
    proxy_config={
        "rules": [
            {
                "name": "openai-api",
                "match_hosts": ["api.openai.com"],
                "headers": [
                    {
                        "name": "Authorization",
                        "type": "workspace_secret",
                        "value": "Bearer {OPENAI_API_KEY}",
                    }
                ],
            }
        ]
    },
)
ts
import { SandboxClient } from "langsmith/sandbox";

const client = new SandboxClient();

await client.createSandbox({
  name: "openai-sandbox",
  proxyConfig: {
    rules: [
      {
        name: "openai-api",
        match_hosts: ["api.openai.com"],
        headers: [
          {
            name: "Authorization",
            type: "workspace_secret",
            value: "Bearer {OPENAI_API_KEY}",
          },
        ],
      },
    ],
  },
});

回调凭据示例

静态 workspace_secret 规则在代理配置被应用时从你的工作区拉取凭据,而 opaque 规则让你的应用 patch 进短命凭据,如 GitHub 令牌示例。对于必须在代理时由你自己的服务解析的凭据,用一个回调。代理 POST 到一个你提供的 URL,你的端点返回要注入的标头,代理缓存结果。

回调与规则一起在 proxy_config 下配置:

字段描述
match_hosts要拦截的主机(与规则相同的语法;支持如 *.github.com 的 glob)。
url你的回调端点。必须是一个解析到公共地址的 http:// 或 https:// URL;私有、回环、Kubernetes 内部和云元数据目标会被拒绝。
request_headers附到代理 → 回调请求的标头,例如你的端点用来验证请求的一个 HMAC 或共享密钥。只允许 plaintext 和 opaque 类型(无 workspace_secret)。
ttl_seconds必需。在重新调用回调之前,已解析标头被缓存多久。必须在 60 到 3600 之间。
full_request当为 true 时,对匹配主机的每个请求都调用回调(什么都不缓存),且 body 包含一个 request 快照:method、url、scheme、host、path、query、headers,以及最多 1 MiB 的 body 作为 body_base64(body_truncated 标记截断)。

**静态规则获胜。**如果一条已启用的标头注入规则同时匹配主机和路径,该请求会跳过回调。规则内首匹配获胜;多条回调都匹配时同样适用。

回调约定

每当代理需要在缓存未命中时为一个匹配主机解析凭据,它就发出以下请求:

POST <callback.url>
Content-Type: application/json
X-LangSmith-Signature-JWT: <signature>
<request_headers from your config, attached verbatim except Content-Type and X-LangSmith-Signature-JWT, which the proxy always sets>

{
  "host": "api.example.com",
  "port": 443,
  "identity": {
    "tenant_id": "<workspace-uuid>",
    "sandbox_id": "<sandbox-uuid>",
    "organization_id": "<organization-uuid>",
    "ls_user_id": "<creator-uuid>"
  }
}

identity 告诉你的端点哪个沙箱在请求,所以一个回调 URL 能服务多个沙箱并铸造按沙箱或按用户的凭据。ls_user_id 是创建者用户,当沙箱用一个工作区或服务密钥创建时省略它。忽略你不认识的字段和标头;代理可能添加更多。

你的端点必须用一个带 JSON body 的 2xx 响应:

json
{
  "headers": {
    "Authorization": "Bearer <token>",
    "X-Org-Id": "..."
  }
}

代理把响应中的每个标头注入沙箱的出站请求,并把响应缓存 ttl_seconds。任何非-2xx 响应、传输错误或格式错误的 JSON 都以失败关闭:沙箱的请求被以 502 callback resolution failed 拒绝(不注入标头,不缓存响应)。重定向不被跟随且算作失败。

验证回调请求

request_headers 让你的端点检查你选定的一个共享密钥。要验证一个请求来自 LangSmith 且未在传输中被篡改,检查 X-LangSmith-Signature-JWT 标头。它是一个用 Ed25519 密钥(alg: EdDSA)签名的 JWT,其公钥部分在 <LANGSMITH_ENDPOINT>/.well-known/jwks.json 发布,由令牌的 kid 选取。

声明预期值
iss你的 LangSmith 端点 origin,例如 https://api.smith.langchain.com
sublangsmith-sandbox-callback
aud你的回调 URL,与所配置的完全一致
exp签发后五分钟;拒绝过期令牌
body_sha256原始请求 body 的十六进制 SHA-256

对照 JWKS 验证签名,检查上面每个声明,对你收到的 body 做哈希,并与 body_sha256 比较。然后信任 body 中的 identity。

Example

当你的 OAuth 令牌由你自己的服务按需铸造时,用一个回调:

bash
curl -X POST "$LANGSMITH_ENDPOINT/v2/sandboxes/boxes" \
  -H "x-api-key: $LANGSMITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "snapshot_id": "<snapshot-uuid>",
    "name": "callback-sandbox",
    "proxy_config": {
      "callbacks": [
        {
          "match_hosts": ["api.github.com", "*.githubusercontent.com"],
          "url": "https://auth.your-app.example.com/sandbox-credentials",
          "request_headers": [
            {
              "name": "X-Integrator-Secret",
              "type": "opaque",
              "value": "<shared-secret-your-endpoint-verifies>"
            }
          ],
          "ttl_seconds": 300
        }
      ]
    }
  }'

通过 SDK 配置

python
from langsmith.sandbox import SandboxClient

client = SandboxClient()

client.create_sandbox(
    name="callback-sandbox",
    proxy_config={
        "callbacks": [
            {
                "match_hosts": ["api.github.com", "*.githubusercontent.com"],
                "url": "https://auth.your-app.example.com/sandbox-credentials",
                "request_headers": [
                    {
                        "name": "X-Integrator-Secret",
                        "type": "opaque",
                        "value": "<shared-secret-your-endpoint-verifies>",
                    }
                ],
                "ttl_seconds": 300,
            }
        ]
    },
)
ts
import { SandboxClient } from "langsmith/sandbox";

const client = new SandboxClient();

await client.createSandbox({
  name: "callback-sandbox",
  proxyConfig: {
    callbacks: [
      {
        match_hosts: ["api.github.com", "*.githubusercontent.com"],
        url: "https://auth.your-app.example.com/sandbox-credentials",
        request_headers: [
          {
            name: "X-Integrator-Secret",
            type: "opaque",
            value: "<shared-secret-your-endpoint-verifies>",
          },
        ],
        ttl_seconds: 300,
      },
    ],
  },
});

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。