GitHub Security Advisory: GHSA-8rf9-c59g-f82f
Package: github.com/Tencent/WeKnora (GO)
Severity: MODERATE (CVSS 5.9)
CWE: CWE-639: Authorization Bypass Through User-Controlled Key
Vulnerable versions: <= 0.2.14
Patched in: 0.3.0

Summary: WeKnora has Unauthorized Cross‑Tenant Knowledge Base Cloning

Details:
### Summary
A 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.

### Details

The `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`).
```go
// Create KB clone payload
payload := types.KBClonePayload{
  TenantID: tenantID.(uint64),
  TaskID:   taskID,
  SourceID: req.SourceID, // from attacker's input
  TargetID: req.TargetID,
}

payloadBytes, err := json.Marshal(payload)
if err != nil {
  logger.Errorf(ctx, "Failed to marshal KB clone payload: %v", err)
  c.Error(errors.NewInternalServerError("Failed to create task"))
  return
}

// Enqueue KB clone task to Asynq
task := asynq.NewTask(types.TypeKBClone, payloadBytes,
asynq.TaskID(taskID), asynq.Queue("default"), asynq.MaxRetry(3)) // enqueue task
info, err := h.asynqClient.Enqueue(task)
if err != nil {
  logger.Errorf(ctx, "Failed to enqueue KB clone task: %v", err)
  c.Error(errors.NewInternalServerError("Failed to enqueue task"))
  return
}
```

Then, the asynq task handler (`ProcessKBClone`) invokes the `CopyKnowledgeBase` service method to perform the clone operation (see `internal/application/service/knowledge.go`):

```go
// Get source and target knowledge bases
srcKB, dstKB, err := s.kbService.CopyKnowledgeBase(ctx, payload.SourceID, payload.TargetID)
if err != nil {
    logger.Errorf(ctx, "Failed to copy knowledge base: %v", err)
    handleError(progress, err, "Failed to copy knowledge base configuration")
    return err
}
```

After that, the `CopyKnowledgeBase` method calls the repository method to load the source knowledge base (see `internal/application/service/knowledgebase.go`):

```go
func (s *knowledgeBaseService) CopyKnowledgeBase(ctx context.Context,
	srcKB string, dstKB string,
) (*types.KnowledgeBase, *types.KnowledgeBase, error) {
	sourceKB, err := s.repo.GetKnowledgeBaseByID(ctx, srcKB)
	if err != nil {
		logger.Errorf(ctx, "Get source knowledge base failed: %v", err)
		return nil, nil, err
	}
	sourceKB.EnsureDefaults()
	tenantID := ctx.Value(types.TenantIDContextKey).(uint64)
	var targetKB *types.KnowledgeBase
	if dstKB != "" {
		targetKB, err = s.repo.GetKnowledgeBaseByID(ctx, dstKB)
        // ...
    }
    // ...
}
```


> 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.

However, the repository method `GetKnowledgeBaseByID` loads knowledge bases by `id` only, allowing cross-tenant reads (see `internal/application/repository/knowledgebase.go`).

```go
func (r *knowledgeBaseRepository) GetKnowledgeBaseByID(ctx context.Context, id string) (*types.KnowledgeBas