fix(web): harden exploration policy
- commit
- 964ab58beb4f603223e5a661e9769596bcc8e8d4
- author
- brice · · 2026-08-19 06:06 UTC
- parent
- 8dff8a1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +65 -16
M packages/agent/build.go +4 -2
| @@ -1266,8 +1266,10 @@ func buildToolRegistry(args Args, cwd string, sandbox *tools.Sandbox, lspEnabled | |||
| 1266 | 1266 | if orchestratorExplorationToolsAllowed(args) { | |
| 1267 | 1267 | reg["read"] = all["read"] | |
| 1268 | 1268 | reg["grep"] = all["grep"] | |
| 1269 | - | for name, tool := range tools.NewWebTools() { | |
| 1270 | - | reg[name] = tool | |
| 1269 | + | if args.PermissionSet == nil { | |
| 1270 | + | for name, tool := range tools.NewWebTools() { | |
| 1271 | + | reg[name] = tool | |
| 1272 | + | } | |
| 1271 | 1273 | } | |
| 1272 | 1274 | } | |
| 1273 | 1275 | return reg | |
| @@ -1266,8 +1266,10 @@ func buildToolRegistry(args Args, cwd string, sandbox *tools.Sandbox, lspEnabled | |||||
| 1266 | if orchestratorExplorationToolsAllowed(args) { | 1266 | if orchestratorExplorationToolsAllowed(args) { | ||
| 1267 | reg["read"] = all["read"] | 1267 | reg["read"] = all["read"] | ||
| 1268 | reg["grep"] = all["grep"] | 1268 | reg["grep"] = all["grep"] | ||
| 1269 | - | for name, tool := range tools.NewWebTools() { | 1269 | + | if args.PermissionSet == nil { |
| 1270 | - | reg[name] = tool | 1270 | + | for name, tool := range tools.NewWebTools() { |
| 1271 | + | reg[name] = tool | |||
| 1272 | + | } | |||
| 1271 | } | 1273 | } | ||
| 1272 | } | 1274 | } | ||
| 1273 | return reg | 1275 | return reg | ||
M packages/agent/build_orchestrator_test.go +14 -2
| @@ -12,30 +12,42 @@ func TestOrchestratorAlwaysReceivesResearchTools(t *testing.T) { | |||
| 12 | 12 | name string | |
| 13 | 13 | args Args | |
| 14 | 14 | wantResearch bool | |
| 15 | + | wantWeb bool | |
| 15 | 16 | wantWriteTool bool | |
| 16 | 17 | }{ | |
| 17 | 18 | { | |
| 18 | 19 | name: "requested allowlist", | |
| 19 | 20 | args: Args{Orchestrate: true, ToolsSet: true, Tools: []string{"subagent_spawn"}}, | |
| 20 | 21 | wantResearch: true, | |
| 22 | + | wantWeb: true, | |
| 21 | 23 | wantWriteTool: false, | |
| 22 | 24 | }, | |
| 23 | 25 | { | |
| 24 | 26 | name: "web search policy disabled", | |
| 25 | 27 | args: Args{Orchestrate: true, WebSearchPolicy: subagents.WebSearchDeny}, | |
| 26 | 28 | wantResearch: true, | |
| 29 | + | wantWeb: true, | |
| 30 | + | wantWriteTool: true, | |
| 31 | + | }, | |
| 32 | + | { | |
| 33 | + | name: "permission ceiling", | |
| 34 | + | args: Args{Orchestrate: true, PermissionSet: &tools.PermissionSet{}}, | |
| 35 | + | wantResearch: true, | |
| 36 | + | wantWeb: false, | |
| 27 | 37 | wantWriteTool: true, | |
| 28 | 38 | }, | |
| 29 | 39 | { | |
| 30 | 40 | name: "tools disabled", | |
| 31 | 41 | args: Args{Orchestrate: true, NoTools: true, ToolsSet: true, Tools: []string{"write"}}, | |
| 32 | 42 | wantResearch: false, | |
| 43 | + | wantWeb: false, | |
| 33 | 44 | wantWriteTool: false, | |
| 34 | 45 | }, | |
| 35 | 46 | { | |
| 36 | 47 | name: "normal session", | |
| 37 | 48 | args: Args{ToolsSet: true, Tools: []string{"write"}}, | |
| 38 | 49 | wantResearch: false, | |
| 50 | + | wantWeb: false, | |
| 39 | 51 | wantWriteTool: true, | |
| 40 | 52 | }, | |
| 41 | 53 | } { | |
| @@ -54,8 +66,8 @@ func TestOrchestratorAlwaysReceivesResearchTools(t *testing.T) { | |||
| 54 | 66 | } | |
| 55 | 67 | for _, name := range tools.WebCapabilityNames { | |
| 56 | 68 | _, gotWebTool := registry[name] | |
| 57 | - | if gotWebTool != tc.wantResearch { | |
| 58 | - | t.Fatalf("%s present = %v, want research tool present = %v", name, gotWebTool, tc.wantResearch) | |
| 69 | + | if gotWebTool != tc.wantWeb { | |
| 70 | + | t.Fatalf("%s present = %v, want web tool present = %v", name, gotWebTool, tc.wantWeb) | |
| 59 | 71 | } | |
| 60 | 72 | } | |
| 61 | 73 | _, gotWrite := registry["write"] | |
| @@ -12,30 +12,42 @@ func TestOrchestratorAlwaysReceivesResearchTools(t *testing.T) { | |||||
| 12 | name string | 12 | name string | ||
| 13 | args Args | 13 | args Args | ||
| 14 | wantResearch bool | 14 | wantResearch bool | ||
| 15 | + | wantWeb bool | |||
| 15 | wantWriteTool bool | 16 | wantWriteTool bool | ||
| 16 | }{ | 17 | }{ | ||
| 17 | { | 18 | { | ||
| 18 | name: "requested allowlist", | 19 | name: "requested allowlist", | ||
| 19 | args: Args{Orchestrate: true, ToolsSet: true, Tools: []string{"subagent_spawn"}}, | 20 | args: Args{Orchestrate: true, ToolsSet: true, Tools: []string{"subagent_spawn"}}, | ||
| 20 | wantResearch: true, | 21 | wantResearch: true, | ||
| 22 | + | wantWeb: true, | |||
| 21 | wantWriteTool: false, | 23 | wantWriteTool: false, | ||
| 22 | }, | 24 | }, | ||
| 23 | { | 25 | { | ||
| 24 | name: "web search policy disabled", | 26 | name: "web search policy disabled", | ||
| 25 | args: Args{Orchestrate: true, WebSearchPolicy: subagents.WebSearchDeny}, | 27 | args: Args{Orchestrate: true, WebSearchPolicy: subagents.WebSearchDeny}, | ||
| 26 | wantResearch: true, | 28 | wantResearch: true, | ||
| 29 | + | wantWeb: true, | |||
| 30 | + | wantWriteTool: true, | |||
| 31 | + | }, | |||
| 32 | + | { | |||
| 33 | + | name: "permission ceiling", | |||
| 34 | + | args: Args{Orchestrate: true, PermissionSet: &tools.PermissionSet{}}, | |||
| 35 | + | wantResearch: true, | |||
| 36 | + | wantWeb: false, | |||
| 27 | wantWriteTool: true, | 37 | wantWriteTool: true, | ||
| 28 | }, | 38 | }, | ||
| 29 | { | 39 | { | ||
| 30 | name: "tools disabled", | 40 | name: "tools disabled", | ||
| 31 | args: Args{Orchestrate: true, NoTools: true, ToolsSet: true, Tools: []string{"write"}}, | 41 | args: Args{Orchestrate: true, NoTools: true, ToolsSet: true, Tools: []string{"write"}}, | ||
| 32 | wantResearch: false, | 42 | wantResearch: false, | ||
| 43 | + | wantWeb: false, | |||
| 33 | wantWriteTool: false, | 44 | wantWriteTool: false, | ||
| 34 | }, | 45 | }, | ||
| 35 | { | 46 | { | ||
| 36 | name: "normal session", | 47 | name: "normal session", | ||
| 37 | args: Args{ToolsSet: true, Tools: []string{"write"}}, | 48 | args: Args{ToolsSet: true, Tools: []string{"write"}}, | ||
| 38 | wantResearch: false, | 49 | wantResearch: false, | ||
| 50 | + | wantWeb: false, | |||
| 39 | wantWriteTool: true, | 51 | wantWriteTool: true, | ||
| 40 | }, | 52 | }, | ||
| 41 | } { | 53 | } { | ||
| @@ -54,8 +66,8 @@ func TestOrchestratorAlwaysReceivesResearchTools(t *testing.T) { | |||||
| 54 | } | 66 | } | ||
| 55 | for _, name := range tools.WebCapabilityNames { | 67 | for _, name := range tools.WebCapabilityNames { | ||
| 56 | _, gotWebTool := registry[name] | 68 | _, gotWebTool := registry[name] | ||
| 57 | - | if gotWebTool != tc.wantResearch { | 69 | + | if gotWebTool != tc.wantWeb { |
| 58 | - | t.Fatalf("%s present = %v, want research tool present = %v", name, gotWebTool, tc.wantResearch) | 70 | + | t.Fatalf("%s present = %v, want web tool present = %v", name, gotWebTool, tc.wantWeb) |
| 59 | } | 71 | } | ||
| 60 | } | 72 | } | ||
| 61 | _, gotWrite := registry["write"] | 73 | _, gotWrite := registry["write"] | ||
M packages/agent/subagent_runtime.go +7 -5
| @@ -366,14 +366,14 @@ func (rt *subagentRuntime) buildResidentChildSpec(_ context.Context, request too | |||
| 366 | 366 | } | |
| 367 | 367 | ||
| 368 | 368 | func expandWebCapabilityTools(selected, catalogue []string, permitted func(string) bool) []string { | |
| 369 | - | hasSearch := false | |
| 369 | + | hasWebTool := false | |
| 370 | 370 | for _, name := range selected { | |
| 371 | - | if name == "web_search" { | |
| 372 | - | hasSearch = true | |
| 371 | + | if tools.IsWebCapabilityName(name) { | |
| 372 | + | hasWebTool = true | |
| 373 | 373 | break | |
| 374 | 374 | } | |
| 375 | 375 | } | |
| 376 | - | if !hasSearch { | |
| 376 | + | if !hasWebTool { | |
| 377 | 377 | return selected | |
| 378 | 378 | } | |
| 379 | 379 | available := make(map[string]struct{}, len(catalogue)) | |
| @@ -382,7 +382,9 @@ func expandWebCapabilityTools(selected, catalogue []string, permitted func(strin | |||
| 382 | 382 | available[name] = struct{}{} | |
| 383 | 383 | } | |
| 384 | 384 | for _, name := range selected { | |
| 385 | - | selectedSet[name] = struct{}{} | |
| 385 | + | if permitted == nil || permitted(name) { | |
| 386 | + | selectedSet[name] = struct{}{} | |
| 387 | + | } | |
| 386 | 388 | } | |
| 387 | 389 | for _, name := range tools.WebCapabilityNames { | |
| 388 | 390 | if _, ok := available[name]; ok && (permitted == nil || permitted(name)) { | |
| @@ -366,14 +366,14 @@ func (rt *subagentRuntime) buildResidentChildSpec(_ context.Context, request too | |||||
| 366 | } | 366 | } | ||
| 367 | 367 | ||||
| 368 | func expandWebCapabilityTools(selected, catalogue []string, permitted func(string) bool) []string { | 368 | func expandWebCapabilityTools(selected, catalogue []string, permitted func(string) bool) []string { | ||
| 369 | - | hasSearch := false | 369 | + | hasWebTool := false |
| 370 | for _, name := range selected { | 370 | for _, name := range selected { | ||
| 371 | - | if name == "web_search" { | 371 | + | if tools.IsWebCapabilityName(name) { |
| 372 | - | hasSearch = true | 372 | + | hasWebTool = true |
| 373 | break | 373 | break | ||
| 374 | } | 374 | } | ||
| 375 | } | 375 | } | ||
| 376 | - | if !hasSearch { | 376 | + | if !hasWebTool { |
| 377 | return selected | 377 | return selected | ||
| 378 | } | 378 | } | ||
| 379 | available := make(map[string]struct{}, len(catalogue)) | 379 | available := make(map[string]struct{}, len(catalogue)) | ||
| @@ -382,7 +382,9 @@ func expandWebCapabilityTools(selected, catalogue []string, permitted func(strin | |||||
| 382 | available[name] = struct{}{} | 382 | available[name] = struct{}{} | ||
| 383 | } | 383 | } | ||
| 384 | for _, name := range selected { | 384 | for _, name := range selected { | ||
| 385 | - | selectedSet[name] = struct{}{} | 385 | + | if permitted == nil || permitted(name) { |
| 386 | + | selectedSet[name] = struct{}{} | |||
| 387 | + | } | |||
| 386 | } | 388 | } | ||
| 387 | for _, name := range tools.WebCapabilityNames { | 389 | for _, name := range tools.WebCapabilityNames { | ||
| 388 | if _, ok := available[name]; ok && (permitted == nil || permitted(name)) { | 390 | if _, ok := available[name]; ok && (permitted == nil || permitted(name)) { | ||
M packages/agent/subagent_runtime_test.go +25 -0
| @@ -25,6 +25,31 @@ func TestExpandWebCapabilityTools(t *testing.T) { | |||
| 25 | 25 | } | |
| 26 | 26 | } | |
| 27 | 27 | ||
| 28 | + | func TestExpandWebCapabilityToolsPreservesPolicyAndNonWebSelections(t *testing.T) { | |
| 29 | + | catalogue := append([]string{"read"}, tools.WebCapabilityNames...) | |
| 30 | + | partial := expandWebCapabilityTools([]string{"web_open"}, catalogue, func(string) bool { return true }) | |
| 31 | + | for _, name := range tools.WebCapabilityNames { | |
| 32 | + | found := false | |
| 33 | + | for _, candidate := range partial { | |
| 34 | + | if candidate == name { | |
| 35 | + | found = true | |
| 36 | + | break | |
| 37 | + | } | |
| 38 | + | } | |
| 39 | + | if !found { | |
| 40 | + | t.Fatalf("partial web selection did not expand: %v", partial) | |
| 41 | + | } | |
| 42 | + | } | |
| 43 | + | denied := expandWebCapabilityTools([]string{"read", "web_search"}, catalogue, func(name string) bool { return name == "read" }) | |
| 44 | + | if len(denied) != 1 || denied[0] != "read" { | |
| 45 | + | t.Fatalf("denied web capability = %v, want [read]", denied) | |
| 46 | + | } | |
| 47 | + | nonWeb := expandWebCapabilityTools([]string{"read"}, catalogue, func(string) bool { return true }) | |
| 48 | + | if len(nonWeb) != 1 || nonWeb[0] != "read" { | |
| 49 | + | t.Fatalf("non-web selection changed to %v", nonWeb) | |
| 50 | + | } | |
| 51 | + | } | |
| 52 | + | ||
| 28 | 53 | func TestResidentChildSpecSnapshotsCurrentProviderTransportSettings(t *testing.T) { | |
| 29 | 54 | runtime := newSubagentRuntime(subagentRuntimeConfig{ | |
| 30 | 55 | Args: Args{}, Root: t.TempDir(), RepoRoot: t.TempDir(), | |
| @@ -25,6 +25,31 @@ func TestExpandWebCapabilityTools(t *testing.T) { | |||||
| 25 | } | 25 | } | ||
| 26 | } | 26 | } | ||
| 27 | 27 | ||||
| 28 | + | func TestExpandWebCapabilityToolsPreservesPolicyAndNonWebSelections(t *testing.T) { | |||
| 29 | + | catalogue := append([]string{"read"}, tools.WebCapabilityNames...) | |||
| 30 | + | partial := expandWebCapabilityTools([]string{"web_open"}, catalogue, func(string) bool { return true }) | |||
| 31 | + | for _, name := range tools.WebCapabilityNames { | |||
| 32 | + | found := false | |||
| 33 | + | for _, candidate := range partial { | |||
| 34 | + | if candidate == name { | |||
| 35 | + | found = true | |||
| 36 | + | break | |||
| 37 | + | } | |||
| 38 | + | } | |||
| 39 | + | if !found { | |||
| 40 | + | t.Fatalf("partial web selection did not expand: %v", partial) | |||
| 41 | + | } | |||
| 42 | + | } | |||
| 43 | + | denied := expandWebCapabilityTools([]string{"read", "web_search"}, catalogue, func(name string) bool { return name == "read" }) | |||
| 44 | + | if len(denied) != 1 || denied[0] != "read" { | |||
| 45 | + | t.Fatalf("denied web capability = %v, want [read]", denied) | |||
| 46 | + | } | |||
| 47 | + | nonWeb := expandWebCapabilityTools([]string{"read"}, catalogue, func(string) bool { return true }) | |||
| 48 | + | if len(nonWeb) != 1 || nonWeb[0] != "read" { | |||
| 49 | + | t.Fatalf("non-web selection changed to %v", nonWeb) | |||
| 50 | + | } | |||
| 51 | + | } | |||
| 52 | + | ||||
| 28 | func TestResidentChildSpecSnapshotsCurrentProviderTransportSettings(t *testing.T) { | 53 | func TestResidentChildSpecSnapshotsCurrentProviderTransportSettings(t *testing.T) { | ||
| 29 | runtime := newSubagentRuntime(subagentRuntimeConfig{ | 54 | runtime := newSubagentRuntime(subagentRuntimeConfig{ | ||
| 30 | Args: Args{}, Root: t.TempDir(), RepoRoot: t.TempDir(), | 55 | Args: Args{}, Root: t.TempDir(), RepoRoot: t.TempDir(), | ||
M packages/agent/tools/web_page.go +3 -3
| @@ -153,6 +153,9 @@ func (s *BrowsingStore) add(doc *webDocument, generation uint64, checkGeneration | |||
| 153 | 153 | for _, link := range doc.links { | |
| 154 | 154 | doc.size += len(link.label) + len(link.url) | |
| 155 | 155 | } | |
| 156 | + | if doc.size > webPageMaxStoreBytes { | |
| 157 | + | return "" | |
| 158 | + | } | |
| 156 | 159 | for len(s.docs) >= webPageMaxDocuments || (len(s.docs) > 0 && s.used+uint64(doc.size) > webPageMaxStoreBytes) { | |
| 157 | 160 | var victim *webDocument | |
| 158 | 161 | for _, candidate := range s.docs { | |
| @@ -166,9 +169,6 @@ func (s *BrowsingStore) add(doc *webDocument, generation uint64, checkGeneration | |||
| 166 | 169 | delete(s.docs, victim.id) | |
| 167 | 170 | s.used -= uint64(victim.size) | |
| 168 | 171 | } | |
| 169 | - | if doc.size > webPageMaxStoreBytes { | |
| 170 | - | return "" | |
| 171 | - | } | |
| 172 | 172 | s.next++ | |
| 173 | 173 | doc.id = fmt.Sprintf("web-%d", s.next) | |
| 174 | 174 | doc.touched = s.next | |
| @@ -153,6 +153,9 @@ func (s *BrowsingStore) add(doc *webDocument, generation uint64, checkGeneration | |||||
| 153 | for _, link := range doc.links { | 153 | for _, link := range doc.links { | ||
| 154 | doc.size += len(link.label) + len(link.url) | 154 | doc.size += len(link.label) + len(link.url) | ||
| 155 | } | 155 | } | ||
| 156 | + | if doc.size > webPageMaxStoreBytes { | |||
| 157 | + | return "" | |||
| 158 | + | } | |||
| 156 | for len(s.docs) >= webPageMaxDocuments || (len(s.docs) > 0 && s.used+uint64(doc.size) > webPageMaxStoreBytes) { | 159 | for len(s.docs) >= webPageMaxDocuments || (len(s.docs) > 0 && s.used+uint64(doc.size) > webPageMaxStoreBytes) { | ||
| 157 | var victim *webDocument | 160 | var victim *webDocument | ||
| 158 | for _, candidate := range s.docs { | 161 | for _, candidate := range s.docs { | ||
| @@ -166,9 +169,6 @@ func (s *BrowsingStore) add(doc *webDocument, generation uint64, checkGeneration | |||||
| 166 | delete(s.docs, victim.id) | 169 | delete(s.docs, victim.id) | ||
| 167 | s.used -= uint64(victim.size) | 170 | s.used -= uint64(victim.size) | ||
| 168 | } | 171 | } | ||
| 169 | - | if doc.size > webPageMaxStoreBytes { | |||
| 170 | - | return "" | |||
| 171 | - | } | |||
| 172 | s.next++ | 172 | s.next++ | ||
| 173 | doc.id = fmt.Sprintf("web-%d", s.next) | 173 | doc.id = fmt.Sprintf("web-%d", s.next) | ||
| 174 | doc.touched = s.next | 174 | doc.touched = s.next | ||
M packages/agent/tools/web_page_test.go +12 -4
| @@ -139,9 +139,9 @@ func TestWebPageFetcherRedirectsWithFreshValidation(t *testing.T) { | |||
| 139 | 139 | } | |
| 140 | 140 | ||
| 141 | 141 | func TestWebPageFetcherPreservesHTTPSHostnameForSNI(t *testing.T) { | |
| 142 | - | var serverName string | |
| 142 | + | var serverName atomic.Value | |
| 143 | 143 | server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 144 | - | serverName = r.TLS.ServerName | |
| 144 | + | serverName.Store(r.TLS.ServerName) | |
| 145 | 145 | w.Header().Set("Content-Type", "text/plain") | |
| 146 | 146 | _, _ = w.Write([]byte("secure")) | |
| 147 | 147 | })) | |
| @@ -153,8 +153,9 @@ func TestWebPageFetcherPreservesHTTPSHostnameForSNI(t *testing.T) { | |||
| 153 | 153 | // InsecureSkipVerify. | |
| 154 | 154 | fetcher.tlsConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // test-only local certificate | |
| 155 | 155 | body, _, _, message := fetcher.fetch(context.Background(), "https://public.test/") | |
| 156 | - | if message != "" || string(body) != "secure" || serverName != "public.test" { | |
| 157 | - | t.Fatalf("HTTPS body=%q message=%q SNI=%q", body, message, serverName) | |
| 156 | + | got, _ := serverName.Load().(string) | |
| 157 | + | if message != "" || string(body) != "secure" || got != "public.test" { | |
| 158 | + | t.Fatalf("HTTPS body=%q message=%q SNI=%q", body, message, got) | |
| 158 | 159 | } | |
| 159 | 160 | } | |
| 160 | 161 | ||
| @@ -254,7 +255,14 @@ func TestBrowsingStoreConcurrentAccess(t *testing.T) { | |||
| 254 | 255 | ||
| 255 | 256 | func TestWebPageStoreGenerationAndSchemaFailures(t *testing.T) { | |
| 256 | 257 | store := NewBrowsingStore() | |
| 258 | + | retained := store.addSource("http://public.test/") | |
| 257 | 259 | generation := store.snapshotGeneration() | |
| 260 | + | if id, committed := store.addPage(&webDocument{url: "http://public.test", lines: []string{strings.Repeat("x", webPageMaxStoreBytes+1)}}, generation); id != "" || committed { | |
| 261 | + | t.Fatalf("oversized page commit = %q, %v", id, committed) | |
| 262 | + | } | |
| 263 | + | if _, ok := store.get(retained, false); !ok { | |
| 264 | + | t.Fatal("oversized page evicted an existing document") | |
| 265 | + | } | |
| 258 | 266 | store.Clear() | |
| 259 | 267 | if id, committed := store.addPage(&webDocument{url: "http://public.test", lines: []string{"text"}}, generation); id != "" || committed { | |
| 260 | 268 | t.Fatalf("stale page commit = %q, %v", id, committed) | |
| @@ -139,9 +139,9 @@ func TestWebPageFetcherRedirectsWithFreshValidation(t *testing.T) { | |||||
| 139 | } | 139 | } | ||
| 140 | 140 | ||||
| 141 | func TestWebPageFetcherPreservesHTTPSHostnameForSNI(t *testing.T) { | 141 | func TestWebPageFetcherPreservesHTTPSHostnameForSNI(t *testing.T) { | ||
| 142 | - | var serverName string | 142 | + | var serverName atomic.Value |
| 143 | server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 143 | server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| 144 | - | serverName = r.TLS.ServerName | 144 | + | serverName.Store(r.TLS.ServerName) |
| 145 | w.Header().Set("Content-Type", "text/plain") | 145 | w.Header().Set("Content-Type", "text/plain") | ||
| 146 | _, _ = w.Write([]byte("secure")) | 146 | _, _ = w.Write([]byte("secure")) | ||
| 147 | })) | 147 | })) | ||
| @@ -153,8 +153,9 @@ func TestWebPageFetcherPreservesHTTPSHostnameForSNI(t *testing.T) { | |||||
| 153 | // InsecureSkipVerify. | 153 | // InsecureSkipVerify. | ||
| 154 | fetcher.tlsConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // test-only local certificate | 154 | fetcher.tlsConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // test-only local certificate | ||
| 155 | body, _, _, message := fetcher.fetch(context.Background(), "https://public.test/") | 155 | body, _, _, message := fetcher.fetch(context.Background(), "https://public.test/") | ||
| 156 | - | if message != "" || string(body) != "secure" || serverName != "public.test" { | 156 | + | got, _ := serverName.Load().(string) |
| 157 | - | t.Fatalf("HTTPS body=%q message=%q SNI=%q", body, message, serverName) | 157 | + | if message != "" || string(body) != "secure" || got != "public.test" { |
| 158 | + | t.Fatalf("HTTPS body=%q message=%q SNI=%q", body, message, got) | |||
| 158 | } | 159 | } | ||
| 159 | } | 160 | } | ||
| 160 | 161 | ||||
| @@ -254,7 +255,14 @@ func TestBrowsingStoreConcurrentAccess(t *testing.T) { | |||||
| 254 | 255 | ||||
| 255 | func TestWebPageStoreGenerationAndSchemaFailures(t *testing.T) { | 256 | func TestWebPageStoreGenerationAndSchemaFailures(t *testing.T) { | ||
| 256 | store := NewBrowsingStore() | 257 | store := NewBrowsingStore() | ||
| 258 | + | retained := store.addSource("http://public.test/") | |||
| 257 | generation := store.snapshotGeneration() | 259 | generation := store.snapshotGeneration() | ||
| 260 | + | if id, committed := store.addPage(&webDocument{url: "http://public.test", lines: []string{strings.Repeat("x", webPageMaxStoreBytes+1)}}, generation); id != "" || committed { | |||
| 261 | + | t.Fatalf("oversized page commit = %q, %v", id, committed) | |||
| 262 | + | } | |||
| 263 | + | if _, ok := store.get(retained, false); !ok { | |||
| 264 | + | t.Fatal("oversized page evicted an existing document") | |||
| 265 | + | } | |||
| 258 | store.Clear() | 266 | store.Clear() | ||
| 259 | if id, committed := store.addPage(&webDocument{url: "http://public.test", lines: []string{"text"}}, generation); id != "" || committed { | 267 | if id, committed := store.addPage(&webDocument{url: "http://public.test", lines: []string{"text"}}, generation); id != "" || committed { | ||
| 260 | t.Fatalf("stale page commit = %q, %v", id, committed) | 268 | t.Fatalf("stale page commit = %q, %v", id, committed) | ||