From a121e8967cb60b0265e0490ce64558fcce8e7fb6 Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Sun, 9 Aug 2026 09:09:59 -0400 Subject: [PATCH] =?UTF-8?q?fix(scope):=20add=5Ftask=5Fscope=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20host:port=20=E7=9B=AE=E6=A0=87,=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E5=89=8D=E5=89=A5=E7=AB=AF=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ip/root_domain/subdomain 三类范围入库前统一 stripHostPort: - ip 带端口(如 10.0.188.136:3000)之前直接报"无效 ip/cidr" - 域名带端口更隐蔽,不报错却存下匹配不到资产的垃圾键 Co-Authored-By: Claude Opus 4.8 --- db/task_scope.go | 19 ++++++++++++++++--- db/task_scope_test.go | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 db/task_scope_test.go diff --git a/db/task_scope.go b/db/task_scope.go index dee0c61..e3108eb 100644 --- a/db/task_scope.go +++ b/db/task_scope.go @@ -22,6 +22,19 @@ type TaskScope struct { Reason string `json:"reason,omitempty"` } +// stripHostPort drops a trailing :port from a host:port / ip:port / [ipv6]:port +// value, returning the bare host. Bare hosts, bare IPs (v4 or v6, whose colons make +// them ambiguous), and anything not in host:port form are returned unchanged. Scope +// is host/net based, so the port from a target like "10.0.188.136:3000" or +// "api.example.com:8080" is simply discarded rather than baked into the key. +func stripHostPort(v string) string { + v = strings.TrimSpace(v) + if host, _, err := net.SplitHostPort(v); err == nil { + return host + } + return v +} + // ipToHostCIDR turns a bare IP into its single-host CIDR (/32 or /128). "" if invalid. func ipToHostCIDR(ip string) string { ip = strings.TrimSpace(ip) @@ -132,7 +145,7 @@ func (s *AssetStore) AddAgentScope(taskID int64, kind, value, reason string) (Ta } ts.CompanyID = &comp.ID case "root_domain": - d := DomainKey(value) + d := DomainKey(stripHostPort(value)) root, _ := RootDomain(d) if root == "" { root = d @@ -142,7 +155,7 @@ func (s *AssetStore) AddAgentScope(taskID int64, kind, value, reason string) (Ta } ts.Domain = root case "subdomain": - d := DomainKey(value) + d := DomainKey(stripHostPort(value)) if d == "" { return ts, fmt.Errorf("无效子域: %s", value) } @@ -150,7 +163,7 @@ func (s *AssetStore) AddAgentScope(taskID int64, kind, value, reason string) (Ta case "ip", "cidr": v := value if !strings.Contains(v, "/") { - v = ipToHostCIDR(v) + v = ipToHostCIDR(stripHostPort(v)) ts.Kind = "ip" } else { ts.Kind = "cidr" diff --git a/db/task_scope_test.go b/db/task_scope_test.go new file mode 100644 index 0000000..ec74bfe --- /dev/null +++ b/db/task_scope_test.go @@ -0,0 +1,23 @@ +package db + +import "testing" + +// TestStripHostPort covers the ip:port / [ipv6]:port stripping used by the +// ip/cidr scope path so a target like "10.0.188.136:3000" no longer 404s. +func TestStripHostPort(t *testing.T) { + cases := []struct{ in, want string }{ + {"10.0.188.136:3000", "10.0.188.136"}, // the reported IP case + {"10.0.188.136", "10.0.188.136"}, // bare IPv4 unchanged + {"[2001:db8::1]:8080", "2001:db8::1"}, // bracketed IPv6 + port + {"2001:db8::1", "2001:db8::1"}, // bare IPv6 unchanged (has colons) + {" 1.2.3.4:80 ", "1.2.3.4"}, // trims surrounding space + {"example.com:443", "example.com"}, // domain + port → bare host + {"api.example.com:8080", "api.example.com"}, // subdomain + port + {"example.com", "example.com"}, // bare domain unchanged + } + for _, c := range cases { + if got := stripHostPort(c.in); got != c.want { + t.Errorf("stripHostPort(%q) = %q, want %q", c.in, got, c.want) + } + } +}