Every CVE Search/ Get API key

CVE-2025-23221

Fedify has an Infinite loop and Blind SSRF found inside the Webfinger mechanism

5.4 MediumFix available

Fedify is a TypeScript library for building federated server apps powered by ActivityPub and other standards. This vulnerability allows a user to maneuver the Webfinger mechanism to perform a GET request to any internal resource on any Host, Port, URL combination regardless of present security mechanisms, and forcing the victim’s server into an infinite loop causing Denial of Service. Moreover, this issue can also be maneuvered into performing a Blind SSRF attack. This vulnerability is fixed in 1.0.14, 1.1.11, 1.2.11, and 1.3.4.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
Low
Integrity
None
Availability
Low

Exploit likelihood

0.62%Up 0.6 points since Jan 21, 2025
90%50%10%1%

What each source reports

CVE List Score used 5.4

Fedify has an Infinite loop and Blind SSRF found inside the Webfinger mechanism

Fedify is a TypeScript library for building federated server apps powered by ActivityPub and other standards. This vulnerability allows a user to maneuver the Webfinger mechanism to perform a GET request to any internal resource on any Host, Port, URL combination regardless of present security mechanisms, and forcing the victim’s server into an infinite loop causing Denial of Service. Moreover, this issue can also be maneuvered into performing a Blind SSRF attack. This vulnerability is fixed in 1.0.14, 1.1.11, 1.2.11, and 1.3.4.

Record
CVE-2025-23221
CVSS 3.1
5.4 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:L
Weakness
CWE-835, CWE-918
Updated
2026-11-02
EUVDEUVD-2025-0153 5.4

Fedify is a TypeScript library for building federated server apps powered by ActivityPub and other standards. This vulnerability allows a user to maneuver the Webfinger mechanism to perform a GET request to any internal resource on any Host, Port, URL combination regardless of present security mechanisms, and forcing the victim’s server into an infinite loop causing Denial of Service. Moreover, this issue can also be maneuvered into performing a Blind SSRF attack. This vulnerability is fixed in 1.0.14, 1.1.11, 1.2.11, and 1.3.4.

Record
EUVD-2025-0153
CVSS 3.1
5.4 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:L
Updated
2026-11-03
GitHub AdvisoriesGHSA-c59p-wq67-24wx 5.4

Infinite loop and Blind SSRF found inside the Webfinger mechanism in @fedify/fedify

Summary

This vulnerability allows a user to maneuver the Webfinger mechanism to perform a GET request to any internal resource on any Host, Port, URL combination regardless of present security mechanisms, and forcing the victim’s server into an infinite loop causing Denial of Service. Moreover, this issue can also be maneuvered into performing a Blind SSRF attack.

Details

