Release 0.35.0 - #29
Merged
Merged
Conversation
…hart never exposed it The switch landed with `feat(tracker): let an operator close UDP scrape`. It reached `config.go`, `docker-compose.prod.yml` and the UDP guide — and neither `doc/reference/env.md`, which is where an operator looks for the list, nor the Helm chart, where a Kubernetes operator has no other way to set it. Found while assembling the release notes: the env reference went from 91 variables to 93 between 0.34.0 and now, and this was not one of them. `tracker.udp.scrapeEnabled` in values.yaml, wired in the tracker template. Verified in the rendered chart rather than in the source — the first render came back without it, which was the bind-mount cache serving a stale values.yaml, not a templating mistake. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
101 commits since 0.34.0 — 20 features, 68 fixes, the rest docs, tests, CI
and two performance changes. Most of it came out of an audit pass over the
four apps, where the findings that mattered came from probing the running
stack, not from static analysis: semgrep, trivy, gitleaks, gosec, hadolint,
actionlint, zizmor, govulncheck and osv-scanner were clean or already
arbitrated, while calling 131 GET and 90 write routes without a session — and
then with another member's session — was what turned up the real leak.
Security
safeFetchhanded the caller's credentials to whoever asked for aredirect. The SSRF guard re-validated every hop, so no private range could
be reached, but the caller's
initwas spread into everyfetch— headersincluded. A public target answering
302 Location: https://elsewherereceived the sixteen member-chosen webhook headers, the body HMAC, the ntfy
Authorizationor the object store's SigV4 signature. Fixed with anallowlist rather than the standard denylist, because the callers here set
their own credential headers. A
307/308to another origin carrying a bodyis now refused outright rather than replayed.
internal ids. The anonymous path existed for one field; the contributor list
had come along with it.
uriinstead ofrequest>uri— so passkeys had been written in cleartextsince it was added, without a warning.
/api/auth; an IP ban lockedstaff out of the very route that lifts it; the API served a CSP written for
the web application.
block a member could lift by rotating their own key.
encryptwas an unbounded oracle against the panic password, and atwelve-character password was derived at Node's defaults.
history.
limit.
Performance
was updating their own row 70 times per interval, and the increments are
commutative. Measured over five minutes at 1 964 credited announces/s, 50 000
members, steady state: WAL 113–125 MB → 45.8 MB at the 60 s default and
7.5 MB at 300 s; statements 1.00 per announce → 0.038. Table and index sizes
unchanged. Flushed in SMALL chunks, because a single-block flush defeats HOT
pruning and writes more WAL than the individual writes it replaces.
100 000 updates inside one
DOblock — one transaction — so nothing couldprune and
fillfactorlooked guilty. Remeasured with one transaction perannounce,
hnr_trackingreaches full HOT at 95 for +0.5% of table, where 70cost +35% for the same result.
concurrent peer ids could reach a hundred GiB/s of credit; an unauthenticated
scrape could reach Postgres.
Features
Members — a copy of your own record and what it deliberately leaves out;
three keys instead of one doing three jobs; alerts on saved searches; "sign out
everywhere"; where an account has been signed in from; drafts kept until sent.
Tracker — BEP 52: the second infohash a hybrid torrent announces under; a
partial seed is not a seed; an operator can close UDP scrape (BEP 15 has no
field for authentication in a scrape request, so a private tracker closes the
door rather than guarding it).
Torznab / integrations — a Prowlarr definition generated per instance; the
seeding rules a client was never told, and rates that were a lie; one line in
an IRC channel and the parser for it in the same breath.
Staff — who vouched for them and who they let in; an audit register across
the whole console; the moderation queue says which of these are yours; a
freeleech you can put on a single torrent.
Site — a statistics page, the site looking at itself; installable as a PWA,
with an icon size nobody made up; three things a catalogue needs when it gets
old.
Interface and accessibility
An interface pass produced five hundred findings; four systemic faults were
behind most of them and were fixed at the root. Since then: 49 form controls
had no accessible name, then 24 more that a first crude count had put at ~300 —
the difference was three classes of false positive, now documented in the test
that guards the number. A modal with no focus trap and a group row only a mouse
could open. A sanitiser that passed a phishing link and ate legitimate
attributes. Two locales hardcoded in a bilingual site, an error page in
English, numbers formatted in the wrong locale. Two pages that shipped the
server's clock and corrected it on hydration.
document.writeon therecovery-codes screen. Three pages rendering bare native buttons.
Correctness
to Postgres; shutdown now drains them.
stop()thenstart()left an orphan message stream delivering twice.db.executesilentlydrops the timestamp parser that
db.selecthonours.readable 400.
index.
saved URL never reopened the search.
job were invisible — and worse, the tests were not exercising what they
appeared to: account erasure passed while its Redis half did nothing at all.
Operators
Two new settings, both on the tracker, both optional:
TRACKER_STATS_FLUSH_INTERVAL60s0srestores the per-announce write.300sdivides WAL by 15 at the cost of five minutes of staleness on the counters a member sees.TRACKER_STATS_FLUSH_CHUNK10TRACKER_UDP_SCRAPE_ENABLEDshipped in 0.34.0's successor work but was neverin the env reference or the Helm chart; both are corrected here
(
tracker.udp.scrapeEnabled).15 migrations (0058–0072), applied at boot. Verified to apply to an empty
database: 204 tables, 1 672 columns, 578 indexes.
Shutdown grace period. The tracker now flushes byte credits (up to 10 s)
and drains in-flight UDP datagrams (up to 5 s) before the existing HnR drain
(8 s) — 33 s worst case. Compose gained
stop_grace_period: 45s; the chart'sterminationGracePeriodSecondsmoved 30 → 45. Docker's 10 s default wasalready cutting the HnR drain short before this release.
A NetworkPolicy is now shipped with the chart.
After upgrading
Migration 0072 sets
fillfactoronhnr_trackingbut deliberately rewritesnothing — a boot-time migration must not take an exclusive lock on a table in
the announce path. To realise it on existing pages:
Online, no maintenance window.
VACUUM FULL public.hnr_trackingis thealternative and takes an
ACCESS EXCLUSIVElock.Verification
Every CI gate, plus what CI cannot reach. The behavioural claims above were
checked against a running compiled stack rather than read off the source: the
Caddy scrub with a canary, the forum cascade, session revocation across two
devices, the credit-budget clamp (2 000 GiB claimed → 68 GiB credited), the UDP
scrape switch in both positions, the byte-credit batching against a real
tracker with real announces, and the redirect header-stripping against real
undici with two servers on two origins.
🤖 Generated with Claude Code