Files
Cyrene_v2/plugins/tools/io/ssrf_test.go
T
AskaEth 2d211c2d16 feat(backthink,browser): 自排工具面 C1-3 + 浏览器两处偏离收口(X2+X3 同文件一并落)
X2 C1-3(自排工具面与提示词):
- request_think(thinktool.go,照 transcode/tool.go 范式)+ reason 必填/urgency 闭集 + 双守门
  (安全闸 + owner 判定:群聊/陌生人一律拒)
- **成对**进两张唤醒态允许集(quiet + awake;CAPACITY_TOOL_SETS 是整体替换)
- 提示词抽共用段落 + 自排前缀优先(不枚举工具清单);面板加 self_wake 标签与筛选轴
- 4 个变异自检(拿掉 owner 判定/只加一张表/放宽 urgency/前端标签恒等)

X3 收口(与 X2 共用 wire_services.go ⇒ 一并落):
- 删掉第三份 SSRF 实现(guard.go),改由 io.ValidateRequestURL 唯一判定 + 薄门面(保两个错误身份)
- 浏览器会话挂进 a.close(真机:真 Chrome 被关、无残留、生产未扰动)
- a.wireBrowser() 归位到 wire_runtime.go,删掉 ioToolOptions 的临时挂点
- 5 个变异自检

顺带补提交 C1-2 漏掉的 internal/config/runtimeconfig/backthink_test.go(ed50053 漏 add)。
2026-10-06 21:16:23 +08:00

81 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package io
import (
"context"
"errors"
"testing"
)
// TestValidateRequestURL_DeniedByDefault 钉住**共享判定本体**的默认拒绝面(收口 X3):
// 收口之前浏览器侧另有一份同策实现,两份各自被自己的用例覆盖;收口之后浏览器侧
// (`internal/capability/browser` 的守卫用例)与本用例打的是**同一张表、同一份代码**。
func TestValidateRequestURL_DeniedByDefault(t *testing.T) {
t.Parallel()
denied := []string{
"http://127.0.0.1/",
"http://[::1]/",
"http://192.168.1.10/admin",
"http://10.0.0.5/",
"http://172.16.3.4/",
"http://169.254.169.254/latest/meta-data/",
"http://0.0.0.0/",
"file:///etc/passwd",
"ftp://example.com/",
"http:///no-host",
"",
}
for _, raw := range denied {
if _, err := ValidateRequestURL(context.Background(), raw, nil); err == nil {
t.Errorf("ValidateRequestURL(%q) = nil,应当拒绝", raw)
}
}
// 网段拒绝必须**保住 ErrSSRF 身份**:调用方(含浏览器侧的门面)按 errors.Is 分类降级。
if _, err := ValidateRequestURL(context.Background(), "http://127.0.0.1/", nil); !errors.Is(err, ErrSSRF) {
t.Errorf("回环目标的错误丢了 ErrSSRF 身份: %v", err)
}
}
// TestValidateRequestURL_AllowlistEntryForms 钉住白名单条目的**全部写法**都按主机名匹配。
// 这是收口 X3 的回归网:完整 URL 写法(以及裸 IPv6 字面量)原先只有被删掉的那份
// 浏览器侧实现认;合并成一份时若丢掉它,面板里写 `http://host:port` 的白名单会**静默
// 失效**(改前生效、改后不生效,没有一条日志会说出这件事)。
func TestValidateRequestURL_AllowlistEntryForms(t *testing.T) {
t.Parallel()
cases := []struct{ entry, raw string }{
{"127.0.0.1", "http://127.0.0.1:7777/x"},
{"127.0.0.1:9", "http://127.0.0.1:7777/x"},
{"http://127.0.0.1:9999/", "http://127.0.0.1:7777/x"},
{"localhost", "http://localhost:8080/x"},
{"LocalHost:8080", "http://LOCALHOST/x"},
{"http://localhost:8080", "http://localhost/x"},
{"[::1]", "http://[::1]:9999/x"},
{"::1", "http://[::1]:9999/x"},
}
for _, tc := range cases {
if _, err := ValidateRequestURL(context.Background(), tc.raw, []string{tc.entry}); err != nil {
t.Errorf("条目 %q 未放行 %q: %v", tc.entry, tc.raw, err)
}
}
}
// TestValidateRequestURL_AllowlistDoesNotOverreach 钉住**放行的边界**:白名单只放行它
// 自己那条主机名,且不依赖 DNS 的放行面照旧(公网字面 IP)。
func TestValidateRequestURL_AllowlistDoesNotOverreach(t *testing.T) {
t.Parallel()
if _, err := ValidateRequestURL(context.Background(), "http://93.184.216.34/", nil); err != nil {
t.Fatalf("公网字面 IP 被拒: %v", err)
}
// 白名单按主机名字符串比较:写 `localhost` 不等于放行 `127.0.0.1`(是两个名字)。
if _, err := ValidateRequestURL(context.Background(), "http://127.0.0.1/", []string{"localhost"}); !errors.Is(err, ErrSSRF) {
t.Errorf("条目 localhost 不该放行 127.0.0.1(实得 %v)", err)
}
// 无关的 URL 条目(主机是别的名字)不能被「看谁像 URL」放过。
if _, err := ValidateRequestURL(context.Background(), "http://127.0.0.1/", []string{"http://other:1/"}); !errors.Is(err, ErrSSRF) {
t.Errorf("无关的 URL 条目不该放行 127.0.0.1(实得 %v)", err)
}
// 空条目不能让任何主机命中(面板里删空一行 = 不放行任何东西)。
if _, err := ValidateRequestURL(context.Background(), "http://127.0.0.1/", []string{"", " "}); !errors.Is(err, ErrSSRF) {
t.Errorf("空白条目不该放行任何主机(实得 %v)", err)
}
}