The Webfinger endpoint takes a remote domain for checking accounts as a feature, however, as per the ActivityPub spec (https://www.w3.org/TR/activitypub/#security-considerations), on the security considerations section at B.3, access to Localhost services should be prevented while running in production.

The lookupWebFinger function, responsible for returning an actor handler for received actor objects from a remote server, can be abused to perform a Denial of Service (DoS) and Blind SSRF attacks while attempting to resolve a malicious actor’s object. On Fedify, two client-facing functions implement the lookupWebFinger function- getActorHandle, and lookupObject, which are both used as a wrapper for the vulnerable lookup function. As the lookupObject function is implemented only for CLI usage, we won’t focus our PoC and explanation on it, but it is still vulnerable in the same way getActorHandle is.

The getActorHandle function is a wrapper function for the getActorHandleInternal function (both present at /src/vocab/actor.ts):

async function getActorHandleInternal(
  actor: Actor | URL,
  options: GetActorHandleOptions = {},
): Promise<`@${string}@${string}` | `${string}@${string}`> {
  const actorId = actor instanceof URL ? actor : actor.id;
  if (actorId != null) {
    const result = await lookupWebFinger(actorId, {
      userAgent: options.userAgent,
      tracerProvider: options.tracerProvider,
    });
    if (result != null) {
      const aliases = [...(result.aliases ?? [])];
      if (result.subject != null) aliases.unshift(result.subject);
      for (const alias of aliases) {
        const match = alias.match(/^acct:([^@]+)@([^@]+)$/);
        if (match != null) {
          const hostname = new URL(`https://${match[2]}/`).hostname;
          if (
            hostname !== actorId.hostname &&
            !await verifyCrossOriginActorHandle(
              actorId.href,
              alias,
              options.userAgent,
              options.tracerProvider,
            )
          ) {
            continue;
          }
          return normalizeActorHandle(`@${match[1]}@${match[2]}`, options);
        }
      }
    }
  }
  if (
    !(actor instanceof URL) && actor.preferredUsername != null &&
    actor.id != null
  ) {
    return normalizeActorHandle(
      `@${actor.preferredUsername}@${actor.id.host}`,
      options,
    );
  }
  throw new TypeError(
    "Actor does not have enough information to get the handle.",
  );
}

The actorId parameter containing a URL of the actor ID sinks into the lookupWebFinger function which is a wrapper for the lookupWebFingerInternal:

async function lookupWebFingerInternal(
  resource: URL | string,
  options: LookupWebFingerOptions = {},
): Promise<ResourceDescriptor | null> {
  if (typeof resource === "string") resource = new URL(resource);
  let protocol = "https:";
  let server: string;
  if (resource.protocol === "acct:") {
    const atPos = resource.pathname.lastIndexOf("@");
    if (atPos < 0) return null;
    server = resource.pathname.substring(atPos + 1);
    if (server === "") return null;
  } else {
    protocol = resource.protocol;
    server = resource.host;
  }
  let url = new URL(`${protocol}//${server}/.well-known/webfinger`);
  url.searchParams.set("resource", resource.href);
  while (true) {
    logger.debug(
      "Fetching WebFinger resource descriptor from {url}...",
      { url: url.href },
    );
    let response: Response;
    try {
      response = await fetch(url, {
        headers: {
          Accept:…
Record
GHSA-c59p-wq67-24wx
CVSS 3.1
5.4 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:L
Weakness
CWE-835, CWE-918
Updated
2026-11-02
NVD 5.4

Fedify is a TypeScript library for building federated server apps powered by ActivityPub and other standards. This vulnerability allows a user to maneuver the Webfinger mechanism to perform a GET request to any internal resource on any Host, Port, URL combination regardless of present security mechanisms, and forcing the victim’s server into an infinite loop causing Denial of Service. Moreover, this issue can also be maneuvered into performing a Blind SSRF attack. This vulnerability is fixed in 1.0.14, 1.1.11, 1.2.11, and 1.3.4.

Record
CVE-2025-23221
CVSS 3.1
5.4 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:L
Weakness
CWE-835, CWE-918
Updated
2026-11-02
OSV 5.4

Fedify has an Infinite loop and Blind SSRF found inside the Webfinger mechanism

Fedify is a TypeScript library for building federated server apps powered by ActivityPub and other standards. This vulnerability allows a user to maneuver the Webfinger mechanism to perform a GET request to any internal resource on any Host, Port, URL combination regardless of present security mechanisms, and forcing the victim’s server into an infinite loop causing Denial of Service. Moreover, this issue can also be maneuvered into performing a Blind SSRF attack. This vulnerability is fixed in 1.0.14, 1.1.11, 1.2.11, and 1.3.4.

Record
CVE-2025-23221
CVSS 3.1
5.4 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:L
Weakness
CWE-835, CWE-918
Updated
2026-11-02
OSVGHSA-c59p-wq67-24wx 5.4

Infinite loop and Blind SSRF found inside the Webfinger mechanism in @fedify/fedify

Summary

This vulnerability allows a user to maneuver the Webfinger mechanism to perform a GET request to any internal resource on any Host, Port, URL combination regardless of present security mechanisms, and forcing the victim’s server into an infinite loop causing Denial of Service. Moreover, this issue can also be maneuvered into performing a Blind SSRF attack.

Details

The Webfinger endpoint takes a remote domain for checking accounts as a feature, however, as per the ActivityPub spec (https://www.w3.org/TR/activitypub/#security-considerations), on the security considerations section at B.3, access to Localhost services should be prevented while running in production.

The lookupWebFinger function, responsible for returning an actor handler for received actor objects from a remote server, can be abused to perform a Denial of Service (DoS) and Blind SSRF attacks while attempting to resolve a malicious actor’s object. On Fedify, two client-facing functions implement the lookupWebFinger function- getActorHandle, and lookupObject, which are both used as a wrapper for the vulnerable lookup function. As the lookupObject function is implemented only for CLI usage, we won’t focus our PoC and explanation on it, but it is still vulnerable in the same way getActorHandle is.

The getActorHandle function is a wrapper function for the getActorHandleInternal function (both present at /src/vocab/actor.ts):

async function getActorHandleInternal(
  actor: Actor | URL,
  options: GetActorHandleOptions = {},
): Promise<`@${string}@${string}` | `${string}@${string}`> {
  const actorId = actor instanceof URL ? actor : actor.id;
  if (actorId != null) {
    const result = await lookupWebFinger(actorId, {
      userAgent: options.userAgent,
      tracerProvider: options.tracerProvider,
    });
    if (result != null) {
      const aliases = [...(result.aliases ?? [])];
      if (result.subject != null) aliases.unshift(result.subject);
      for (const alias of aliases) {
        const match = alias.match(/^acct:([^@]+)@([^@]+)$/);
        if (match != null) {
          const hostname = new URL(`https://${match[2]}/`).hostname;
          if (
            hostname !== actorId.hostname &&
            !await verifyCrossOriginActorHandle(
              actorId.href,
              alias,
              options.userAgent,
              options.tracerProvider,
            )
          ) {
            continue;
          }
          return normalizeActorHandle(`@${match[1]}@${match[2]}`, options);
        }
      }
    }
  }
  if (
    !(actor instanceof URL) && actor.preferredUsername != null &&
    actor.id != null
  ) {
    return normalizeActorHandle(
      `@${actor.preferredUsername}@${actor.id.host}`,
      options,
    );
  }
  throw new TypeError(
    "Actor does not have enough information to get the handle.",
  );
}

The actorId parameter containing a URL of the actor ID sinks into the lookupWebFinger function which is a wrapper for the lookupWebFingerInternal:

async function lookupWebFingerInternal(
  resource: URL | string,
  options: LookupWebFingerOptions = {},
): Promise<ResourceDescriptor | null> {
  if (typeof resource === "string") resource = new URL(resource);
  let protocol = "https:";
  let server: string;
  if (resource.protocol === "acct:") {
    const atPos = resource.pathname.lastIndexOf("@");
    if (atPos < 0) return null;
    server = resource.pathname.substring(atPos + 1);
    if (server === "") return null;
  } else {
    protocol = resource.protocol;
    server = resource.host;
  }
  let url = new URL(`${protocol}//${server}/.well-known/webfinger`);
  url.searchParams.set("resource", resource.href);
  while (true) {
    logger.debug(
      "Fetching WebFinger resource descriptor from {url}...",
      { url: url.href },
    );
    let response: Response;
    try {
      response = await fetch(url, {
        headers: {
          Accept:…
Record
GHSA-c59p-wq67-24wx
CVSS 3.1
5.4 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:L
Weakness
CWE-835, CWE-918
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
@fedify/fedify
npm
1.0.13 to 1.0.13
1.0.14
@fedify/fedify
npm
1.1.10 to 1.1.10
1.1.11
@fedify/fedify
npm
1.2.10 to 1.2.10
1.2.11
@fedify/fedify
npm
1.3.3 to 1.3.3
1.3.4
@fedify/fedify
npm
1.0.13 up to 1.0.14
1.0.14
@fedify/fedify
npm
1.1.10 up to 1.1.11
1.1.11
@fedify/fedify
npm
1.2.10 up to 1.2.11
1.2.11
@fedify/fedify
npm
1.3.3 up to 1.3.4
1.3.4

History

History
RecordedChangedSources
aliases, products, publishedEUVD
first indexedCVE List, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2025-23221 is a medium vulnerability (CVSS 5.4) in @fedify Fedify, published January 20, 2025. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.62%, the 49th percentile. Fixed in @fedify/fedify 1.0.14.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open