Sandboxed Tool Authorization

Clawdbot Contributors· validated-in-production

问题

工具授权既要兼顾灵活性,又要保障安全性。静态允许列表无法适配以下各类场景:

  • 多环境差异:开发环境(权限宽松)与生产环境(权限严格)
  • 智能体角色分化:代码智能体需要文件系统访问权限,消息智能体则不应拥有该权限
  • 层级委托机制:子智能体需继承父智能体的权限限制,同时遵循额外约束
  • 插件生态体系:外部工具需支持动态接入,无需手动更新允许列表

智能体需要一套具备模式匹配、默认拒绝语义及层级继承能力的策略系统。

方案

基于匹配模式的权限策略(默认拒绝+支持继承)

工具授权通过与编译后的匹配模式(精确匹配、正则表达式、通配符)比对实现,拒绝列表优先级高于允许列表。子Agent继承父Agent的策略并可添加额外限制;基于配置文件的分层策略为常见Agent类型提供预设配置。


核心概念

  • 模式匹配:支持精确匹配(如exec)、通配符匹配(如fs:*)及类正则匹配模式(如*test*)。
  • 默认拒绝:允许列表为空时,所有工具均被拒绝;仅显式添加至允许列表的工具会获得授权。
  • 拒绝优先:先校验拒绝列表,只要工具匹配拒绝规则,无论允许列表配置如何,都会被阻止。
  • 关联工具权限继承:部分工具会隐式授予关联权限(例如,拥有exec权限则自动允许apply_patch)。
  • 层级策略继承:子Agent的策略继承自父Agent,且可叠加额外拒绝规则。
  • 配置文件分层策略:预定义配置文件(minimalcodingmessagingfull)支持快速完成权限配置。

实现示例

type CompiledPattern =
  | { kind: "all" }           // "*" 匹配所有工具
  | { kind: "exact"; value: string } // 精确匹配模式
  | { kind: "regex"; value: RegExp }; // 正则匹配模式

