IronClaw GitHub 扩展完全指南:49 个工具的 WASM 能力面、凭据注入与 Webhook 归一化

原创2026-09-22 17:40:301,694 阅读
文章标签:人工智能AI 应用AI Agent本地部署MCP Clients提示词注入防护RAG插件系统

IronClaw GitHub 扩展完全指南:49 个工具的 WASM 能力面、凭据注入与 Webhook 归一化

本篇技术指南以 IronClaw 开源仓库中 github 扩展包 为核心,系统讲解这个以 WASM guest 形式交付、覆盖仓库/Issue/PR/评审/文件/Release/Actions 等 49 个工具的数据型扩展:包括 manifest 声明与工具注册模型、[auth.github] 凭据体系与宿主侧 egress 安全边界、WASM 源码内部的请求路由与参数校验实现,以及 Webhook 事件归一化到系统事件的工作机制。读完你将掌握该扩展从"安装清单"到"字节级实现"的完整脉络,并能在 IronClaw 中以源码为据排查 GitHub 工具的授权、校验与错误分类问题。

扩展一览:目录中工具面最大的数据型包

github 是 IronClaw 扩展目录(crates/extensions/packages/)中 工具面最大的包:扩展 id 为 github,共 49 个工具(从 github.get_repo 到 github.handle_webhook),并附带 [auth.github] 认证配置。它被归类为 data-only 包——没有独立 crate,行为完全以 WASM guest 形式随包交付。

维度 值
扩展 id github
工具面 49 个工具(github.get_repo … github.handle_webhook)+ [auth.github]
Vendor(凭据权威) github
运行时 wasm —— 已提交产物在 wasm/,guest 源码在 wasm-src/(被排除在工作区构建图之外)
包内容 manifest.toml、prompts/、schemas/、wasm/、wasm-src/
嵌入方式 由 ironclaw_extension_support::packages::github 通过 include_str!/include_bytes! 内联
测试 / 检查 manifest 投影:cargo test -p ironclaw_extension_registry;产物新鲜度:python3 scripts/ci/check-wasm-artifact-freshness.py

数据型包之所以"无需 crate",遵循的是 crates/extensions/AGENTS.md 中明确的 package-to-crate 规则:只有实现 channel adapter 或 provider surface、或携带重量级原生依赖的包才需要自己的 crate;其余包是一个由 manifest + 资源数据构成的目录,需要原生(非 WASM)工具逻辑时,作为模块放进共享的 ironclaw_extension_support crate,并以包的 manifest 身份注册。github 恰好属于"纯 WASM 交付"一类,因此整个行为面都在 wasm/github_tool.wasm 中。

包目录解剖:五类资产的各自职责

crates/extensions/packages/github/
├── manifest.toml        # 扩展声明:schema v3、运行时、49 个工具、[auth.github]
├── prompts/github/      # 每个工具的模型可见 prompt 文档(49 个 .md)
├── schemas/github/      # 每个工具的输入/输出 JSON Schema(.input.v1.json / .output.v1.json)
├── wasm/
│   └── github_tool.wasm # 已编译并提交的 WASM guest 产物
└── wasm-src/            # guest 源码(独立 Cargo 工程,不参与工作区构建)
    ├── Cargo.toml
    └── src/
        ├── lib.rs           # Guest trait 实现:execute / schema / description
        ├── dispatch.rs      # capability_id → 具体操作的派发
        ├── request.rs       # GitHub REST 请求构造与宿主 egress 调用
        ├── response.rs      # 响应处理
        ├── schema.rs        # 静态 JSON Schema 生成
        ├── types.rs         # GitHubAction 等反序列化类型
        ├── validation.rs    # 输入长度/路径/ref/搜索限定符校验
        ├── webhook.rs       # Webhook 归一化为系统事件
        └── api/             # 按领域拆分的 API 实现(actions/contents/issues/pulls/repos/review_threads/search)

