Skip to content

feat(lists): parse bracketed IPv6 literals and map IDNs like DNS clients - #2261

Open
hawkff wants to merge 3 commits into
0xERR0R:mainfrom
hawkff:feat/list-ipv6-literals-idna
Open

hawkff wants to merge 3 commits into
0xERR0R:mainfrom
hawkff:feat/list-ipv6-literals-idna

Conversation

@hawkff

@hawkff hawkff commented Sep 12, 2026

Copy link
Copy Markdown

Summary

Blocky rejects bracketed IPv6 literals in host lists and mishandles some internationalized domain names (IDNs). Valid names can fail to parse or produce entries that do not match DNS queries.

This PR accepts [2001:db8::1] and ABP-style entries such as ||[2001:db8::1]^. It applies one UTS #46 lookup mapping to host-list entries, hosts-file names and aliases, and wildcard entries. The mapping converts IDNs to the ASCII form DNS clients query.

List entry Parsed entry
[2001:db8::1] 2001:db8::1
MÜNCHEN.example.de xn--mnchen-3ya.example.de
имяенн.010.xn--p1acf xn--e1afmfa9h.010.xn--p1acf
0.0.0.0 münchen.example.de xn--mnchen-3ya.example.de
*.münchen.example.de *.xn--mnchen-3ya.example.de

The mixed Unicode/punycode example above comes from #1039.

The parser rejects entries whose labels disappear during IDNA mapping. Without this guard, *.com.xn-- can collapse to *.com. and make the wildcard cache match every .com name. Invalid wildcard suffixes such as *..com and *. now fail before caching.

Regex handling and the ASCII fast path stay unchanged. Blocky keeps accepting trailing root dots, underscores and leading or trailing hyphens. Invalid entries count toward maxErrorsPerSource, with their line numbers in the error messages.

Changes

  • Accepts bracketed IPv6 literals used by URL and ABP syntax.
  • Applies consistent UTS List download: Improve error handling #46 mapping to host-list, hosts-file, and wildcard entries.
  • Rejects labels that disappear during IDNA mapping and validates wildcard suffixes before caching.
  • Adds parser, fuzz, and parser-to-cache integration coverage for the new behavior and malformed edge cases.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[List entry] --> B{Entry type}
  B -->|Regex| C[Preserve as written]
  B -->|Bracketed IPv6| D[Validate and strip brackets]
  B -->|Host or wildcard| E[Apply IDNA mapping when needed]
  E --> F{Any label vanished?}
  F -->|Yes| G[Reject with parse error]
  F -->|No| H[Validate domain or wildcard suffix]
  H --> I[Emit normalized cache entry]
  D --> I
  C --> I
Loading

Host list entries can be URL-style IPv6 literals such as
"||[2001:db8::1]^": the brackets are stripped and the address is emitted
as is. Brackets around anything else stay rejected.

Domain names in all three entry types (host list, hosts file, wildcard)
go through one IDNA profile: the UTS 0xERR0R#46 lookup mapping without the STD3
and label validity checks. This replaces the ToUnicode/ToASCII sequence,
which kept mixed punycode/Unicode names such as "имяенн.010.xn--p1acf"
in Unicode and then rejected them, and encoded uppercase Unicode
("MÜNCHEN.de") into a punycode form no query can match. Hosts file names
and wildcards skipped the conversion.

Malformed punycode labels, Base64-like junk and other invalid entries
fail as before, with the same messages, line positions and error limit.
Invalid UTF-8 now fails IDNA instead of being encoded into a dead label.
An empty "xn--" payload decodes to nothing and a label made only of
ignored code points (soft hyphen, zero-width space) maps to nothing, so
"*.com.xn--" came out as "*.com." with no parse error. The wildcard
cache folds the trailing dot away and the rule then matched every .com
name. Allowlists take the same path.

The shared conversion now rejects an entry whose mapping has fewer
non-empty labels than the input, counting the ideographic and fullwidth
full stops as separators since the mapping turns them into dots. This
covers host list, hosts file and wildcard entries. Trailing root dots
and dot-like separators in valid names keep working.

Wildcard suffixes are validated like plain entries, which closes
"*..com" and "*." widening to every name.

The unmarshal fuzzer checks that no accepted entry has fewer non-empty
labels than the field it came from, and a parser-to-cache test asserts
the malformed forms cannot match example.com.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Trailing-dot IDN entries do not match hosts-file or blocking lookups consistently.

Pull request overview

Adds bracketed IPv6 parsing and UTS #46 IDN normalization across list and hosts-file handling.

Changes:

  • Supports bracketed IPv6 literals.
  • Normalizes IDNs and validates wildcard entries.
  • Adds parser, fuzz, golden, and cache integration coverage.
File summaries
File Reviewed change
lists/parsers/testdata/fuzz/FuzzHostsUnmarshalText/b421722db68f7e5c Adds a fuzz regression corpus entry.
lists/parsers/hosts.go Implements IPv6 unwrapping and IDNA normalization.
lists/parsers/hosts_test.go Tests valid and invalid normalized entries.
lists/parsers/hosts_funcs_test.go Extends reference, fuzz, and invariant coverage.
cache/stringcache/testdata/golden/edge_cases.txt Adds malformed wildcard and IDNA fixtures.
cache/stringcache/list_scope_test.go Verifies malformed entries cannot widen cache scope.
Review details

Suppressed comments (2)

lists/parsers/hosts.go:186

  • Because toASCII preserves a trailing root dot, this now accepts 0.0.0.0 münchen.example.de. as xn--mnchen-3ya.example.de.. HostsFileResolver stores that exact key, but util.ExtractDomainOnly removes the query's final dot before lookup, so the new IDN hosts-file entry never resolves. Normalize the stored key or lookup input consistently and add resolver coverage.
		host, err := toASCII(string(field))
		if err != nil {
			return err
		}

lists/parsers/hosts.go:283

  • IDNA mapping intentionally preserves trailing root dots, but blocking lookups pass util.ExtractDomain(question) to the list cache with that dot removed. A newly accepted entry such as münchen.example.de. is therefore cached as xn--mnchen-3ya.example.de. and never matches DNS queries. Normalize root-dot handling at the cache boundary and cover this path with a blocking lookup test.
		host, err = toASCII(host)
		if err != nil {
			return "", err
		}
  • Files reviewed: 6/6 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.16%. Comparing base (45a242f) to head (f85f91b).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2261      +/-   ##
==========================================
+ Coverage   88.09%   88.16%   +0.07%     
==========================================
  Files         126      126              
  Lines        9967    10003      +36     
==========================================
+ Hits         8780     8819      +39     
+ Misses        923      921       -2     
+ Partials      264      263       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Lookups strip the trailing dot from the query name (util.ExtractDomain),
but the parsers kept it. An entry written as "example.com." went into
the string cache or the hosts file resolver with the dot and matched no
query. The wildcard cache trims the dot itself, so the gap only hit
plain entries and hosts file names.

The parsers now drop the dot after validation, which keeps "example.com.."
rejected. Tests cover the parser tables, the fuzz invariants, the
parser-to-cache scope check and a hosts file resolver lookup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants