{"schema_version":"1.7.3","id":"GHSA-8rf9-c59g-f82f","published":"2026-03-06T23:55:47Z","modified":"2026-03-23T04:56:18.889051174Z","aliases":["CVE-2026-30857","GO-2026-4640"],"summary":"WeKnora has Unauthorized Cross‑Tenant Knowledge Base Cloning","details":"### Summary\nA cross-tenant authorization bypass in the knowledge base copy endpoint allows any authenticated user to clone (duplicate) another tenant’s knowledge base into their own tenant by knowing/guessing the source knowledge base ID. This enables bulk data exfiltration (document/FAQ content) across tenants, making the impact critical.\n\n### Details\n\nThe `POST /api/v1/knowledge-bases/copy` endpoint enqueues an asynchronous KB clone task using the caller-supplied `source_id` without verifying ownership (see `internal/handler/knowledgebase.go`).\n```go\n// Create KB clone payload\npayload := types.KBClonePayload{\n  TenantID: tenantID.(uint64),\n  TaskID:   taskID,\n  SourceID: req.SourceID, // from attacker's input\n  TargetID: req.TargetID,\n}\n\npayloadBytes, err := json.Marshal(payload)\nif err != nil {\n  logger.Errorf(ctx, \"Failed to marshal KB clone payload: %v\", err)\n  c.Error(errors.NewInternalServerError(\"Failed to create task\"))\n  return\n}\n\n// Enqueue KB clone task to Asynq\ntask := asynq.NewTask(types.TypeKBClone, payloadBytes,\nasynq.TaskID(taskID), asynq.Queue(\"default\"), asynq.MaxRetry(3)) // enqueue task\ninfo, err := h.asynqClient.Enqueue(task)\nif err != nil {\n  logger.Errorf(ctx, \"Failed to enqueue KB clone task: %v\", err)\n  c.Error(errors.NewInternalServerError(\"Failed to enqueue task\"))\n  return\n}\n```\n\nThen, the asynq task handler (`ProcessKBClone`) invokes the `CopyKnowledgeBase` service method to perform the clone operation (see `internal/application/service/knowledge.go`):\n\n```go\n// Get source and target knowledge bases\nsrcKB, dstKB, err := s.kbService.CopyKnowledgeBase(ctx, payload.SourceID, payload.TargetID)\nif err != nil {\n    logger.Errorf(ctx, \"Failed to copy knowledge base: %v\", err)\n    handleError(progress, err, \"Failed to copy knowledge base configuration\")\n    return err\n}\n```\n\nAfter that, the `CopyKnowledgeBase` method calls the repository method to load the source knowledge base (see `internal/application/service/knowledgebase.go`):\n\n```go\nfunc (s *knowledgeBaseService) CopyKnowledgeBase(ctx context.Context,\n\tsrcKB string, dstKB string,\n) (*types.KnowledgeBase, *types.KnowledgeBase, error) {\n\tsourceKB, err := s.repo.GetKnowledgeBaseByID(ctx, srcKB)\n\tif err != nil {\n\t\tlogger.Errorf(ctx, \"Get source knowledge base failed: %v\", err)\n\t\treturn nil, nil, err\n\t}\n\tsourceKB.EnsureDefaults()\n\ttenantID := ctx.Value(types.TenantIDContextKey).(uint64)\n\tvar targetKB *types.KnowledgeBase\n\tif dstKB != \"\" {\n\t\ttargetKB, err = s.repo.GetKnowledgeBaseByID(ctx, dstKB)\n        // ...\n    }\n    // ...\n}\n```\n\n\n> Note: until now, the tenant ID is correctly set in context to the attacker’s tenant (from the payload), which can be used to prevent cross-tenant access.\n\nHowever, the repository method `GetKnowledgeBaseByID` loads knowledge bases by `id` only, allowing cross-tenant reads (see `internal/application/repository/knowledgebase.go`).\n\n```go\nfunc (r *knowledgeBaseRepository) GetKnowledgeBaseByID(ctx context.Context, id string) (*types.KnowledgeBase, error) {\n\tvar kb types.KnowledgeBase\n\tif err := r.db.WithContext(ctx).Where(\"id = ?\", id).First(&kb).Error; err != nil {\n\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\treturn nil, ErrKnowledgeBaseNotFound\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn &kb, nil\n}\n```\n\nThe data access layer fails to enforce tenant isolation because `GetKnowledgeBaseByID` only filters by ID and ignores the `tenant_id` present in the context. A secure implementation should enforce a tenant-scoped lookup (e.g., `WHERE id = ? AND tenant_id = ?`) or use a tenant-aware repository API to prevent cross-tenant access.\n\nService shallow-copies the KB configuration by calling `GetKnowledgeBaseByID(ctx, srcKB)` for the source KB, then creates a new KB under the attacker’s tenant while copying fields from the victim KB (`internal/application/service/knowledgebase.go`):\n\n```go\nsourceKB, err := s.repo.GetKnowledgeBaseByID(ctx, srcKB) // not tenant-scoped\n...\ntargetKB = &types.KnowledgeBase{\n    ID:                    uuid.New().String(),\n    Name:                  sourceKB.Name,\n    Type:                  sourceKB.Type,\n    Description:           sourceKB.Description,\n    TenantID:              tenantID,\n    ChunkingConfig:        sourceKB.ChunkingConfig,\n    ImageProcessingConfig: sourceKB.ImageProcessingConfig,\n    EmbeddingModelID:      sourceKB.EmbeddingModelID,\n    SummaryModelID:        sourceKB.SummaryModelID,\n    VLMConfig:             sourceKB.VLMConfig,\n    StorageConfig:         sourceKB.StorageConfig,\n    FAQConfig:             faqConfig,\n}\ntargetKB.EnsureDefaults()\n  if err := s.repo.CreateKnowledgeBase(ctx, targetKB); err != nil {\n      return nil, nil, err\n  }\n}\n```\n\n### PoC\n\nPrecondition: Attacker is authenticated in Tenant A and can obtain (or guess) a victim's knowledge base UUID belonging to Tenant B.\n\n1) Authenticate as Tenant A and obtain a bearer token or API key.\n\n2) Start a cross-tenant clone using the victim’s knowledge base ID as `source_id`:\n\n```bash\ncurl -X POST http://localhost:8088/api/v1/knowledge-bases/copy \\\n  -H \"Authorization: Bearer <ATTACKER_TOKEN>\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"source_id\":\"<VICTIM_KB_UUID>\",\"target_id\":\"\"}'\n```\n\n3) Observe that the task is accepted:\n- HTTP `200 OK`\n- Response contains a `task_id` and a message like `\"Knowledge base copy task started\"`.\n\n4) After the async task completes, a new knowledge base appears under Tenant A containing copied content/config from Tenant B.\n\n> Note: the copy can succeed even when models referenced by the source KB do not exist in the attacker tenant, indicating the workflow does not validate model ownership during copy.\n\nPoC Video:\n\nhttps://github.com/user-attachments/assets/8313fa44-5d5d-43f4-8ebd-f465c5a9d56e\n\n### Impact\n\nThis is a Broken Access Control (BOLA/IDOR) vulnerability enabling cross-tenant data exfiltration:\n\n- Any authenticated user can trigger a clone of a victim tenant’s knowledge base into their own tenant.\n- Results in bulk disclosure/duplication of knowledge base contents (documents/FAQ entries/chunks), plus associated configuration.","affected":[{"package":{"name":"github.com/Tencent/WeKnora","ecosystem":"Go","purl":"pkg:golang/github.com/Tencent/WeKnora"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.3.0"}]}],"database_specific":{"last_known_affected_version_range":"<= 0.2.14","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-8rf9-c59g-f82f/GHSA-8rf9-c59g-f82f.json"}}],"references":[{"type":"WEB","url":"https://github.com/Tencent/WeKnora/security/advisories/GHSA-8rf9-c59g-f82f"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-30857"},{"type":"PACKAGE","url":"https://github.com/Tencent/WeKnora"}],"database_specific":{"cwe_ids":["CWE-639"],"github_reviewed":true,"github_reviewed_at":"2026-03-06T23:55:47Z","nvd_published_at":"2026-03-07T17:15:53Z","severity":"MODERATE"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N"}]}