返回 DeepSeek-Reasonix
webhook_security.go
根目录 / internal / bot / dingtalk / webhook_security.go
1 package dingtalk
2
3 import (
4 "fmt"
5 "net/http"
6 "net/url"
7 "strings"
8 )
9
10 // dingtalkWebhookHosts is the sessionWebhook hostname allowlist. DingTalk
11 // supplies this callback URL, and replies must never be redirected elsewhere.
12 var dingtalkWebhookHosts = []string{"api.dingtalk.com", "oapi.dingtalk.com"}
13
14 func (a *adapter) validDingtalkWebhook(raw string) bool {
15 u, err := url.Parse(strings.TrimSpace(raw))
16 if err != nil || u.Host == "" {
17 return false
18 }
19 if u.Scheme != "https" && !(a.allowHTTPWebhook && u.Scheme == "http") {
20 return false
21 }
22 for _, allowed := range a.webhookHosts {
23 if strings.EqualFold(u.Hostname(), allowed) {
24 return true
25 }
26 }
27 return false
28 }
29
30 // webhookClient clones the shared client so redirect policy is scoped to
31 // webhook sends. Every hop is revalidated and retains net/http's 10-hop limit.
32 func (a *adapter) webhookClient() *http.Client {
33 client := *a.httpClient
34 client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
35 if !a.validDingtalkWebhook(req.URL.String()) {
36 return fmt.Errorf("dingtalk send rejected redirect to non-dingtalk endpoint")
37 }
38 if len(via) >= 10 {
39 return fmt.Errorf("stopped after 10 redirects")
40 }
41 return nil
42 }
43 return &client
44 }
45
45 lines GO