Husk v0.1.9 - Adblock parser hardening
Patch release. The fix v0.1.8 advertised but only half-shipped.
Downloads
| File | Size | SHA-256 |
|---|---|---|
Husk-Portable-0.1.9.zip |
2.2 MB | 1df3f08eb7bd7a7a41d60e08d9a6e17ea86c77b4e10337efb28e1061d8027423 |
husk.exe (standalone, for in-place update) |
4.6 MB | b320e45931a0aac22498ce875d8d1dfe0d896947e55d9a045fd5a3d5dd879a69 |
Verify before running
Get-FileHash .\Husk-Portable-0.1.9.zip -Algorithm SHA256Match the output against the hash above. If it differs, do not run.
What's fixed
v0.1.8 fixed substring matching. The DEEPER bug was parsing.
v0.1.8 made the JS interceptor match by hostname instead of substring,
which solved the "searching the word facebook on DuckDuckGo dies"
case. But the URL bar still went silently dead under load, the
log added in v0.1.8 told us the Rust top-level navigator was blocking
duckduckgo.com outright. EasyList doesn't ship that rule. So how
did DDG end up in our blocked-domain set?
The culprit was the EasyList line parser, written months ago for the
clean form ||example.com^ and never revisited. EasyList rules in
the wild look like this:
||duckduckgo.com^$image,domain=somesite.com
Which means: block requests to duckduckgo.com only when the page
they're loaded from is somesite.com. The $domain=… is a scope
modifier. The naive parser walked the line character-by-character
until it hit ^, kept whatever was before it, and added that to the
global blocked-domain set. So any host that appeared anywhere in
a scoped EasyList rule became a global block. DDG was one of hundreds.
After the v0.1.8 EasyList fetch, the set contained 239 short / no-dot
entries that had been chopped at the wrong delimiter, collector-,
faro-collector-prod-, util-, et al. Those are harmless (no real
URL matches them) but they were the smoking gun: the parser was
eating rules it had no business eating.
The fix
Three layers in blocker.rs::parse_into:
- Only the canonical
||host^form is treated as a global block.
Anything ending in^$modifier,…is skipped, they're scoped to a
specific page or request type and need a real rule engine to honour
correctly, which we don't ship yet. - The parsed host must contain at least one dot. Stops single-label
entries likecomfrom ever being inserted, even if a source list
accidentally ships||com^. - The host-suffix walk in
should_block_top_levelwas already
correct (duckduckgo.com → com, exact-string match at each level),
so once the bad entries stop being inserted the walk does the right
thing automatically.
After the fix the same EasyList drop yields 6 suspicious entries
(real short tracker domains like ad.gt and hh.ru), down from 239
parser artefacts.
Upgrade path
- From v0.1.8: in-app
Settings → About → Check for updatesand
click through, the v0.1.7 updater hardening landed clean. - From v0.1.7 or earlier: same in-app flow, or grab the standalone
husk.exe(above) and drop it next to your existingHuskData/.
What's unchanged from v0.1.8
The substring-to-hostname matcher in the JS interceptor stays. The
per-page PAGE_EXEMPTIONS table (YouTube, Twitch, youtu.be) stays.
The should_block_top_level diagnostic log stays, keyed off
husk-dns.log via the existing privacy-redacting writer, so the next
"URL bar does nothing" report tells us which rule (label level) was
hit without leaking actual hostnames to disk.
Known limitations carried over
- macOS + Linux still v0.2 targets.
- YouTube ads pass through (cosmetic filtering on the roadmap).
- Scoped EasyList rules (
||host^$domain=…) are dropped entirely
rather than applied correctly. A full rule engine is what comes
next once the host-only path is solid.
If Husk is useful to you, support keeps it free, ad-free and
account-free: husk.run/donate.