Skip to content

MCP stdio child stderr inherited — unfiltered output to daemon stderr, fd-fill stall risk #3805

Description

@houko

Severity

P2 — full stderr pipe (journald, container logs with slow consumer) can deadlock the MCP request loop.

Files

  • crates/librefang-runtime-mcp/src/lib.rs:1010-1036 (connect_stdio)

Finding

The Command configured for the MCP child never sets
.stderr(Stdio::piped()) or .stderr(Stdio::null()), so it inherits the
daemon's stderr. Two consequences:

  1. Failure debugging: when a server crashes during handshake, its stack
    trace is dumped raw to the daemon's stderr without any server="<name>"
    context, mixed with structured logs — operators can't tell which MCP
    server died.
  2. Fd-fill stall: if the daemon's stderr is a fully-buffered pipe
    (journald, container docker logs with a slow consumer, CI capture), an
    MCP server spamming stderr can fill the pipe; once full and the consumer
    is slow, the child blocks on write(stderr), which deadlocks the MCP
    request loop. There's no rate limit, no truncation, no per-server log
    routing.

Evidence

tokio::process::Command::new(&resolved_command).configure(|cmd| {
    cmd.args(&args_owned);
    cmd.env_clear();
    // ... no .stderr(Stdio::piped()) or .stderr(Stdio::null())
})

Suggested Fix

Set cmd.stderr(std::process::Stdio::piped()) and spawn a tokio task that
forwards lines to tracing::warn!(server=%name, line=%l) with a per-server
line/byte cap; alternatively Stdio::null() if surfacing isn't required.
Always pair with kill_on_drop(true) from #66.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/runtimeAgent loop, LLM drivers, WASM sandboxbugSomething isn't workingseverity/mediumBug or limitation with workaround / partial impact

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions