high · 8.8CVE-2026-59160Sep 10, 2026

CVE-2026-59160: @yeger/turbo-graph Unauthenticated Remote Task Execution

Rohit Hatagale
AI Security Researcher, SecureLayer7

A developer tool for visualizing Turborepo task graphs accidentally exposes an unauthenticated HTTP endpoint to the local network, letting any nearby attacker silently run arbitrary build tasks with…

Package@yeger/turbo-graph
Ecosystemnpm
Affected<= 2.8.8
Fixed in2.8.12
CVE-2026-59160: @yeger/turbo-graph Unauthenticated Remote Task Execution

The problem

Two independent bugs combine into the vulnerability. First, packages/turbo-graph/src/index.ts calls .listen(options.port, callback) without passing a hostname, so Node.js defaults to binding on all interfaces (0.0.0.0:29312) instead of loopback only.

Second, the /api/run route in packages/turbo-graph-ui/app/api/run/route.ts has zero authentication. It reads tasks, filter, and force straight from the query string and passes them to spawn(turboBin, ['run', ...tasks], { cwd: dir }). Any host on the same network segment can trigger any task in the victim's turbo.json, including tasks that write files, call cloud CLIs, or run deployment scripts.

Proof of concept

A working proof-of-concept for CVE-2026-59160 in @yeger/turbo-graph, with the exact payload below.

bash
# From any host on the same LAN as a developer running turbo-graph:
curl -N "http://<victim-ip>:29312/api/run?tasks=pwn&force=true"

# The server responds HTTP 200 with a text/event-stream body.
# An SSE 'start' event confirms execution:
#   data: {"args":["run","pwn","--ui=stream","--force"]}
# No token, cookie, or credential of any kind is required.

The root cause is CWE-306: missing authentication for a critical function. The hostname variable declared at index.ts:19 is only ever used to build the console log URL string. It is never passed to .listen(), so the server is network-exposed by accident, not by design.

Because spawn() is called with an argument array rather than a shell string, classic shell injection characters do not apply. The exploit does not need them: any task name already in turbo.json can be selected directly via the ?tasks= parameter with no escaping or bypass required.

The 2.8.12 patch passes the hostname argument to .listen() so the server binds to 127.0.0.1 only, and adds an authentication check to the /api/run handler so the endpoint is no longer reachable or usable without authorization.

The fix

Upgrade to @yeger/turbo-graph version **2.8.12** or later. The patch binds the embedded Next.js server to 127.0.0.1 (loopback) instead of all interfaces, and adds authentication to the /api/run endpoint. No configuration change can mitigate affected versions; a firewall rule blocking port 29312 from the LAN is a partial workaround only.

Reporter not attributed.

References: [1][2]

Related research