• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

umputun / remark42 / 26203356486
85%
master: 85%

Build:
Build:
LAST BUILD BRANCH: refs/tags/backend/v1.16.0
DEFAULT BRANCH: master
Ran 21 May 2026 03:20AM UTC
Jobs 1
Files 52
Run time 1min
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

21 May 2026 03:17AM UTC coverage: 84.569% (+0.3%) from 84.273%
26203356486

Pull #2067

github

paskal
fix(security): reject non-image content-types in image proxy and /picture/ to prevent stored XSS

The /api/v1/img proxy and /api/v1/picture/{user}/{id} endpoints emitted
http.DetectContentType on the served bytes as the response Content-Type. A
controlled upstream serving Content-Type: image/png with an HTML body passed
the upstream check (only the response header was inspected, not the body),
and the body bytes then sniffed back to text/html — so the proxy served the
attacker's HTML from the remark42 origin. Browsers honoured the declared
text/html and executed the response as a document with access to cookies and
CSRF tokens. Affected from v1.6.0 (April 2020) through v1.15.0; verified live
via published docker images.

Layered defense applied to both handlers:

- rest.SafeImgContentType (in backend/app/rest/) validates sniffed content
  against a strict allowlist: image/png, image/jpeg, image/gif, image/webp,
  image/bmp, image/x-icon. Anything else (HTML, XML, SVG, plain text,
  octet-stream, or any future image type the stdlib sniffer may learn) is
  rejected with no body echo. SVG is implicitly excluded — it sniffs as
  text/xml or text/plain, never image/svg+xml, and SVG can execute scripts
  when navigated to top-level. The previous octet-stream → image/* fallback
  is gone.
- Per-endpoint Content-Security-Policy override sets
  "default-src 'none'; sandbox; frame-ancestors 'none'" on every response
  (success, 304, or error). Sandbox neuters scripts even if Content-Type
  ever regresses. The same policy is also applied to all /api/v1/* via
  apiCSPMiddleware as defense-in-depth.
- Content-Disposition: inline; filename="image" frames the response as a
  file rather than a renderable document.
- /picture/ rejection paths set Cache-Control: no-store so 4xx responses
  are never cached.

The defense headers and the strict ETag matcher are extracted as
rest.SetImageDefenseHeaders and rest.EtagMatches in the shared rest package
(consumed by both pro... (continued)
Pull Request #2067: fix(security): reject non-image content-types in image proxy and /picture/ to prevent stored XSS

87 of 90 new or added lines in 4 files covered. (96.67%)

5 existing lines in 1 file now uncovered.

6341 of 7498 relevant lines covered (84.57%)

34.78 hits per line

Uncovered Changes

Lines Coverage ∆ File
3
88.28
3.95% backend/app/rest/proxy/image.go

Coverage Regressions

Lines Coverage ∆ File
5
88.28
3.95% backend/app/rest/proxy/image.go
Jobs
ID Job ID Ran Files Coverage
1 26203356486.1 21 May 2026 03:20AM UTC 52
84.57
GitHub Action Run
Source Files on build 26203356486
  • Tree
  • List 52
  • Changed 4
  • Source Changed 0
  • Coverage Changed 4
Coverage ∆ File Lines Relevant Covered Missed Hits/Line
  • Back to Repo
  • Pull Request #2067
  • PR Base - master (#26202461678)
  • Delete
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc