FlawOpen

flawopen.com/Incidents/microsoft-autogen-studio-autojack-agent-rce-june-2026

● Microsoft AutoGen project · CWE-1385 / CWE-306 / CWE-78 · 2026-06-18 · High
FlawOpen Security Research

AutoJack: AutoGen Studio RCE Research Chain

Microsoft’s AutoJack research found a local MCP process-launch chain in AutoGen Studio’s dev build. Fixed before release, it never shipped on PyPI.[1], [2]

💡 Plain English Explainer (ELI5)

Imagine a research lab where an assistant reads notes from visitors. The assistant is allowed into a control room because it works in the same building. A receptionist skips the normal identity check for that room, and a note from a visitor can choose which tool the room starts. AutoJack joined those shortcuts: a browsing agent rendered untrusted page content, that content reached AutoGen Studio's local MCP control plane, and caller-supplied server settings could lead to a child process. The lesson is to authenticate the control room, keep launch settings out of WebSocket URLs, and isolate the assistant from the developer's workstation. Microsoft says this was fixed on the upstream branch before a PyPI release. The report concerns pre-release source code, not an affected customer release.[1]

Core Concepts & Subsystem Terms

AutoJack
Microsoft's name for a demonstrated chain in an AutoGen Studio development build. The report says the affected MCP route was fixed before it appeared in a published PyPI package.[1], [2]
Loopback / localhost
A network interface that reaches services on the same host. It limits remote network access, but it does not isolate local processes such as an agent's browser from other services on that host.[1]
WebSocket Origin validation
A server-side check of the browser Origin during a WebSocket handshake. Microsoft found that this check alone did not stop the tested agent-rendered page from reaching the local MCP service.[1], [3]
StdioServerParams
AutoGen's MCP stdio configuration, including the program and arguments used to start a server process. The reported route decoded these settings from a WebSocket query parameter.[1], [2], [5]
Confused deputy
A privileged component is induced to use its own authority for an untrusted party. Here, the browsing agent and local Studio service formed the bridge from page content to host process creation.[1]

Framework mappings

Incident Timeline

Before 2026-06-18

Microsoft reported the behavior to MSRC; AutoGen maintainers hardened the upstream branch in commit b047730. The report says the affected MCP WebSocket route had not shipped in a PyPI release.[1], [2]

2026-06-18

Microsoft published the AutoJack research and its mitigation guidance. It describes a controlled local demonstration, not a confirmed attack against customers.[1]

Step-by-Step Attack Flow

Step 1

The agent is asked to browse untrusted content

A user prompt, retrieved page, or other untrusted input leads an AutoGen browsing agent to render an attacker-controlled page on the same workstation as AutoGen Studio.[1]

Step 2

Rendered page reaches the local MCP WebSocket

In Microsoft's tested setup, page script could connect to the Studio MCP endpoint on localhost. The Origin allowlist did not isolate code rendered by the local agent browser from that control plane.[1], [3]

Step 3

The MCP path bypasses the normal authentication middleware

The affected middleware skipped /api/mcp paths on the assumption that the WebSocket handler would authenticate them. Microsoft reports that this handler did not perform that check.[1], [4]

Step 4

The WebSocket query controls server parameters

The development route decoded server_params from the URL into StdioServerParams. Its command and arguments were then used by the MCP stdio transport to start a child process.[1], [2], [5]

Step 5

The chain reaches the host account

The local Studio process starts the configured child process with its own operating-system privileges. Microsoft demonstrated this in a controlled local proof of concept; it says the affected route was not in a released PyPI package.[1], [2]

Source Code: Flaw vs. Secure Implementation

The vulnerable example parses caller-controlled launch settings but deliberately starts no process. The upstream fix moved settings out of the WebSocket query into one-time server-side session state and restored authentication for MCP routes. This defensive reference pattern adds expiry, a size bound, locking, and an executable allowlist; replace its example name with the exact approved MCP binaries. It does not replace Origin validation or OS isolation.[1], [2]

UNPATCHED FLAW
import base64
import json
from dataclasses import dataclass


@dataclass(frozen=True)
class StdioServerParams:
    command: str
    args: tuple[str, ...]


def params_from_websocket_query(query: dict[str, str]) -> StdioServerParams:
    # The development route trusted configuration supplied in the URL.
    encoded = query["server_params"]
    decoded = base64.b64decode(encoded)
    return StdioServerParams(**json.loads(decoded))
HARDENED SECURE PATCH
from dataclasses import dataclass
from threading import Lock
from time import monotonic
from uuid import uuid4


@dataclass(frozen=True)
class StdioServerParams:
    command: str
    args: tuple[str, ...]


SESSION_TTL = 30.0
MAX_PENDING = 256
ALLOWED_COMMANDS = {"approved-mcp-server"}
pending: dict[str, tuple[float, StdioServerParams]] = {}
pending_lock = Lock()


def register_params(params: StdioServerParams, authenticated: bool) -> str:
    # authenticated must come from verified middleware, never request data.
    if not authenticated:
        raise PermissionError("MCP control-plane authentication required")
    if params.command not in ALLOWED_COMMANDS:
        raise ValueError("unapproved MCP server executable")
    now = monotonic()
    with pending_lock:
        for key, (expires, _) in list(pending.items()):
            if expires <= now:
                pending.pop(key, None)
        if len(pending) >= MAX_PENDING:
            raise RuntimeError("pending MCP session limit reached")
        session_id = str(uuid4())
        pending[session_id] = (now + SESSION_TTL, params)
        return session_id


def consume_params(session_id: str, authenticated: bool) -> StdioServerParams:
    if not authenticated:
        raise PermissionError("MCP control-plane authentication required")
    with pending_lock:
        entry = pending.pop(session_id, None)
    if entry is None or entry[0] <= monotonic():
        raise ValueError("unknown or expired MCP session")
    return entry[1]

Engineering & System Hardening Checklist

Sources