function compilePattern(pattern: string): CompiledPattern {
  const normalized = normalizeToolName(pattern);
  if (!normalized) return { kind: "exact", value: "" };
  if (normalized === "*") return { kind: "all" };
  if (!normalized.includes("*")) return { kind: "exact", value: normalized };
  // 将 "fs:*" 转换为 /^fs:.*$/ 正则表达式
  const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  return {
    kind: "regex",
    value: new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`),
  };
}

// 判断工具名是否匹配任意给定的编译模式
function matchesAny(name: string, patterns: CompiledPattern[]): boolean {
  const normalized = normalizeToolName(name);
  for (const pattern of patterns) {
    if (pattern.kind === "all") return true;
    if (pattern.kind === "exact" && name === pattern.value) return true;
    if (pattern.kind === "regex" && pattern.value.test(name)) return true;
  }
  return false;
}

// 生成工具策略匹配器,校验工具是否符合权限规则
function makeToolPolicyMatcher(policy: ToolPolicy) {
  const deny = compilePatterns(policy.deny);
  const allow = compilePatterns(policy.allow);
  return (name: string) => {
    const normalized = normalizeToolName(name);
    // 拒绝规则优先级更高
    if (matchesAny(normalized, deny)) return false;
    // 允许列表为空则默认允许所有工具(默认拒绝逻辑由调用方处理)
    if (allow.length === 0) return true;
    // 需要显式匹配允许规则
    if (matchesAny(normalized, allow)) return true;
    // 关联工具权限继承:若允许exec,则自动允许apply_patch
    if (normalized === "apply_patch" && matchesAny("exec", allow)) return true;
    return false;
  };
}

基于配置文件的分层策略

const TOOL_PROFILES: Record<ToolProfileId, ToolProfilePolicy> = {
  minimal: {
    allow: ["session_status"],  // 最小权限集
  },
  coding: {
    allow: [
      "group:fs",        // 文件系统组:读取、写入、编辑、应用补丁
      "group:runtime",   // 运行时组:执行命令、进程管理
      "group:sessions",  // 会话组:会话列表、创建会话等
      "group:memory",    // 记忆组:记忆检索、获取记忆
      "image",
    ],
  },
  messaging: {
    allow: [
      "group:messaging", // 消息工具组
      "sessions_list",
      "sessions_history",
      "sessions_send",
      "session_status",
    ],
  },
  full: {},  // 空策略表示允许所有工具
};

批量配置工具组

const TOOL_GROUPS: Record<string, string[]> = {
  "group:memory": ["memory_search", "memory_get"],
  "group:web": ["web_search", "web_fetch"],
  "group:fs": ["read", "write", "edit", "apply_patch"],
  "group:runtime": ["exec", "process"],
  "group:sessions": ["sessions_list", "sessions_history", "sessions_send", "sessions_spawn"],
  "group:clawdbot": ["browser", "canvas", "nodes", "cron", "message", "gateway", /* ... */],
};

子Agent策略继承

const DEFAULT_SUBAGENT_TOOL_DENY = [
  // 会话管理:由主Agent统一调度
  "sessions_list",
  "sessions_history",
  "sessions_send",
  "sessions_spawn",
  // 系统管理:子Agent使用存在风险
  "gateway",
  "agents_list",
  // 状态与调度:由主Agent协调处理
  "session_status",
  "cron",
];

function resolveSubagentToolPolicy(config?: Config): ToolPolicy {
  const configured = config?.tools?.subagents?.tools;
  const deny = [
    ...DEFAULT_SUBAGENT_TOOL_DENY,      // 基础限制规则
    ...(configured?.deny ?? []),        // 额外添加的限制规则
  ];
  const allow = configured?.allow;       // 可选允许列表
  return { allow, deny };
}

跨多层级的策略解析

function resolveEffectiveToolPolicy(params: {
  config?: Config;
  sessionKey?: string;
  modelProvider?: string;
  modelId?: string;
}) {
  const agentId = resolveAgentIdFromSessionKey(params.sessionKey);
  const agentConfig = resolveAgentConfig(params.config, agentId);
  const globalTools = params.config?.tools;
  const agentTools = agentConfig?.tools;

  // 获取基于配置文件的分层策略
  const profile = agentTools?.profile ?? globalTools?.profile;

  // 获取模型供应商专属的覆盖规则
  const providerPolicy = resolveProviderToolPolicy({
    byProvider: globalTools?.byProvider,
    modelProvider: params.modelProvider,
    modelId: params.modelId,
  });

  return {
    globalPolicy: pickToolPolicy(globalTools),
    agentPolicy: pickToolPolicy(agentTools),
    providerPolicy: pickToolPolicy(providerPolicy),
    profile,
  };
}

如何使用

  1. 定义工具组:将相关工具(group:fsgroup:runtime)分组,用于批量配置策略规则。
  2. 选择预设配置模板(Profile):选择一个预定义的配置模板(minimalcodingmessagingfull)作为基准。
  3. 添加显式规则:在配置模板基础上叠加允许/拒绝规则,以满足特定需求。
  4. 配置子Agent限制:为衍生生成的Agent定义额外的拒绝规则。
  5. 运行时过滤工具:使用策略匹配器在将工具传递给Agent之前,过滤可用工具。

需要规避的陷阱:

  • 过于宽泛的匹配模式:诸如*的通配符模式可能会意外授予过度权限,建议优先使用具体匹配模式。
  • 忽视拒绝规则优先级:必须先评估拒绝规则,再处理允许规则;否则允许规则可能会违背安全设计初衷。
  • 遗漏关联工具:如果允许使用exec工具,别忘了apply_patch也应被许可(它属于文件操作类工具)。
  • 继承关系混淆:子Agent的策略是在父策略基础上追加限制,而非完全替代父策略。

权衡

优势:

  • 灵活的匹配模式:通配符与分组机制可为大规模工具集构建简洁的策略。
  • 默认安全机制:默认拒绝语义可防止意外授予权限。
  • 分层管控:无需修改父级策略,即可进一步对子Agent进行权限限制。
  • 配置文件预设:针对编码、消息类等常见Agent类型提供预配置策略。
  • 插件支持:工具组可通过动态发现机制纳入插件类工具。

劣势/注意事项:

  • 模式复杂度:类正则表达式模式易造成混淆,模式语法错误可能导致意外授予权限。
  • 策略膨胀:拥有不同策略的大量Agent会增加管理与审计的难度。
  • 评估顺序风险:必须严格执行“先拒绝后允许”的优先级规则,否则漏洞可能引发安全问题。
  • 关联工具歧义:判断哪些工具属于“关联工具”(如execapply_patch)具有主观性,且可能无法覆盖所有场景。

参考文献

关键词

涵盖Clawdbot项目中的工具策略、PI工具策略、沙箱专用策略三类核心策略文件,以及关联的“出站锁定(无数据泄露通道)”安全模式。

直译

来源摘要

正在获取来源并生成中文摘要…

来源: https://github.com/clawdbot/clawdbot/blob/main/src/agents/tool-policy.ts

← 返回社区