# @yeger/turbo-graph: Unauthenticated Network-Exposed Task Execution via /api/run

turbo-graph binds its Next.js server to 0.0.0.0 and exposes unauthenticated /api/run, letting LAN attackers execute arbitrary Turborepo tasks as the developer.

- Payout: Research
- Severity: high
- Program: @yeger/turbo-graph
- Found by: EQSTLab, min8282
- Disclosed: 2026-09-09
- CVE: CVE-2026-59160
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-2r5q-h53f-9rp3
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-2r5q-h53f-9rp3
- Tags: area:web domain:oss domain:web impact:integrity impact:rce kind:research platform:ghsa sev:high tech:nextjs tech:nodejs technique:source-review vuln:auth-bypass vuln:misconfiguration vuln:rce

**Target:** Next.js dev server /api/run endpoint spawned via turbo CLI
**Root cause:** listen() is called without a hostname so Node binds 0.0.0.0 instead of the declared 'localhost'; the /api/run GET handler passes attacker-supplied task names straight into spawn() with no auth, CSRF, or allowlist.
**Steps:**
1. Victim runs `npx turbo-graph --port 29312` in a repo with a side-effecting task in turbo.json.
2. Confirm binding: `ss -tlnp 'sport = :29312'` shows `*:29312` (0.0.0.0), not 127.0.0.1.
3. From any LAN host send `curl -N "http://<victim-ip>:29312/api/run?tasks=pwn&force=true"`.
4. Server returns HTTP 200 text/event-stream with SSE start event `args: ["run","pwn","--ui=stream","--force"]`.
5. Task executes in victim cwd; proof file `/tmp/turbo-graph-poc` written with `owned`.
**How found:** Source review: noticed `const hostname = 'localhost'` declared but never passed to `.listen(options.port)`, then traced the /api/run GET handler and found query params flow directly into the spawn() argument array with no auth check.
**Exploitation:** No credentials or interaction needed: attacker scans the static default port 29312 on the local segment, issues a plain GET with ?tasks=<any task in turbo.json>&force=true, and the server spawns `turbo run <task>` in the developer's project directory with the developer's OS privileges, yielding code execution, file writes, or deployment actions.
**Insight:** When a dev tool declares a hostname constant for its console URL, verify it is actually passed to listen() — Node defaults to 0.0.0.0. Also check dev-server API routes that wrap CLI spawn() for missing auth, since argument-array spawn still allows arbitrary task selection.

