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

umputun / remark42 / 26203935339
85%

Build:
DEFAULT BRANCH: master
Ran 21 May 2026 03:40AM 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:37AM UTC coverage: 84.555% (+0.3%) from 84.273%
26203935339

push

github

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

* 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 ex... (continued)

85 of 86 new or added lines in 4 files covered. (98.84%)

2 existing lines in 1 file now uncovered.

6345 of 7504 relevant lines covered (84.55%)

34.7 hits per line

Uncovered Changes

Lines Coverage ∆ File
1
88.74
4.41% backend/app/rest/proxy/image.go

Coverage Regressions

Lines Coverage ∆ File
2
83.67
-0.2% backend/app/cmd/server.go
Jobs
ID Job ID Ran Files Coverage
1 26203935339.1 21 May 2026 03:40AM UTC 52
84.55
GitHub Action Run
Source Files on build 26203935339
  • Tree
  • List 52
  • Changed 5
  • Source Changed 0
  • Coverage Changed 5
Coverage ∆ File Lines Relevant Covered Missed Hits/Line
  • Back to Repo
  • 0e208614 on github
  • Prev Build on 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