Address review feedback on PR #2044.
safehttp.Transport():
* Clone http.DefaultTransport instead of building a bare &http.Transport{} so
Proxy, ForceAttemptHTTP2, MaxIdleConns, IdleConnTimeout, TLSHandshakeTimeout
and ExpectContinueTimeout are inherited (the bare struct loses them all).
Verified by new TestTransport_PreservesDefaultTransportSettings.
* TestTransport_AllowsPublic: bound the dial of TEST-NET-3 with a 100ms
context so the test does not depend on real-world routing of 203.0.113.0/24,
and drop the dead dialer var.
proxy/image.go:
* Document Image.Transport contract: nil installs safehttp.Transport (SSRF-safe);
caller-supplied transport is the caller's responsibility.
* Replace the misleading "SSRF mitigated by safehttp.Transport" nolint comments
with one that points at the documented contract above.
The image proxy got an ssrfSafeTransport in commit aca0cff3 that resolves
DNS first, blocks any IP in private/reserved CIDRs, then dials by IP to
defeat DNS rebinding. The TitleExtractor used to construct comments'
PostTitle from Locator.URL — a user-supplied field — was missed by that
fix and kept using http.DefaultTransport. The hostname allowlist there
checks the parsed URL host but never the IP it resolves to, so a domain
suffix-matching an allowed host (or 127.0.0.1 itself when AllowedHosts
is empty) reaches the metadata service or any other internal endpoint.
The same gosec rule (G704) was excluded globally in .golangci.yml as part
of aca0cff3, so this gap was not caught by the linter either.
Extract the transport into a new safehttp package so it lives in one
place and can be reused, then pass safehttp.Transport() into the
TitleExtractor's http.Client at construction (cmd/server.go). The image
proxy switches to safehttp.Transport() too — same behaviour, no longer
duplicated.
Reproduction in title_test.go uses the production-style client to hit
an httptest.Server (always 127.0.0.1) and asserts the dialer refuses
even though "127.0.0.1" is in the allowed-domains list. A control case
shows the same setup without safehttp.Transport returns the page —
making the original vulnerability explicit.
Replace strings.Split(RemoteAddr, ":") with net.SplitHostPort for correct
IPv6 address extraction in vote deduplication and comment IP tracking.
Harden image proxy: add SSRF-safe transport blocking private/reserved IPs
at connection time with DNS rebinding protection, sanitize error messages
to prevent information leakage, add response size limit via io.LimitReader.
Fix shadowed error variables in BlockedUsers, SetTitle, and Delete methods.
Exclude gosec taint analysis false positives at linter config level.
@paskal
* sort imports, add missing copyright
* regenerate engine mock
* make all image.Store interface functions public
* go mod tidy
* make image.Store.Load return []byte instead of io.ReadCloser
* separate memory_store example RPC server to multiple files by handlers groups
* fix potential race on close
* move validate inside
* demote commit and cleanup in image.Store to non-exposed functions.
* replace immediate image commit with delayed via Submit
* minor: remove error logging, rename tests
* minor: err wrapping, comments wording
* clarify FileSystem.Save code
* attempt to fix#584 by making submitted image commits on a half of TTL
Co-authored-by: Dmitry Verkhoturov <paskal.07@gmail.com>
* fresh vendor
* enforce vendor flag with env
* move tree to service package
* move controllers routing to rest.go, remove separate subrouters
* cleanup/formatting
* missing vendor packages
* move rss, img proxy, robots and getstarted to top-level routes
* simplify logger middleware usage
* randomize server test ports
* check for non-admin in admin tests
* lint: minor warns
* admin test to jwt admin