Gives every browser on the machine ad and tracker blocking, including
WebKitGTK ones like surf that have no extension mechanism and so cannot run
uBlock Origin. 80,002 rules from the StevenBlack list load in 82ms and sit
in 9MB resident.
A proxy receives CONNECT doubleclick.net:443 before any TLS handshake, so a
blocked host is refused without decrypting anything. Ads and trackers are
third-party hosts, which is why host-level refusal captures nearly all of
the weight while leaving traffic sealed: no certificate authority in the
trust store, no CA private key on disk, and no visibility into a bank
session this program merely relays.
What that cannot do is cosmetic filtering and first-party ads, which need
TLS interception. That is omitted deliberately, and the README records the
reasoning and where the seam would be, rather than leaving it looking like
an oversight.
CONNECT to a blocked host 403, refused before the handshake
CONNECT to anything else tunnelled bytes, untouched
plain HTTP to a blocked host 204, so a beacon looks empty not failed
plain HTTP otherwise forwarded and relayed
Matching is by domain suffix, so a rule for doubleclick.net covers
stats.g.doubleclick.net; lookups walk the labels of the requested host
rather than the list. Hosts files, bare domain lists and the ||domain^
subset of Adblock syntax are accepted, while rules needing response
inspection are skipped rather than half-applied. Allow rules win at any
depth, and IP addresses are never blocked, since hosts files are full of
them as addresses.
Lists cache for a week and fall back to a stale copy when a refresh fails.
Connections are capped at 96 with a Drop guard releasing the slot even on
panic, and only the loopback interface is bound.
The catch-all rule now sends anything that did not match a more specific
rule to furst-serve, while `default` stays a plain browser. Because rules
fall through when their command is missing, that arrangement needs no new
mechanism: if furst-serve is not installed the reader rule is skipped and
the browser still gets the URL.
Firefox drops to a short list of sites that genuinely need it. Every reader
page carries a browser link, so the list can stay short.
Adds a README for furst-serve and rewrites the top-level one around the
whole pipeline: what it is for, what each piece does, measured output
sizes, and the limits — client-rendered pages, rate-limited search
endpoints, DRM.
Fetches a page, discards everything that is not the article, and renders
what is left as a self-contained HTML document with no scripts, no
stylesheets, no webfonts and no third-party requests. The reader rule in
the starter config already points at it.
Extraction follows Readability: score p/pre/td/blockquote by prose weight,
propagate to ancestors with decay, adjust by class and id names, scale by
one minus link density, then take the winner plus sibling nodes that also
read like body copy. Serialisation runs against a tag whitelist, with
unlisted elements contributing their children but no tag of their own, so
wrapper divs disappear.
Handling for what real pages actually do:
- follow meta http-equiv=refresh stubs, including inside noscript
- fall back to data-src when src holds a lazy-load placeholder
- take the first srcset candidate, the smallest, not the last
- decode via Content-Type charset, then meta charset, then UTF-8
- resolve links against the post-redirect URL so file:// output works
- ignore script and style text so a page cannot inflate its own score
Output goes to a cache file named by a hash of the URL and opens in
$FURST_BROWSER, $BROWSER, or the first light browser on $PATH; --html,
--text, --out and --stdin cover the other uses.
Restructures the repository as a workspace so the router keeps its two
dependencies and its fast build.
Sits where the default browser used to and dispatches each URL to the
cheapest tool that can handle it: mpv for video, zathura for PDFs, a light
WebKit browser for reading, Firefox only when nothing else will do.
Rules live in ~/.config/furst/rules.toml and are matched in order. When a
rule's command is missing from $PATH the router falls through to the next
match, then to the default, so a config may name tools that do not exist
yet without breaking today.
- host patterns match apex plus subdomains, with = for exact and * for any
- paths glob case-insensitively, contains matches the raw URL
- {url} {url_enc} {host} {path} {scheme} placeholders, URL appended if unused
- terminal = true wraps a handler in $TERMINAL -e for TUI tools
- --explain shows the parse and every candidate in priority order
- --install registers a .desktop entry as the system default browser
Host parsing strips userinfo with rfind('@') so that
https://bank.example@evil.example/ routes on evil.example. Handlers are
exec'd rather than forked, leaving no process behind.