Cover byte fetcher gated by destination class #57
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
Spec: #55. Originating bug: #47. Architecture and rejected alternatives:
docs/adr/0007-backend-hosts-cover-bytes.md. Domain vocabulary:CONTEXT.md.Do not close #47 or #55 from this ticket.
What to build
The backend gains the ability to fetch cover bytes from a third-party host over plain TLS and store them through the content-addressed store, with a gate that decides where the request is allowed to go.
This gate is the security control of the whole feature and deserves care. Every cover URL originates in a page controlled by someone else, so fetching one points the deployment's server at an address an attacker may choose. The server sits somewhere no internet client can reach — its own network — so a naive fetcher is a probe of internal services handed to whoever can edit a manga page's metadata tag. Validating the response does not address this: by the time the bytes are inspected the request has already happened, and the timing of the failure alone tells an attacker what is listening.
The gate is therefore destination-class, applied before any connection:
httpsonly.127.0.0.1, which any hostile page can publish.Note that this deliberately differs from the host allowlist that gates series URLs, and the ADR records why: cover hosts are CDNs that move independently of their Site — demonicscans serves its covers from an unrelated host — so an allowlist would stop producing Covers the day a Site switched CDN, and that failure would look exactly like the bug this whole effort is fixing.
For testability, name resolution is injected rather than called directly, defaulting to the real resolver. This mirrors how the poller already injects its fetcher, and it exists because the most important case — a hostname resolving into private space — cannot otherwise be produced in a test.
Acceptance criteria
Blocked by
Implemented on branch issue-57-cover-fetch-gate. Added plain-net/http TLS cover fetcher with injected DNS resolution, resolve-then-classify SSRF gate, redirect revalidation, direct dialing, shared body cap, and safe image content-type allowlist. Wired non-kagane covers through generic content-addressed Store.PutCover/GetCover while preserving kagane browser path and failure isolation. Added observable no-connection, DNS-range, redirect, streaming-cap, non-image, storage, and end-to-end poller tests. Verification: go test -count=1 ./...; go vet ./...; both pass. Closing PR will reference #57.