artifact boundary(产物边界) 是这套结构的关键约束:已提交的 wasm/*.wasm 由带外构建产生(./scripts/build-wasm-extensions.sh --first-party),wasm-src/ 的 guest crate 被排除在工作区构建图之外;scripts/ci/check-wasm-artifact-freshness.py 把每个已提交产物与其 wasm-src/ 目录树的摘要(记录在 scripts/ci/wasm-src-digests.toml)绑定——修改 wasm-src/ 而不重建并重新记录摘要会导致 CI 失败。也就是说,扩展的行为以"源码 → 摘要 → 产物"的三方一致为交付前提。

manifest 声明:49 个工具的注册模型

manifest.toml 以 schema_version = "reborn.extension_manifest.v3" 声明(当前包版本 0.2.8,trust = "first_party_requested"),运行时块指向 WASM 模块:

schema_version = "reborn.extension_manifest.v3"
id = "github"
name = "GitHub"
version = "0.2.8"
description = "GitHub repository, issue, pull request, search, branch, file, release, workflow, fork, and webhook capabilities."
trust = "first_party_requested"

[runtime]
kind = "wasm"
module = "wasm/github_tool.wasm"

每个工具都通过 [[tools]] 数组条目声明,携带四组关键元数据,以 github.get_repo 为例:

[[tools]]
origin_gate_matrix = { loop_run = "gated_unless_granted", product = "forbidden", automation = "forbidden" }
id = "github.get_repo"
description = "Get a GitHub repository."
effects = ["network", "use_secret"]
default_permission = "allow"
visibility = "model"
input_schema_ref = "schemas/github/get_repo.input.v1.json"
prompt_doc_ref = "prompts/github/get_repo.md"
  • origin_gate_matrix:所有 GitHub 工具统一为 loop_run = "gated_unless_granted"(循环运行中默认受门控、除非被授予)、product 与 automation 均为 forbidden——即该工具面只对 agent loop 开放,产品直连与自动化流程被禁止。
  • effects:读操作为 ["network", "use_secret"];写操作(创建/更新/删除、external_write 类副作用)追加 "external_write"。
  • default_permission:只读查询类工具为 allow(如 get_repo、list_issues、get_pull_request、get_workflow_runs、get_job_logs 等);一切产生外部写入的工具为 ask(如 create_repo、create_issue、merge_pull_request、trigger_workflow、delete_file 等)。值得注意的两个特例:github.search_code 虽是读操作却默认为 ask(代码搜索内容敏感,需要显式确认);github.handle_webhook 无任何 effects(纯本地归一化)但同样为 ask。
  • input_schema_ref / prompt_doc_ref:分别指向 schemas/github/*.input.v1.json 与 prompts/github/*.md;仅 github.get_job_logs 额外声明 output_schema_ref = "schemas/github/raw_output.v1.json"(纯文本日志输出)。

49 个工具全景

按能力域分类(均为 github. 前缀):

能力域 工具
仓库 get_repo、create_repo、list_repos、fork_repo、get_authenticated_user
Issues list_issues、get_issue、create_issue、update_issue、list_issue_comments、create_issue_comment、comment_issue(兼容别名)、add_issue_labels、remove_issue_label、add_issue_assignees、remove_issue_assignees
Pull Requests list_pull_requests、get_pull_request、create_pull_request、update_pull_request、get_pull_request_files、merge_pull_request、get_combined_status
评审与评论 create_pr_review、list_pull_request_comments、reply_pull_request_comment、get_pull_request_reviews、list_pull_request_review_threads、resolve_review_thread、unresolve_review_thread
搜索 search_repositories、search_code、search_issues_pull_requests、search_issues(兼容别名)
分支与文件 list_branches、create_branch、get_file_content、create_or_update_file、delete_file
Releases list_releases、create_release
GitHub Actions trigger_workflow、get_workflow_runs、get_workflow_run_jobs、get_job_logs、get_workflow_run_artifacts、rerun_failed_workflow_run_jobs、rerun_workflow_job
Webhook handle_webhook

manifest 中对两个"兼容别名"有明确说明:github.comment_issue 是 github.create_issue_comment 的别名;github.search_issues 是 github.search_issues_pull_requests 的紧凑结果别名。这类别名在 WASM 派发层被归一(见下文 dispatch.rs),模型侧不会感知到两套实现。

认证与凭据安全:[auth.github] 与宿主 egress 边界

github 扩展的凭据体系在 manifest 尾部以 [auth.github] 声明,采用 API Key 方式,配以在线校验:

[auth.github]
method = "api_key"
display_name = "GitHub personal access token"
fields = [ { handle = "github_runtime_token", label = "Personal access token", secret = true } ]
validation = { method = "GET", url = "https://api.github.com/user", success_status = [200], inject = { handle = "github_runtime_token", type = "header", name = "authorization", prefix = "Bearer " } }
  • 凭据句柄固定为 github_runtime_token,标记为 secret,产品层展示名为 "GitHub personal access token"。
  • 绑定校验:对 GET https://api.github.com/user 发起请求,仅当返回 200 时认为凭据有效;该校验请求以 Bearer <token> 形式注入 authorization 头。

每次调用时的凭据注入

每个工具的 [[tools.credentials]] 声明了运行时凭据注入协议,全包统一:

[[tools.credentials]]
handle = "github_runtime_token"
vendor = "github"
audience = { scheme = "https", host = "api.github.com" }
injection = { type = "header", name = "authorization", prefix = "token " }
placeholder_env = "GH_TOKEN"

其要点是 凭据不进入 WASM guest,而是由宿主在 egress(出站)时注入。manifest 注释也引用了 GitHub CLI 的同类做法:Authorization: token <GH_TOKEN>。这意味着 guest 代码(wasm-src/src/lib.rs 文件头注释)"从不读取或构造 GitHub token"——token 只在宿主侧按 audience(仅 api.github.com)匹配后附加到请求头。placeholder_env = "GH_TOKEN" 则作为工具说明中的环境变量占位符供模型理解。

重定向白名单:get_job_logs 的 Azure blob 跳转

github.get_job_logs 是唯一声明了额外 network_targets 的工具,对应 GitHub Actions 日志接口 302 重定向到短期预签名 Azure blob URL 的行为:

# The logs endpoint 302-redirects to a short-lived, pre-signed Azure blob URL
# (e.g. productionresultssaN.blob.core.windows.net). The host egress follows the
# redirect only after re-authorizing the destination against this allowlist and
# strips the Authorization header on the hop, so the GitHub token is never sent
# to blob storage. No credential is injected for these hosts.
network_targets = [
  { scheme = "https", host_pattern = "*.blob.core.windows.net" },
]

这是凭据防泄漏的一个典型设计:宿主 egress 仅在目标地址通过 *.blob.core.windows.net 白名单复核后才跟随重定向,并在跳转那一跳剥离 Authorization 头——GitHub token 永远不会被送往 blob 存储;该目标也不注入任何凭据。其余 48 个工具的网络目标只有 api.github.com 这一个 audience。

WASM guest 内部:从 capability_id 到 HTTP egress 的完整调用链

扩展的行为在 wasm-src/ 中以独立 Rust crate(github-tool,edition 2021,crate-type = <a href="https://link.gitcode.com/i/8db0e61fa930fe73cce18559225bc5aa" target="_blank">"cdylib"],release 配置 opt-level = "z" + lto + panic = "abort" + strip)实现,通过 WIT 接口 sandboxed-tool(见 [crates/lanes/ironclaw_wasm/wit/tool.wit)与宿主交互。

入口:Guest trait 三件套

lib.rs 实现 exports::near::agent::tool::Guest:

  • execute(req):把请求参数与调用上下文交给 dispatch::execute_inner,成功返回 Response::Success,失败则取出最近一次 401 响应中捕获的 provider message 组装 GuestFailure(含 kind、code、message 三个字段)。
  • schema():返回静态生成的 JSON Schema(oneOf 形式,每个动作一个分支),供宿主在工具发现时使用。
  • description():一段面向模型的能力描述。

派发:操作由宿主上下文决定,而非参数形状

dispatch.rs 的关键设计是 动作来源与参数形状解耦:

pub(crate) fn action_from_context(context: Option<&str>) -> Result<String, String> {
    let context = context.ok_or_else(|| "missing_invocation_context".to_string())?;
    let context: ToolContext =
        serde_json::from_str(context).map_err(|_| "invalid_invocation_context".to_string())?;
    action_name_from_capability_id(&context.capability_id)
        .ok_or_else(|| "unsupported_github_capability".to_string())
}

宿主通过调用上下文的 capability_id(如 github.get_issue)选择操作;随后 params_with_action 把动作名注入参数对象(并拒绝参数中已含 action 键),再反序列化为强类型的 GitHubAction 枚举,最后 match 分发到 api/ 各模块的具体函数。lib.rs 测试 operation_comes_from_host_context_not_param_shape 直接验证了这条约定:github.get_issue → get_issue、github.comment_issue → create_issue_comment(别名归一)、github.get_authenticated_user → get_authenticated_user;而缺失上下文返回 missing_invocation_context、未知 capability 返回 unsupported_github_capability。

同时 params_with_action 中 serde_json::from_str(params) 的反序列化是 默认拒绝未知字段 的(deny_unknown_fields),测试 serde_rejects_unknown_fields_before_egress 证明多余字段会在发起任何网络请求前被 invalid_parameters 拒绝。

请求层:固定的 API 常量与宿主 http_request

request.rs 定义了三个固定常量:

const GITHUB_API_ROOT: &str = "https://api.github.com";
const GITHUB_API_VERSION: &str = "2026-03-10";
const HTTP_TIMEOUT_MS: u32 = 10_000;

每次请求固定携带四个头:Accept: application/vnd.github+json、Content-Type: application/json、X-GitHub-Api-Version: 2026-03-10、User-Agent: IronClaw-GitHub-Reborn-WASM,然后调用宿主提供的 crate::near::agent::host::http_request(method, url, headers, body, timeout)(超时 10 秒)。响应处理有专门的分类逻辑:

  • 2xx:返回 JSON 文本(空 body 时返回 {"status": <code>});
  • 422 且响应体形如 GitHub 的 {"message": "Validation Failed", "errors": [...]}:归为 github_api_error_status_422_validation(输入错误);单纯 "Validation failed, or the endpoint has been spammed." 这类消息不会被当作输入错误,避免误分类滥用风控响应;
  • 401:从响应体提取 message 字段(Bad credentials 等),截断到最多 512 字符存入线程局部槽位,供 execute 拼进 guest-failure.message 随 AuthRequired 一起传给宿主上的认证门控;
  • 其余状态码:github_api_error_status_<code>。

宿主侧 HttpFailure 也会被映射为语义化错误码:AuthRequired、invalid_parameters、github_api_body_limit(输出过大)、github_api_egress_denied / github_api_redirect_denied(网络被拒)、github_api_request_failed 等。

错误分类到 ErrorKind

lib.rs 的 guest_error_kind 把上述字符串错误码收敛到 WIT 层的五类 ErrorKind:

ErrorKind 典型错误码
AuthRequired AuthRequired、github_api_error_status_401
Input invalid_parameters、invalid_repository、invalid_query_*、invalid_* 系列、github_api_error_status_422_validation、字符串校验错误(Invalid labels:...、Invalid assignees:...、invalid_comments:...)
OutputTooLarge github_api_body_limit
Executor github_api_timeout、github_api_executor_failed
NetworkDenied github_api_egress_denied、github_api_redirect_denied
Client github_api_error_status_403、github_api_error_status_429
OperationFailed 其余一切(含通用 422)

测试 guest_error_kind_does_not_classify_generic_422_as_input 特意锁死了"通用 422 不归为输入错误"这一边界。

输入校验:出站前的第一道防线

validation.rs 在 egress 之前完成全部输入校验,三个关键上限常量:

const MAX_TEXT_LENGTH: usize = 65536;          // 单字段文本上限(body、path 等)
const MAX_SEARCH_QUERY_LENGTH: usize = 512;    // 搜索查询上限
const MAX_REPOSITORY_SEGMENT_LENGTH: usize = 100; // 仓库名/owner 段上限

主要校验规则(均有测试覆盖):

  • 路径安全:validate_repo_path 拒绝 ..、. 相对段与空段(Invalid path: relative path segments not allowed / Invalid path: empty segment not allowed),路径段按 / 拆分后逐段百分号编码;
  • Git ref 校验:validate_git_ref 拒绝包含 ..、:、?、[、\、^、~、@{、//、以 //. 开头或结尾、以 .lock 结尾、含控制字符或空格的名字;normalize_ref_lookup 只允许 branch/tag ref(refs/heads/*、refs/tags/*),明确拒绝裸 commit SHA(40 位十六进制);
  • 分页与数量:page 不能为 0,limit 必须在 1..=100;order 只能是 asc/desc;add_issue_assignees 的 assignees 上限 10 个(schema maxItems: 10);
  • 搜索限定符:build_issue_search_query 支持 repo:、author:、assignee:、involves:、state:、is: 等限定符组合,且 repository 与 owner/repo 互斥(同时给出报 invalid_repository);限定符值拒绝空白、控制字符、:、"、(、);
  • 搜索排序白名单:issue 搜索只允许 GitHub 官方支持的 sort 枚举(comments、created、updated、reactions 系列、interactions),PR 列表只允许 created/updated/popularity/long-running。

这些规则的意义在于:绝大多数恶意或错误输入在发出任何网络请求之前就被拒绝(测试中反复断言 requests().is_empty()),既保护 GitHub API 调用额度,也缩小了 guest 构造出站请求的攻击面。

响应紧凑化:高基数接口的模型友好投影

manifest 顶部的注释解释了响应策略:"GitHub REST endpoints return heterogeneous provider-owned response shapes. Detail tools preserve those raw JSON contracts."——即:

  • 详情类工具(get_issue、get_pull_request、get_repo 等)原样保留 provider 的原始 JSON 契约;
  • 高基数的列表/搜索工具(list_pull_requests、search_issues_pull_requests 等)保持顶层数组形状,但把每条记录投影为紧凑的模型友好摘要,完整详情只通过对应的详情工具获取。

lib.rs 测试 list_pull_requests_compacts_realistic_large_provider_response 用一份超过 10MB 的 provider fixture 验证:100 条紧凑 PR 摘要输出被压到 100KB 以内(output.len() < 100 * 1024),同时保留 number、title、user.login、labels[].name、assignees[].login、requested_reviewers[].login、milestone.title、head.ref/sha 等模型决策所需字段,而 body、_links、head.repo 等大字段被剥离。分页语义也得到保留:list_issues 等工具把逻辑分页映射到上游 per_page+page 扫描,并设有上游页扫描上限(测试证明 10 页全是 PR 时以 github_issue_page_scan_limit 中止,避免无限翻页)。

Webhook 归一化:把 GitHub 事件翻译成系统事件意图

github.handle_webhook 不产生任何外部副作用(effects = <a href="https://link.gitcode.com/i/54ce48280cbaa5797fa1fb36870ec96f" target="_blank">]),它在 guest 内部把 GitHub webhook payload 归一化为 SystemEventIntent(来源 github)。核心逻辑在 [webhook.rs:

  1. 事件识别:从请求头取 X-GitHub-Event(缺失或为空则报 Missing X-GitHub-Event header),body 必须携带 body_json;
  2. 事件类型映射(github_event_type):
X-GitHub-Event 归一化事件类型
issues issue
pull_request pr
issue_comment pr.comment(payload 含 /issue/pull_request)或 issue.comment
pull_request_review pr.review
pull_request_review_comment pr.review_comment
pull_request_review_thread pr.review_thread
check_suite ci.check_suite
check_run ci.check_run
status ci.status
其他 原样保留

随后若 payload 含非空 action 字段,则拼接为 <base>.<action>(如 issue.opened、pr.closed、ci.check_run.completed);

  1. Payload 增强(github_enriched_payload):以 put_if_missing 语义(仅当键不存在时补入)向原 payload 注入统一的扁平字段:event、event_type、delivery_id(取自 X-GitHub-Delivery 头)、action、repository_name(/repository/full_name)、repository_owner、sender_login、issue_number、pr_number(含 issue_comment 场景的 fallback:/pull_request/number 缺失时若存在 /issue/pull_request 则取 /issue/number)、comment_author、comment_body、review_state、pr_state、pr_merged、pr_draft、base_branch、head_branch、ci_status、ci_conclusion(check_run/check_suite/status 三种来源依次兜底)。

输出结构为 { "accepted": true, "emit_events": <a href="https://link.gitcode.com/i/dce052931603ef00a88ce527a582c3ed" target="_blank">{ "source": "github", "event_type": ..., "payload": ... }] },供宿主接入统一的事件/触发管线。这样下游消费者(如 [ironclaw_triggers 生态)只需处理一套扁平、带语义字段的事件形状,而无需理解各 GitHub 事件类型的原始差异。

嵌入与产物分发:extension_support 如何装载该包

数据型包通过 PACKAGES 清单在 crates/extensions/ironclaw_extension_support/src/packages/github.rs 注册:include_str!("../../../packages/github/manifest.toml") 内联 manifest、include_bytes!("../../../packages/github/wasm/github_tool.wasm") 内联 WASM 字节,schemas/ 与 prompts/ 下的每个资产也逐个 include_bytes! 挂入资源表,并附带 setup_url: Some("https://github.com/settings/personal-access-tokens/new") 作为凭据配置引导。这意味着 github 包在编译期就整体烘焙进二进制,运行时无需从磁盘加载任何包文件。

测试与质量门禁

该扩展的可验证性体现在三个层面:

  1. Guest 内部单元测试:wasm-src/src/lib.rs 内的 #[cfg(test)] 测试模块通过 request::test_support 的 mock 响应队列,验证了从 URL 构造、HTTP 方法、请求体、分页映射到错误分类的几乎全部行为(前述各节均已引用);
  2. Manifest 投影测试:cargo test -p ironclaw_extension_registry 校验 manifest 能被 registry 正确解析、投影为 resolved 记录;
  3. 产物新鲜度门禁:python3 scripts/ci/check-wasm-artifact-freshness.py 以 scripts/ci/wasm-src-digests.toml 中的摘要校验 wasm/github_tool.wasm 与 wasm-src/ 一致;编辑 guest 源码后需先 ./scripts/build-wasm-extensions.sh --first-party 重建、再 --update 更新摘要(该命令链在 github 包 README 与 crates/extensions/AGENTS.md 中均有说明)。

此外,家族级架构门禁(如 reborn_extension_specificity.rs 禁止通用代码出现 vendor 名、reborn_dependency_boundaries.rs 限制 per-crate 依赖边)同样约束 github 这类包的代码归属,确保"vendor 行为只存在于 packages/*"这一铁律不被破坏。

参考路径速查

目的 路径
包 README(本指南依据) crates/extensions/packages/github/README.md
包声明与 49 个工具注册 crates/extensions/packages/github/manifest.toml
Guest 入口与错误分类 crates/extensions/packages/github/wasm-src/src/lib.rs
capability 派发 crates/extensions/packages/github/wasm-src/src/dispatch.rs
请求构造与宿主 egress crates/extensions/packages/github/wasm-src/src/request.rs
输入校验规则 crates/extensions/packages/github/wasm-src/src/validation.rs
Webhook 归一化 crates/extensions/packages/github/wasm-src/src/webhook.rs
包嵌入(extension_support) crates/extensions/ironclaw_extension_support/src/packages/github.rs
家族规则与 package-to-crate 规则 crates/extensions/AGENTS.md
产物新鲜度检查 scripts/ci/check-wasm-artifact-freshness.py
登录后查看全文
ironclaw