GHSA-r5pq-6chh-j3xp: CSRF

Published Aug 21, 2026
·
Updated

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 []."

2. A single anonymous body nested 5000 levels deep (10013 bytes) crashes the process.

curl -s -o /dev/null -w '%{httpcode}\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)

3. The server is gone and does not recover on its own.

curl -s -o /dev/null -w '%{httpcode}\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)

Affected Software

3 affected componentsFixes available
npm/unleash-server>=8.0.0<8.0.2
8.0.2
npm/unleash-server>=7.6.0<7.6.5
7.6.5
npm/unleash-server<7.5.2
7.5.2

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/unleash-server to a version that resolves this vulnerability.

    Fixed in 8.0.2
  2. Upgrade

    Upgrade npm/unleash-server to a version that resolves this vulnerability.

    Fixed in 7.6.5
  3. Upgrade

    Upgrade npm/unleash-server to a version that resolves this vulnerability.

    Fixed in 7.5.2

Event History

Aug 21, 2026
Advisory Published
via GitHub·07:14 PM
Data Sourced
via GitHub·07:14 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Do deployments need an authenticated user or a specific API route to be exposed?

No. The affected request can be sent without an account, token, or cookie, and the vulnerable error formatter is used by every OpenAPI-validated API route. Anonymous POST /edge/validate and POST /edge/issue-token are specifically identified as reachable triggers.

2

Will the configured request-size limit prevent exploitation?

No. A deeply nested JSON payload of about 10 KB at depth 5000 is sufficient, which remains below the stated 100 KB body-size limit. The body parser has no JSON nesting-depth limit.

3

What is the operational impact if the request succeeds?

The Unleash process terminates with exit(1) after a synchronous RangeError is thrown in Express error handling. An attacker can keep the service offline by replaying the same request.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203