Skip to content

Commit cfed395

Browse files
author
SqlRush
committed
Canonicalize WebSearch domain filters
1 parent 11f01af commit cfed395

4 files changed

Lines changed: 51 additions & 3 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,8 @@ M5 补充:WebSearch `query` schema 现在按官方 `min(2)` 约束拒绝单字
443443

444444
M5 补充:WebSearch domain filters 现在在 schema 层声明 array `items:string`,通用 tool schema validator 同步支持 `items` 校验;`allowed_domains`/`blocked_domains` 会拒绝空字符串、URL/port、非法 wildcard 和非域名 label。
445445

446+
M5 补充:WebSearch domain filters 现在会 canonicalize 为小写、去尾点、去 `*.` 前缀并去重;过滤匹配和 structured content 暴露同一域名口径。
447+
446448
M5 补充:通用 tool schema validator 现在支持 `enum`,可直接执行 Grep output mode、NotebookEdit edit mode/cell type、Todo status/priority、Task target/action、LSP severity 等工具 schema 的枚举契约。
447449

448450
M5 补充:通用 tool schema validator 现在支持数字 `minimum`/`maximum`,可直接执行 LSPDiagnostics `limit` 等工具 schema 的数值范围契约。

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ test/parity/ # golden tests against TS/official behavior
210210
- 本轮补充:WebSearch JSON result parser 现在支持 `pageUrl`/`targetUrl`/`source_url`/`formattedUrl` 等 URL aliases、`htmlTitle`/`htmlSnippet` 等 HTML 标记字段清理、嵌套 URL object,以及 `deepLinks`/`siteLinks` 子结果递归解析。
211211
- 本轮补充:WebSearch JSON parser 现在继续覆盖 `answerBox`/`answer_box``knowledgeGraph`/`knowledge_graph``news`/`news_results``topStories``peopleAlsoAsk``related_questions` 等常见搜索后端 wrapper,并识别 `website`/`sourceLink` URL alias、`question` title alias、`answer`/`excerpt` snippet alias。
212212
- 本轮补充:WebSearch `query` schema 现在按官方 `min(2)` 约束拒绝单字符查询,通用 tool schema validator 同步支持 `minLength`,让工具定义可直接表达字符串最小长度契约。
213+
- 本轮补充:WebSearch domain filters 现在会 canonicalize 为小写、去尾点、去 `*.` 前缀并去重;过滤匹配和 structured content 暴露同一域名口径。
213214
- 本轮补充:Bash/PowerShell 前台执行现在区分调用方 cancellation、timeout 和普通非零退出;context 取消会返回 `cancelled=true``timed_out=false``exit_code=-1`,并显示 cancelled 状态文本。
214215
- 本轮补充:Bash 和 Unix PowerShell 工具取消现在会先向受管 process group 发 SIGTERM,再用短 `WaitDelay` 兜底 SIGKILL;Bash 前台/后台取消测试通过 `trap TERM` 证明 cleanup signal 可被脚本收到。
215216
- 本轮补充:Bash read-only classifier 现在把常见文件比较命令 `diff`/`cmp` 归入只读路径命令族,并明确拒绝 `diff --output` 这类写文件形态以及越界路径,减少安全工具误走权限确认。

internal/tools/web/web_search.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -818,15 +818,26 @@ func webSearchBlockedDomainsRaw(input webSearchInput) []string {
818818

819819
func normalizeDomains(domains []string) []string {
820820
out := make([]string, 0, len(domains))
821+
seen := make(map[string]struct{}, len(domains))
821822
for _, domain := range domains {
822-
domain = strings.TrimSpace(strings.ToLower(domain))
823-
if domain != "" {
824-
out = append(out, domain)
823+
domain = canonicalWebSearchDomain(domain)
824+
if domain == "" {
825+
continue
826+
}
827+
if _, ok := seen[domain]; ok {
828+
continue
825829
}
830+
seen[domain] = struct{}{}
831+
out = append(out, domain)
826832
}
827833
return out
828834
}
829835

836+
func canonicalWebSearchDomain(domain string) string {
837+
domain = strings.TrimSuffix(strings.ToLower(strings.TrimSpace(domain)), ".")
838+
return strings.TrimPrefix(domain, "*.")
839+
}
840+
830841
func validateDomains(field string, domains []string) error {
831842
for i, domain := range domains {
832843
if strings.Contains(domain, "://") || strings.ContainsAny(domain, "/?#") {

internal/tools/web/web_search_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,40 @@ func TestWebSearchReturnsParsedResults(t *testing.T) {
6868
}
6969
}
7070

71+
func TestWebSearchCanonicalizesDomainFilters(t *testing.T) {
72+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
73+
w.Header().Set("Content-Type", "text/html; charset=utf-8")
74+
_, _ = w.Write([]byte(`
75+
<html><body>
76+
<a href="https://docs.example.com/one">One</a>
77+
<a href="https://blocked.example.net/two">Two</a>
78+
</body></html>`))
79+
}))
80+
defer server.Close()
81+
executor := webExecutor(t)
82+
result, err := executor.Execute(tool.Context{
83+
Context: context.Background(),
84+
Metadata: map[string]any{
85+
MetadataWebSearchEndpointKey: server.URL + "/search",
86+
},
87+
}, contracts.ToolUse{
88+
ID: "toolu_search_domain_canonical",
89+
Name: "WebSearch",
90+
Input: json.RawMessage(`{"query":"domain filters","allowed_domains":["*.Example.COM.","example.com"]}`),
91+
}, nil)
92+
if err != nil {
93+
t.Fatal(err)
94+
}
95+
results := result.StructuredContent["results"].([]map[string]any)
96+
if len(results) != 1 || results[0]["url"] != "https://docs.example.com/one" {
97+
t.Fatalf("results = %#v", results)
98+
}
99+
allowed := result.StructuredContent["allowed_domains"].([]string)
100+
if len(allowed) != 1 || allowed[0] != "example.com" {
101+
t.Fatalf("allowed domains = %#v", allowed)
102+
}
103+
}
104+
71105
func TestWebSearchResolvesHTMLResultsAgainstBaseHref(t *testing.T) {
72106
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
73107
w.Header().Set("Content-Type", "text/html; charset=utf-8")

0 commit comments

Comments
 (0)