tracking: 外呼与输入校验面(4 项)—— 外部可控输入未被当作不可信数据
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 12/100
- Issue type
- Bug
- Clarity
- Needs clarification
- Activity status
- Active
- Domain
- networking, security
Research direction
This is a tracking issue with no code of its own; the four child issues (#169 to #172) are the real work, and they must land in order. Start by reading src/tools/policy_domain/url_guard.rs around validate_url and is_private_ip, then src/tools/http_call.rs (encode_segment, build_url), src/tools/a2a.rs (the base interpolation and task_id check), and src/tools/install_skill.rs (slug and extract_zip). Done means the four cluster acceptance checks in the body pass, each with negative tests for the bypass cases.
Written by the indexing model from the issue text.
Description
优先级 P1 · 依赖:无 · 类型:tracking(簇设计入口,不产出代码;子单全部落地后关闭)
记号:正文中的「不变量 #N」指.dev/AGENTS.md编号,不是 issue 编号;#150这类才是 issue。本簇子单头部写depends-on: #N表示落地串行(同文件/同契约),不会与语义阻塞混淆。
基线 HEAD = 14bac15a(子单同基线)。本单来自一次全仓审计:先按根因归簇,再拆成可独立验收的子单。
共同根因
四条都是「外部可控输入没有被当成不可信数据」,集中在 url_guard 与两个外呼工具:
- SSRF 只查字面主机(#169):
src/tools/policy_domain/url_guard.rs:58-145对Host::Domain仅拦localhost/*.localhost/metadata.google.internal与 IP 字面写法,全仓无自定义 DNS resolver(连接期不校验),且is_private_ip漏100.64/10、0/8(除0.0.0.0)、::a.b.c.d、64:ff9b::/96。169.254.169.254.nip.io这类名字直接放行(重定向绕过已由Policy::none关闭,剩下的就是 DNS 面)。 - 路径逃逸(#170):
src/tools/http_call.rs:437-447的encode_segment保留.,整段..经build_url(:577-586)交给Url::set_path后被解析消解,且不校验最终 path 是否仍以运维 base path 为前缀 → 请求打到网关根路径并带着运维鉴权头。 - shell 注入(#171):
validate_url返回原始输入(url_guard.rs:121),a2a_call把base直接插进单引号命令(src/tools/a2a.rs:272-292),只对task_id做了白名单(:389-409)→ 提前闭合引号的curl|sh可被注入。 - 技能安装缺完整性(#172):
install_dir放行 slug"."(src/tools/install_skill.rs:150-156)→dest变成共享根;extract_zip剥第一层后可在dest内覆盖兄弟技能(:228-258);默认跟随重定向、read_to_end无上限(:290-338、:350-363);slug 未编码即拼 URL(:454-497)。
子单与依赖
| 子单 | 依赖 | 范围 |
|---|---|---|
| #169 | 无 | 自定义 reqwest Resolve:解析结果过 is_private_ip 再连;补齐私网/保留段 |
| #170 | depends-on: #169 | 拒绝 ./.. 参数;断言最终 path 以 base path 为前缀 |
| #171 | depends-on: #170 | 用规范化 Url;拒绝 ' " \ $ \ 空白`;优先 argv/env 传参 |
| #172 | depends-on: #171 | slug 白名单 [A-Za-z0-9_-]+;redirect(none) + sha256 pin;entry/总量双上限 |
落地思路
- #169 不需要新依赖(不变量 #6 不受影响):本机 reqwest 0.12.28 已提供
ClientBuilder::dns_resolver与reqwest::dns::Resolve;把校验放在 resolver 里比放在validate_url里更难绕过(连接期生效)。 - 顺序按同文件串行:#169 与 #171 都动
url_guard.rs,#170/#171 都动src/tools/与 URL 拼装,#172 依赖同一套出站校验约定。 - #170 加的是不变量式断言(
final_url.path().starts_with(base_path)),比逐个过滤字符更可靠。 - 与 B 簇在
url_guard.rs(validate_url被权限面复用)相邻;与 G 簇在src/tools/相邻,按全局顺序错开。
簇级验收
WebFetch/a2a对nip.io形态、DNS rebinding 形态与100.64.x.x全部被拒;http_call的id=".."构建失败;正常id="42"路径为/{base}/42;a2a_call收到含'/空白/;的 URL 时返回错误而非生成可执行命令;install_skill拒 slug".";含..//绝对路径/超限的 zip 被拒且无文件落盘。
边界
不含鉴权与租户身份(G 簇 #152、#146);不含 SKILL 内容来源的信任模型(#64/#76/#77 已定的 SkillSource 边界)。
实施顺序、PR 拆分与验收矩阵(补充 · 2026-10-10)
顺序(四单同域,串行):#169 → #170 → #171 → #172。
理由:#169 与 #171 都动 url_guard.rs;#170 与 #171 都动 URL 拼装;#172 依赖同一套"出站 URL 必须经校验"的约定。四单都不大,串行成本可接受。
跨簇重叠:url_guard.rs(validate_url)被权限面复用(B 簇 #153 只改工具名,不改 URL 逻辑,实际不冲突);src/tools/ 目录级重叠与 G 簇相邻。
PR 拆分:一子单一 PR。每个 PR 附"绕过尝试"的负向测试(本簇的价值就在于此)。
簇级验收 → 测试映射:见正文四条,落在 src/tools/policy_domain/url_guard.rs、src/tools/http_call.rs、src/tools/a2a.rs、src/tools/install_skill.rs 的单测中;#169 建议加一个 reqwest resolver 的单测(可用 Resolve trait 的纯函数部分)。
- Dominant language
- Rust
- Stars
- 4
- Forks
- 0
- Avg merge
- 5h 32m
- Merged PRs (30d)
- 7
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from jeffkit/recursive
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
jeffkit/recursive#159 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 15/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 15/100
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 8/100
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 12/100
Maintainers usually reply within 1 day
All issues in jeffkit/recursive
Similar issues
-
[Bug]: Web chat input doesn't regain focus after a reply finishesPossibly taken @GaijinSystems claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
zeroclaw-labs/zeroclaw#11658 ·
Maintainers usually reply within 2 days
-
good first issue help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
bytecodealliance/wasm-tools#2768 ·
Maintainers usually reply within 1 day
-
documentation
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
NuSkooler/enigma-bbs#907 ·
Maintainers usually reply within 1 day