mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Comments, doc comments, UI labels, log and error messages, LLM prompt strings, markdown docs, test data and assertions. Identifiers, enum keys, routes, placeholders and code structure are unchanged. Notes: - docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md - tests that exercised CJK specific handling (GBK zip names, Chinese full text search) now use ASCII equivalents; go build passes and the test suite matches the pre translation baseline (remaining failures need a PostgreSQL DSN and fail identically on the original) - frontend mention syntax now uses English kinds (finding, asset, ...) matching the backend, chat mentions tests pass
131 lines
5.5 KiB
Go
131 lines
5.5 KiB
Go
package db
|
|
|
|
import "testing"
|
|
|
|
func rule(kind, pattern string, enabled bool) AssetInterceptRule {
|
|
return AssetInterceptRule{Kind: kind, Pattern: pattern, Enabled: enabled}
|
|
}
|
|
|
|
func TestMatchAssetInterceptRules(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
rules []AssetInterceptRule
|
|
domains []string
|
|
ips []string
|
|
urls []string
|
|
want bool
|
|
wantVal string
|
|
}{
|
|
{"built-in fuzzy government domain hit", []AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)},
|
|
[]string{"www.beijing.gov.cn"}, nil, nil, true, "www.beijing.gov.cn"},
|
|
{"fuzzy education domain hit", []AssetInterceptRule{rule("fuzzy_domain", ".edu", true)},
|
|
[]string{"mit.edu"}, nil, nil, true, "mit.edu"},
|
|
{"exact domain hit is case-insensitive", []AssetInterceptRule{rule("exact_domain", "Example.com", true)},
|
|
[]string{"example.com"}, nil, nil, true, "example.com"},
|
|
{"exact domain does not hit subdomain", []AssetInterceptRule{rule("exact_domain", "example.com", true)},
|
|
[]string{"a.example.com"}, nil, nil, false, ""},
|
|
{"exact IP hit", []AssetInterceptRule{rule("exact_ip", "203.0.113.5", true)},
|
|
nil, []string{"203.0.113.5"}, nil, true, "203.0.113.5"},
|
|
{"fuzzy IP prefix hit", []AssetInterceptRule{rule("fuzzy_ip", "203.0.113.", true)},
|
|
nil, []string{"203.0.113.99"}, nil, true, "203.0.113.99"},
|
|
{"CIDR hit", []AssetInterceptRule{rule("cidr", "192.168.0.0/16", true)},
|
|
nil, []string{"192.168.5.20"}, nil, true, "192.168.5.20"},
|
|
{"CIDR no hit", []AssetInterceptRule{rule("cidr", "192.168.0.0/16", true)},
|
|
nil, []string{"10.0.0.1"}, nil, false, ""},
|
|
{"exact URL hit", []AssetInterceptRule{rule("exact_url", "https://a.gov.cn/login", true)},
|
|
nil, nil, []string{"https://a.gov.cn/login"}, true, "https://a.gov.cn/login"},
|
|
{"fuzzy URL hits path", []AssetInterceptRule{rule("fuzzy_url", "/admin", true)},
|
|
nil, nil, []string{"https://x.com/admin/panel"}, true, "https://x.com/admin/panel"},
|
|
{"disabled rule does not hit", []AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", false)},
|
|
[]string{"www.gov.cn"}, nil, nil, false, ""},
|
|
{"no rules no hit", nil, []string{"www.gov.cn"}, nil, nil, false, ""},
|
|
{"empty pattern no hit", []AssetInterceptRule{rule("fuzzy_domain", " ", true)},
|
|
[]string{"www.gov.cn"}, nil, nil, false, ""},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
r, val, ok := MatchAssetInterceptRules(c.rules, c.domains, c.ips, c.urls)
|
|
if ok != c.want {
|
|
t.Fatalf("hit = %v, want %v (rule=%+v)", ok, c.want, r)
|
|
}
|
|
if ok && val != c.wantVal {
|
|
t.Fatalf("matched value = %q, want %q", val, c.wantVal)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEvaluateAssetGate(t *testing.T) {
|
|
block := []AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)}
|
|
allow := []AssetInterceptRule{rule("fuzzy_domain", "example.com", true)}
|
|
|
|
// 1. Interception rule hit → reject (interception reason takes priority).
|
|
if d := EvaluateAssetGate(block, allow, []string{"www.gov.cn"}, nil, nil); d.Allowed {
|
|
t.Fatal("hitting an interception rule should be rejected")
|
|
}
|
|
|
|
// 2. No interception hit, allow rules exist but do not match → reject (not allowed).
|
|
d := EvaluateAssetGate(block, allow, []string{"foo.other.com"}, nil, nil)
|
|
if d.Allowed {
|
|
t.Fatal("having a whitelist without a match should be rejected")
|
|
}
|
|
if d.Reason == "" {
|
|
t.Fatal("rejection should carry a reason")
|
|
}
|
|
|
|
// 3. No interception hit, allow rule hit → allow.
|
|
if d := EvaluateAssetGate(block, allow, []string{"api.example.com"}, nil, nil); !d.Allowed {
|
|
t.Fatal("hitting the whitelist should be allowed")
|
|
}
|
|
|
|
// 4. No allow rules (whitelist not enabled) → no interception hit means allow.
|
|
if d := EvaluateAssetGate(block, nil, []string{"foo.other.com"}, nil, nil); !d.Allowed {
|
|
t.Fatal("with no whitelist, missing the interception rules should be allowed")
|
|
}
|
|
|
|
// 5. All allow rules disabled → treated as whitelist not enabled, allow.
|
|
disabledAllow := []AssetInterceptRule{rule("fuzzy_domain", "example.com", false)}
|
|
if d := EvaluateAssetGate(nil, disabledAllow, []string{"foo.other.com"}, nil, nil); !d.Allowed {
|
|
t.Fatal("with the whitelist fully disabled, should be allowed")
|
|
}
|
|
|
|
// 6. Interception beats allow: the same target hits both → reject.
|
|
if d := EvaluateAssetGate(
|
|
[]AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)},
|
|
[]AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)},
|
|
[]string{"www.gov.cn"}, nil, nil,
|
|
); d.Allowed {
|
|
t.Fatal("interception should take priority over allow")
|
|
}
|
|
}
|
|
|
|
func TestAssetInterceptCandidates(t *testing.T) {
|
|
// Service asset carrying only a URL: the host should be split out into the
|
|
// domain candidates, so it can be hit by fuzzy_domain.
|
|
a := &Asset{Type: "service", URL: "https://portal.beijing.gov.cn:8443/app"}
|
|
domains, _, urls := a.interceptCandidates()
|
|
if len(urls) != 1 || urls[0] != a.URL {
|
|
t.Fatalf("urls = %v", urls)
|
|
}
|
|
found := false
|
|
for _, d := range domains {
|
|
if d == "portal.beijing.gov.cn" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("URL host was not split into domain candidates: %v", domains)
|
|
}
|
|
r, _, ok := MatchAssetInterceptRules([]AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)}, domains, nil, urls)
|
|
if !ok {
|
|
t.Fatalf("a government service asset with only a URL should be hit by fuzzy_domain, rule=%+v", r)
|
|
}
|
|
|
|
// When the URL host is an IP it should go into the IP candidates, matchable by CIDR.
|
|
b := &Asset{Type: "service", URL: "http://10.1.2.3/x"}
|
|
_, ips, _ := b.interceptCandidates()
|
|
if r, _, ok := MatchAssetInterceptRules([]AssetInterceptRule{rule("cidr", "10.0.0.0/8", true)}, nil, ips, nil); !ok {
|
|
t.Fatalf("an IP in a URL should be hit by CIDR, ips=%v rule=%+v", ips, r)
|
|
}
|
|
}
|