From 700de20225c3d96a33a87308b93ecc900c2e1a2d Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Wed, 12 Aug 2026 00:34:24 +0700 Subject: [PATCH] fix: fetch route fails closed for unknown sites (#94) Security review found the route's old default handed the TLS fetcher to any unrecognised site string; unreachable today because the shared read gates first, but a trap for a future caller that skips the gate. Returns nil now, matching the registry's zero-entry handling and the function's own docstring. Review's log-injection finding (unquoted series_url in read.go error wraps) was verified against Go's url.Parse and does not hold: control characters are rejected anywhere in the URL, so a client-supplied value reaching the log cannot carry a newline. --- backend/internal/latest/poller.go | 8 +++++++- backend/internal/latest/poller_test.go | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/internal/latest/poller.go b/backend/internal/latest/poller.go index cfbdea8..8f31abc 100644 --- a/backend/internal/latest/poller.go +++ b/backend/internal/latest/poller.go @@ -124,7 +124,13 @@ func (p *Poller) storeCover(ctx context.Context, sr store.Series, sourceURL stri // absent, the entry's Fallback decides whether plain TLS may take over. One // routing rule for the poll and the acquirer, so the two cannot drift apart. func fetcherFor(site string, browser, tls Fetcher) Fetcher { - s := sites[site] + s, known := sites[site] + if !known { + // No registry entry means nothing to fetch or parse; fail closed even + // though the only caller gates first, so a future caller that skips + // the gate cannot hand an arbitrary https URL to the TLS fetcher. + return nil + } if s.Browser == nil { return tls } diff --git a/backend/internal/latest/poller_test.go b/backend/internal/latest/poller_test.go index d76a2b4..6bbb6e5 100644 --- a/backend/internal/latest/poller_test.go +++ b/backend/internal/latest/poller_test.go @@ -959,6 +959,8 @@ func TestFetcherForRoutesNovelSites(t *testing.T) { // browser-less deployment: kagane is nothing, novelfull degrades to TLS {"kagane", nil, tls, nil}, {"novelfull", nil, tls, tls}, + // unknown site: fail closed — nothing to fetch or parse + {"mangadex", browser, tls, nil}, } for _, tc := range cases { t.Run(tc.site, func(t *testing.T) {