
Loading

Loading
We use strictly necessary cookies to run this site, and analytics cookies to understand how it's used. See our Privacy Policy for details.
A GitHub-reviewed security advisory, not necessarily under active exploitation the way our CISA KEV advisories are, but a real, disclosed vulnerability in an open-source dependency.
Affected packages
Vulnerable: >= 1.0.0, < 1.16.2
Patched in 1.16.2
Vulnerable: >= 1.0.0, <= 1.2.0
Details
Remark42 applied chi's middleware.RealIP globally, trusting True-Client-IP / X-Real-IP / X-Forwarded-For headers without verifying the request came from a trusted proxy. The per-IP rate limiters key on the overwritten r.RemoteAddr, so any client can bypass every per-IP rate limiter by rotating spoofed IP headers. The same spoofed IP also drives other per-IP controls (stored comment IP, anonymous identity derivation, and --votes-ip vote de-duplication). This affects deployments reachable directly from the internet (Remark42's built-in HTTP, static TLS, and ACME modes are all documented and supported), as well as any proxied deployment where the proxy forwards an attacker-controlled recognised header.
The router applies middleware.RealIP globally, ahead of the per-route limiters, without any check that the server is behind a trusted proxy (in v1.16.1, backend/app/rest/api/rest.go:207).
The rateLimiter function (v1.16.1, backend/app/rest/api/rest.go:769-779) builds a tollbooth limiter with IPLookup{Name: "RemoteAddr"}, which reads r.RemoteAddr. Since chi's middleware.RealIP (v1.16.1, backend/vendor/github.com/go-chi/chi/v5/middleware/realip.go:42-55) unconditionally overwrites r.RemoteAddr from True-Client-IP > X-Real-IP > first X-Forwarded-For (accepting any syntactically valid IP), any client can set its apparent IP to an arbitrary value.
This bypasses every per-IP rate limiter that runs after RealIP: /auth (2/s), avatars (100/s), the image proxy (10/s), open / protected / admin route groups (10/s), root routes (50/s), and /web/* (20/s). Comment and vote creation go through the update limiter, whose server default is UPDATE_LIMIT=0.5/s (burst 1), not 10/s. The global 1,000 concurrent-request throttle still applies, so this is a per-IP-limit bypass rather than truly unlimited throughput. chi's own documentation warns that middleware.RealIP should be used only behind a trusted proxy.
# Deploy Remark42 reachable directly from the internet (built-in HTTP / TLS / ACME, no proxy that overwrites the headers).
# Comment creation is limited to UPDATE_LIMIT (default 0.5/s, burst 1) per IP.
# Rotate X-Real-IP across many requests; each distinct value gets its own token bucket.
for i in $(seq 1 100); do
curl -s -o /dev/null -w '%{http_code}\n' \
-H "X-Real-IP: 192.0.2.$i" \
-X POST 'https://remark42.example.com/api/v1/comment?site=site1' \
-H 'Authorization: Bearer YOUR_JWT' \
-H 'Content-Type: application/json' \
-d '{"text":"spam","locator":{"site":"site1","url":"https://example.com/post"}}'
done | sort | uniq -c
# With rotating headers the responses stay 201; without them, requests beyond the burst return 429.
By rotating the spoofed client IP an attacker bypasses the per-IP token buckets on all rate-limited endpoints and defeats other per-IP controls, enabling: (a) spam comment creation beyond the configured per-IP limit, (b) confirmation-email / notification flooding to supplied addresses, (c) high-volume outbound image fetching and resource consumption via the image proxy (its transport still blocks private/reserved destinations and DNS rebinding, so this is amplification, not new SSRF reach), (d) authentication-endpoint abuse, (e) where --votes-ip is enabled, defeating per-IP vote de-duplication (ballot stuffing), and (f) minting fresh anonymous identities and poisoning the stored comment IP and request logs, since the anonymous user ID is derived from name + RemoteAddr. The global concurrency ceiling and per-request timeouts remain, so availability impact is limited.
Mitigation support was added in v1.16.2 (umputun/remark42#2116, follow-up in #2118). Upgrading alone does not remediate the issue: --trusted-proxy must be configured.
The fix adds --trusted-proxy / TRUSTED_PROXY. When set, the forwarding headers (X-Real-IP, then CF-Connecting-IP, then the leftmost public X-Forwarded-For) are honoured only when the direct TCP peer falls inside a trusted-proxy network; for a request from any other peer those headers are dropped and the real socket IP is used. When it is unset, the previous trust-all behaviour is preserved for backward compatibility and a startup warning is logged, so an un-configured v1.16.2 remains exploitable. True-Client-IP is no longer recognised. TRUSTED_PROXY takes a comma-separated list; on the command line, repeat --trusted-proxy per entry.
Required configuration:
--trusted-proxy=127.0.0.1/32 (only when Remark42 is not actually reached over loopback).--trusted-proxy at the proxy's network only, block direct access to Remark42, and make the proxy overwrite X-Real-IP with the real client and discard client-supplied forwarding headers. A proxy that merely appends to X-Forwarded-For, or that forwards a client-supplied X-Real-IP, still lets the client choose the reported IP. Note that the shipped nginx manual sets X-Real-IP but does not strip a client True-Client-IP.True-Client-IP, X-Real-IP, and X-Forwarded-For at the edge and set a known-safe client-IP header.See the Trusted proxies and client IP docs.
EPSS, exploit probability
Low0.22%
estimated chance of real-world exploitation in the next 30 days, higher than 12.4% of every CVE FIRST.org scores
Refreshed 11/10/2026, via FIRST.org's EPSS model, not CVSS, this measures likelihood of exploitation, not how severe it would be.
Talk to us about this
Find out whether this dependency (or others like it) is actually reachable in your codebase.
Get help patching, or building dependency-scanning into your pipeline going forward.
A secure code review or supply chain review finds this before an advisory does.