VYPR
Unrated severityNVD Advisory· Published Jun 16, 2026

Remark42: Cross-Site Scripting (XSS) on /api/v1/img via content-type spoofing

CVE-2026-48788

Description

Remark42 image proxy suffers from XSS due to content-type spoofing; an attacker can serve arbitrary HTML/JS under Remark42's origin, fixed in v1.16.0.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Remark42 image proxy suffers from XSS due to content-type spoofing; an attacker can serve arbitrary HTML/JS under Remark42's origin, fixed in v1.16.0.

Vulnerability

Remark42 versions 1.6.0 through 1.15.0 contain a Cross-Site Scripting (XSS) vulnerability in the image proxy at /api/v1/img [1]. The proxy downloads an image from a user-supplied URL by checking only the Content-Type header of the remote response; if it starts with image/, the proxy accepts the bytes without validating the actual content [1]. Later, when serving the cached image, http.DetectContentType inspects the bytes to set the response Content-Type. An attacker can exploit the inconsistency by hosting a URL that claims Content-Type: image/png but returns an HTML/JavaScript body: the download check passes, but the serving path emits Content-Type: text/html, causing the browser to render attacker-controlled HTML/JavaScript within Remark42's origin [1].

Exploitation

An attacker does not need a Remark42 account on the target instance. The attacker only needs to host a malicious upstream URL that advertises Content-Type: image/png but serves arbitrary HTML/JavaScript content [1]. The attacker then delivers the proxy link (e.g., https://remark42.example.com/api/v1/img?src=MALICIOUS_URL) to a victim via email, direct message, or by embedding it on another website. No user interaction beyond clicking the link is required; the browser will render the HTML/JavaScript in the context of the Remark42 origin.

Impact

Successful exploitation allows arbitrary JavaScript execution in the victim's browser within the Remark42 origin. This can lead to theft of authentication cookies, session hijacking, defacement, or extraction of sensitive information displayed by Remark42 [1]. The attacker gains the same privileges as the victim user, potentially including admin access if the victim is an administrator.

Mitigation

The vulnerability is fixed in Remark42 version 1.16.0 [2]. The fix includes rejecting non-image content-types in the image proxy and adding X-Content-Type-Options: nosniff headers to prevent MIME-sniffing [3]. Users should upgrade to v1.16.0 or later. No workaround is available for unpatched versions; administrators should restrict access to the image proxy if immediate upgrade is not possible.

AI Insight generated on Jun 17, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

1
78d6de6bce1e

Add X-Content-Type-Options and Referrer-Policy security headers

https://github.com/umputun/remark42Dmitry VerkhoturovFeb 22, 2026via nvd-ref
2 files changed · +14 1
  • backend/app/rest/api/rest.go+10 1 modified
    @@ -618,7 +618,14 @@ func cacheControl(expiration time.Duration, version string) func(http.Handler) h
     	}
     }
     
    -// securityHeadersMiddleware sets security-related headers: Content-Security-Policy and Permissions-Policy
    +// securityHeadersMiddleware sets security-related headers:
    +// - Content-Security-Policy: controls which resources the browser is allowed to load
    +// - Permissions-Policy: disables browser features (camera, mic, etc.) not needed by a comment widget
    +// - X-Content-Type-Options: prevents browsers from MIME-sniffing responses away from the declared type,
    +//   stopping e.g. a user-uploaded image from being reinterpreted as executable HTML/JS
    +// - Referrer-Policy: controls how much URL information leaks in the Referer header on cross-origin
    +//   requests; "strict-origin-when-cross-origin" sends only the origin (no path) to other domains
    +//   and nothing at all on HTTPS→HTTP downgrades
     func securityHeadersMiddleware(imageProxyEnabled bool, allowedAncestors []string) func(http.Handler) http.Handler {
     	return func(next http.Handler) http.Handler {
     		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    @@ -632,6 +639,8 @@ func securityHeadersMiddleware(imageProxyEnabled bool, allowedAncestors []string
     			}
     			w.Header().Set("Content-Security-Policy", fmt.Sprintf("default-src 'none'; base-uri 'none'; form-action 'none'; connect-src 'self'; frame-src 'self' mailto:; img-src %s; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src data:; object-src 'none'; frame-ancestors %s;", imgSrc, frameAncestors))
     			w.Header().Set("Permissions-Policy", "accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), display-capture=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), unload=(), window-management=()")
    +			w.Header().Set("X-Content-Type-Options", "nosniff")
    +			w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
     			next.ServeHTTP(w, r)
     		})
     	}
    
  • backend/app/rest/api/rest_test.go+4 0 modified
    @@ -352,6 +352,8 @@ func TestRest_securityHeaders(t *testing.T) {
     	defer resp.Body.Close()
     	assert.Equal(t, http.StatusOK, resp.StatusCode)
     	assert.Contains(t, resp.Header.Get("Content-Security-Policy"), "img-src *;")
    +	assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options"))
    +	assert.Equal(t, "strict-origin-when-cross-origin", resp.Header.Get("Referrer-Policy"))
     	teardown()
     
     	// check CSP with proxy enabled
    @@ -364,6 +366,8 @@ func TestRest_securityHeaders(t *testing.T) {
     	defer resp.Body.Close()
     	assert.Equal(t, http.StatusOK, resp.StatusCode)
     	assert.Contains(t, resp.Header.Get("Content-Security-Policy"), "img-src 'self';")
    +	assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options"))
    +	assert.Equal(t, "strict-origin-when-cross-origin", resp.Header.Get("Referrer-Policy"))
     }
     
     func TestRest_subscribersOnly(t *testing.T) {
    

Vulnerability mechanics

Root cause

"The image proxy validates the remote resource solely by the Content-Type header without inspecting the actual bytes, while the serving path derives the Content-Type by sniffing those bytes, creating an inconsistency that allows HTML/JavaScript to be served as text/html."

Attack vector

An attacker hosts a URL that advertises `Content-Type: image/png` but returns an HTML/JavaScript body. The download check in `downloadImage()` accepts it because the header starts with `image/`; the serving path sniffs the body and emits `Content-Type: text/html`; the browser renders the attacker-controlled HTML/JavaScript as a document within Remark42's origin [ref_id=1]. Exploitation requires no Remark42 account on the target instance; the attacker only needs to host the malicious upstream URL and deliver the proxy link to a victim by any means, such as email, direct message, or a link on another website [ref_id=1].

Affected code

The image proxy in `backend/app/rest/proxy/image.go` — `downloadImage()` (lines 189–206) validates the remote resource solely by inspecting the `Content-Type` header without examining the actual bytes. The serving path in `Handler()` (line 131) calls `ImgContentType()` in `backend/app/store/image/image.go` (lines 242–249), which uses `http.DetectContentType` and returns whatever MIME type the body sniffs to, including `text/html`.

What the fix does

The patch adds `X-Content-Type-Options: nosniff` and `Referrer-Policy: strict-origin-when-cross-origin` headers to the existing `securityHeadersMiddleware` in `backend/app/rest/api/rest.go` [patch_id=6240375]. The `X-Content-Type-Options: nosniff` header prevents browsers from MIME-sniffing the response body away from the declared Content-Type, which stops an attacker's HTML/JavaScript payload from being rendered as a document even if the body sniffs as `text/html` [ref_id=2]. The advisory notes that the full fix in v1.16.0 also includes strict content-type validation in `SafeImgContentType` and a sandboxed CSP on image endpoints [ref_id=1].

Preconditions

  • inputAttacker must host a URL that returns Content-Type: image/png but an HTML/JavaScript body
  • inputVictim must open the proxy URL (https:///api/v1/img?src=<base64(attacker-host)>) in a browser
  • authNo Remark42 account or authentication required on the target instance

Generated on Jun 17, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.