Fix: reject malformed domain rules (#3917)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com>
This commit is contained in:
Copilot 2026-08-05 22:06:16 +08:00 committed by GitHub
parent 21f09bcdba
commit e449533a4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 35 additions and 3 deletions

View File

@ -83,7 +83,7 @@ func exportSite(name string, gs *GeoSites) error {
}
defer file.Close()
w := bufio.NewWriter(file)
fmt.Fprintf(w, "%s:\n", name)
fmt.Fprintf(w, "%q:\n", name)
var b strings.Builder
b.Grow(64)
for _, vdomain := range vDomains {

27
main.go
View File

@ -66,6 +66,9 @@ const (
ModeAll string = "all"
ModeAllowlist string = "allowlist"
ModeDenylist string = "denylist"
maxDomainLen int = 253 // Maximum length of a domain name
maxLabelLen int = 63 // Maximum length of a label of a domain name
)
func makeProtoList(listName string, entries []*Entry) *router.GeoSite {
@ -208,10 +211,15 @@ func parseEntry(typ, rule string) (*Entry, []string, error) {
return entry, nil, fmt.Errorf("invalid regexp %q: %w", parts[0], err)
}
entry.Value = parts[0]
case dlc.RuleTypeDomain, dlc.RuleTypeFullDomain, dlc.RuleTypeKeyword:
case dlc.RuleTypeDomain, dlc.RuleTypeFullDomain:
entry.Value = strings.ToLower(parts[0])
if !validateDomainName(entry.Value) {
return entry, nil, fmt.Errorf("invalid domain: %q", entry.Value)
}
case dlc.RuleTypeKeyword:
entry.Value = strings.ToLower(parts[0])
if !validateDomainChars(entry.Value) {
return entry, nil, fmt.Errorf("invalid domain: %q", entry.Value)
return entry, nil, fmt.Errorf("invalid keyword: %q", entry.Value)
}
default:
return entry, nil, fmt.Errorf("unknown rule type: %q", entry.Type)
@ -310,6 +318,21 @@ func validateDomainChars(domain string) bool {
return true
}
// validateDomainName reports whether the domain is a valid domain name, so that
// typos like "example..com" or "-example.com" would not be silently built into
// rules which can never match any domain.
func validateDomainName(domain string) bool {
if !validateDomainChars(domain) || len(domain) > maxDomainLen {
return false
}
for label := range strings.SplitSeq(domain, ".") {
if label == "" || len(label) > maxLabelLen || label[0] == '-' || label[len(label)-1] == '-' {
return false
}
}
return true
}
func validateAttrChars(attr string) bool {
if attr == "" {
return false

View File

@ -22,10 +22,19 @@ func TestParseEntry(t *testing.T) {
{name: "duplicated attrs", typ: "domain", rule: "example.com @ads @ads", wantPlain: "domain:example.com:@ads"},
{name: "affiliations", typ: "domain", rule: "example.com &other @ads", wantPlain: "domain:example.com:@ads", wantAffs: []string{"OTHER"}},
{name: "regexp", typ: "regexp", rule: `^example\.com$`, wantPlain: `regexp:^example\.com$`},
{name: "tld", typ: "domain", rule: "google", wantPlain: "domain:google"},
{name: "hyphen inside", typ: "domain", rule: "my-example.com", wantPlain: "domain:my-example.com"},
{name: "keyword allows partial", typ: "keyword", rule: "-ads-", wantPlain: "keyword:-ads-"},
{name: "invalid regexp", typ: "regexp", rule: "^example(", wantErr: true},
{name: "empty rule", typ: "domain", rule: " ", wantErr: true},
{name: "unknown type", typ: "prefix", rule: "example.com", wantErr: true},
{name: "invalid domain", typ: "domain", rule: "exa_mple.com @ads", wantErr: true},
{name: "empty label", typ: "domain", rule: "example..com", wantErr: true},
{name: "trailing dot", typ: "full", rule: "example.com.", wantErr: true},
{name: "leading dot", typ: "domain", rule: ".example.com", wantErr: true},
{name: "leading hyphen", typ: "domain", rule: "-example.com", wantErr: true},
{name: "trailing hyphen", typ: "full", rule: "example-.com", wantErr: true},
{name: "overlong label", typ: "domain", rule: strings.Repeat("a", 64) + ".com", wantErr: true},
{name: "empty attr", typ: "domain", rule: "example.com @", wantErr: true},
{name: "empty affiliation", typ: "domain", rule: "example.com &", wantErr: true},
{name: "unknown field", typ: "domain", rule: "example.com ads", wantErr: true},