533db535f1
build / build (push) Successful in 1m12s
按已确认需求提供独立安全模型配置、真实探测、正式字典字段评估与原问题译中,持久批次支持只补空白、暂停恢复、有限重试、幂等和逐字段依据。覆盖固定规则、本地语言、组织关联与主表有效看板共用治理侧读模型,保留源生产和冻结历史。 验证:完整 make verify 退出0,702项Go与329项前端通过;默认118项外部/隔离集成明确跳过,另跑完整镜像及组织隔离PG竞态。12并发、输入漂移、过期租约、暂停/恢复及有限重试通过。用户授权的百炼 deepseek-v4.1-flash 完成真实后台与浏览器评估/翻译;552行、16列、11块看板及完整PNG、独立配置页测试/保存、重开恢复和零JS异常通过。仅非业务样本外发,自动处理关闭,原payload不变。 上线前治理库已做导出快照原生备份并真实恢复,60表行数与17个QA表内容哈希一致;不修改DobotAI生产或隧道。本提交时尚未部署,线上验收须独立完成。
80 lines
2.4 KiB
Go
80 lines
2.4 KiB
Go
package qaopsai
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"net"
|
|
"net/netip"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var blockedNetworks = []netip.Prefix{netip.MustParsePrefix("100.64.0.0/10"), netip.MustParsePrefix("198.18.0.0/15"), netip.MustParsePrefix("192.0.0.0/24"), netip.MustParsePrefix("192.0.2.0/24"), netip.MustParsePrefix("198.51.100.0/24"), netip.MustParsePrefix("203.0.113.0/24"), netip.MustParsePrefix("2001:db8::/32")}
|
|
|
|
func allowedPublicIP(ip netip.Addr) bool {
|
|
ip = ip.Unmap()
|
|
if !ip.IsGlobalUnicast() || ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() {
|
|
return false
|
|
}
|
|
for _, network := range blockedNetworks {
|
|
if network.Contains(ip) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
func (c Client) validateHost(host string) error {
|
|
if host == "" || strings.Contains(host, "%") || strings.HasSuffix(strings.ToLower(host), ".local") {
|
|
return ErrInvalid
|
|
}
|
|
if !c.AllowPrivate {
|
|
if strings.EqualFold(host, "localhost") {
|
|
return ErrInvalid
|
|
}
|
|
if ip, err := netip.ParseAddr(host); err == nil && !allowedPublicIP(ip) {
|
|
return ErrInvalid
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Resolve and dial the same verified IP; a second DNS lookup could rebind the target.
|
|
func (c Client) safeDial(ctx context.Context, network, address string) (net.Conn, error) {
|
|
host, port, err := net.SplitHostPort(address)
|
|
if err != nil || c.validateHost(host) != nil {
|
|
return nil, ErrInvalid
|
|
}
|
|
resolver := net.DefaultResolver
|
|
if c.DNSOverTLS {
|
|
// Scope encrypted public DNS to this model client; never change host DNS or relax destination checks.
|
|
resolver = &net.Resolver{PreferGo: true, Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
dialer := tls.Dialer{NetDialer: &net.Dialer{Timeout: 8 * time.Second}, Config: &tls.Config{ServerName: "dns.alidns.com", MinVersion: tls.VersionTLS12}}
|
|
return dialer.DialContext(ctx, "tcp", "223.5.5.5:853")
|
|
}}
|
|
}
|
|
addresses, err := resolver.LookupNetIP(ctx, "ip", host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(addresses) == 0 {
|
|
return nil, ErrInvalid
|
|
}
|
|
if !c.AllowPrivate {
|
|
for _, ip := range addresses {
|
|
if !allowedPublicIP(ip) {
|
|
return nil, ErrInvalid
|
|
}
|
|
}
|
|
}
|
|
dialer := net.Dialer{Timeout: 10 * time.Second, KeepAlive: 30 * time.Second}
|
|
var last error
|
|
for _, ip := range addresses {
|
|
connection, e := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port))
|
|
if e == nil {
|
|
return connection, nil
|
|
}
|
|
last = e
|
|
}
|
|
return nil, last
|
|
}
|