CMDC.Plugin.Builtin.SkillGuard (cmdc v0.5.3)

Copy Markdown View Source

Skill allowed_tools 白名单 enforcer Plugin(v0.5.3+)。

当某些 Skill 在 SKILL.md frontmatter 声明 allowed_tools 字段时, 本 Plugin 在 :before_tool hook 计算 active skills 的 allowed_tools 并集, 阻止不在白名单内的工具调用。

SKILL.md frontmatter 示例

---
name: elixir-testing
description: Elixir 测试最佳实践
allowed_tools:
  - read_file
  - grep
  - shell
---

使用

skills = CMDC.Skill.discover([{:project, "./.cmdc/skills"}])

CMDC.create_agent(
  model: "anthropic:claude-sonnet-4-5",
  tools: [CMDC.Tool.ReadFile, CMDC.Tool.Grep, CMDC.Tool.WriteFile, CMDC.Tool.Shell],
  plugins: [
    {CMDC.Plugin.Builtin.SkillGuard,
     skills: skills,
     active: ["elixir-testing"],
     enforce_mode: :strict}
  ]
)

选项

  • :skills —— [CMDC.Skill.t()] Plugin 启动时全量传入(与 Options.skills_dirs 自动 discover 的 Skills 一致)
  • :active —— [String.t()] 当前激活的 Skill name 列表;默认全部 :skills 都激活
  • :enforce_mode —— :strict(默认,block_tool)/ :warn(仅日志不 block)

allowed_tools 并集计算规则

  • 无 active skillactive = []:unrestricted(所有工具允许)
  • 任一 active skill 的 allowed_tools 为 nil:unrestricted(该 skill 显式声明不限制,并集自然不限制)
  • 所有 active skills 都设了 allowed_tools → 取并集,工具调用必须在并集内

并集语义(而非交集)让"用户主动激活某 skill 就放开对应工具"语义自然。

block_tool 返回示例

{:error, "Tool 'edit_file' not in active Skills' allowed_tools whitelist " <>
         "(active: ["elixir-testing"], allowed: ["read_file", "grep", "shell"])"}

优先级

priority/0 返回 20,在 SecurityGuard(10)之后但在大多数业务 Plugin 之前。

与其他 Plugin 的关系

  • SecurityGuard 优先 → 路径 / 命令黑名单先 enforce
  • SkillGuard 次之 → Skill 上下文白名单 enforce
  • 业务 Plugin (priority >= 100) 最后

不影响的行为

  • 工具列表本身(Options.tools)不改 —— Plugin 只在调用时拦截,不"卸载"工具
  • 不影响 SystemPrompt 注入 —— Skill 的 description/snippet 照常注入
  • 不影响 Skill 加载链路(Skill.discover / Skill.load

Summary

Functions

计算指定 active skills 的 allowed_tools 并集。

Functions

compute_allowed_tools(skills_index, active_names)

@spec compute_allowed_tools(%{required(String.t()) => CMDC.Skill.t()}, [String.t()]) ::
  :unrestricted | [String.t()]

计算指定 active skills 的 allowed_tools 并集。

返回:

  • :unrestricted —— 无 active skill 或 任一 active skill 未设 allowed_tools
  • [String.t()] —— allowed_tools 并集(去重)

示例

iex> skills = %{
...>   "a" => %Skill{name: "a", description: "", path: "/", allowed_tools: ["read", "write"]},
...>   "b" => %Skill{name: "b", description: "", path: "/", allowed_tools: ["grep"]}
...> }
iex> CMDC.Plugin.Builtin.SkillGuard.compute_allowed_tools(skills, ["a", "b"])
["read", "write", "grep"]

iex> CMDC.Plugin.Builtin.SkillGuard.compute_allowed_tools(%{}, [])
:unrestricted