seerr: Path traversal to remote code execution via the avatar proxy cache filename

A malicious or compromised Jellyfin/Emby media server returns an ETag header with a traversal sequence through the avatar proxy, overwrites the application entry point and, on the next restart, runs code as the node user.

Advisory ID: TP-2026-041
Product: seerr (Self-hosted open-source application that manages media requests for Jellyfin, Emby and Plex media servers)
Vulnerability type: Path traversal to remote code execution (CWE-22)
CVE: CVE-2026-73291
CVSS 3.1: 7.1 (High) · CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:H
Vendor advisory: GHSA-mc6w-69r3-62h8
Affected versions: < 3.4.0
Fixed in: 3.4.0
Reported: 2 July 2026

Summary

seerr is a self-hosted application that manages media requests for connected Jellyfin, Emby and Plex servers. The avatar proxy stores downloaded avatars in a cache and builds the filename of that cache from the media server's ETag response header without removing path separators. Because path.join collapses the ../ sequence and the file extension is taken from the media server's Content-Type, an attacker can drive the write path out of the cache directory and overwrite /app/dist/index.js, the entry point run by npm start. The triggering endpoint is unauthenticated, and the attacker controls both the target path and the written content, so on the next container restart arbitrary code runs as the node user. The precondition is control of the media server's response, that is a malicious or compromised server or an on-path position on the connection, which is unencrypted by default. turingpoint verified the chain end to end and reported it to the vendor, who fixed it in 3.4.0.

Root cause

The avatar proxy builds the cache filename ${maxAge}.${expireAt}.${etag}.${extension} and passes it to path.join and fs.writeFile (server/lib/imageproxy.ts:321, :328), where path.join collapses the ../ sequence so the path can leave the cache directory. The etag segment is taken directly from the media server's ETag response header with only double quotes stripped (server/lib/imageproxy.ts:281), while the file extension is derived from the media server's Content-Type via mime.getExtension (:273), so text/javascript becomes js. An ETag of "/../../../../../../dist/index" with Content-Type: text/javascript therefore writes to /app/dist/index.js, the entry point that npm start runs as node dist/index.js, so both the write path and the written bytes are controlled by the attacker. The write is reached through the unauthenticated route GET /avatarproxy/:jellyfinUserId (server/index.ts:252, no auth middleware) for a user whose id matches ^[a-f0-9]{32}$ and has an avatar version set; the avatar is fetched from the configured media server via getHostname() (avatarproxy.ts:44). The precondition is control of the media server's response, that is a malicious or compromised server or an on-path position on the connection, which is unencrypted by default (server/lib/settings/index.ts:436, useSsl:false).

Proof of Concept

# The media server under attacker control answers the avatar fetch with:
HTTP/1.1 200 OK
ETag: "/../../../../../../dist/index"
Content-Type: text/javascript

<attacker JavaScript>

# A single unauthenticated request triggers the cached write:
GET /avatarproxy/<32-hex jellyfinUserId>

path.join collapses the ../ sequence so the file is written to /app/dist/index.js; on the next container restart npm start runs node dist/index.js and executes the injected JavaScript. turingpoint confirmed this live on ghcr.io/seerr-team/seerr:latest (v3.3.0): an unauthenticated GET /avatarproxy/<32hex> overwrote /app/dist/index.js, and after docker restart the payload ran as uid=1000(node), wrote /tmp/RCE_PROOF and sent a network beacon to the attacker host.

Impact

  • Overwrite of arbitrary files in the container, including the application entry point /app/dist/index.js.
  • Remote code execution as the node user (uid 1000) on the next container restart.
  • Tampering with the SQLite database and the application configuration on disk.
  • No authentication for the triggering request; the precondition is control of the media server's response.

References

Is Something Like This in Your Software?

Our team found this vulnerability in the course of its work. Have your applications reviewed by the same specialists, with a penetration test from turingpoint.