jeecgboot/JeecgBoot · critical · IllegalArgumentException

删除AI知识库文档失败,不能删除其他租户的AI知识库文档!

Error message

删除AI知识库文档失败,不能删除其他租户的AI知识库文档!

What it means

This error is thrown during batch deletion of AI knowledge base documents when SaaS multi-tenant isolation is enabled and any document in the deletion batch belongs to a different tenant than the current request's tenant. The code iterates over each document, checks if its tenantId matches the currentTenantId from the request, and throws if there's a mismatch. This is a tenant isolation security guard for issue #8337.

Source

Thrown at jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/controller/AiragKnowledgeController.java:282

     * @param ids
     * @return
     * @author chenrui
     * @date 2025/2/18 17:09
     */
    @Transactional(rollbackFor = Exception.class)
    @DeleteMapping(value = "/doc/deleteBatch")
    @RequiresPermissions("airag:knowledge:doc:deleteBatch")
    public Result<String> deleteDocumentBatch(HttpServletRequest request, @RequestParam(name = "ids", required = true) String ids) {
        List<String> idsList = Arrays.asList(ids.split(","));
        //update-begin---author:chenrui ---date:20250606  for:[issues/8337]关于ai工作列表的数据权限问题 #8337------------
        //如果是saas隔离的情况下,判断当前租户id是否是当前租户下的
        if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {
            List<AiragKnowledgeDoc> docList = airagKnowledgeDocService.listByIds(idsList);
            //获取当前租户
            String currentTenantId = TokenUtils.getTenantIdByRequest(request);
            docList.forEach(airagKnowledgeDoc -> {
                if (null == airagKnowledgeDoc || !airagKnowledgeDoc.getTenantId().equals(currentTenantId)) {
                    throw new IllegalArgumentException("删除AI知识库文档失败,不能删除其他租户的AI知识库文档!");
                }
            });
        }
        //update-end---author:chenrui ---date:20250606  for:[issues/8337]关于ai工作列表的数据权限问题 #8337------------
        airagKnowledgeDocService.removeDocByIds(idsList);
        return Result.OK("批量删除成功!");
    }

    /**
     * 清空知识库文档
     *
     * @param
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @DeleteMapping(value = "/doc/deleteAll")
    @RequiresPermissions("airag:knowledge:doc:deleteAll")
    public Result<?> deleteDocumentAll(HttpServletRequest request, @RequestParam(name = "knowId") String knowId) {

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Verify that all document IDs in the batch belong to the current tenant before submitting the delete request.
  2. Ensure the frontend filters the document list correctly when switching tenants.
  3. If this is a legitimate cross-tenant operation (admin), ensure the admin has the appropriate tenant context set in the request.
  4. Audit the client-side code that builds the ids parameter to prevent ID injection from other tenants.

Example fix

// Not applicable — this is a security guard. The fix is on the client side:
// before — sending IDs from mixed tenants
fetch('/doc/deleteBatch?ids=id_tenant_a,id_tenant_b');

// after — only send IDs belonging to the current tenant
fetch('/doc/deleteBatch?ids=id_tenant_a,id_tenant_a2');
Defensive patterns

Strategy: validation

Validate before calling

// Before calling deleteBatch, verify all IDs belong to the current tenant
String currentTenantId = TokenUtils.getTenantIdByRequest(request);
List<AiragKnowledgeDoc> docs = airagKnowledgeDocService.listByIds(idsList);
boolean allOwned = docs.stream().allMatch(d -> d != null && currentTenantId.equals(d.getTenantId()));
if (!allOwned) {
    return Result.error("部分文档不属于当前租户,无法删除");
}

Type guard

public static boolean isOwnedByTenant(AiragKnowledgeDoc doc, String tenantId) {
    return doc != null && doc.getTenantId() != null && doc.getTenantId().equals(tenantId);
}

Try / catch

try {
    airagKnowledgeController.deleteDocumentBatch(request, ids);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("不能删除其他租户")) {
        // Filter IDs to current tenant only and retry, or show user error
        log.warn("Cross-tenant deletion attempt blocked. IDs: {}", ids);
        return Result.error("无法删除其他租户的文档");
    }
    throw e;
}

Prevention

When it happens

Trigger: A DELETE /doc/deleteBatch request with a comma-separated list of document IDs where at least one ID belongs to a different tenant. This happens when MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL is true (SaaS mode). The check at line 281 compares each doc's tenantId against TokenUtils.getTenantIdByRequest(request).

Common situations: A client manipulates the ids parameter to include document IDs from other tenants (cross-tenant data access attempt). A bug in the frontend sends stale IDs from a cached list that includes documents from a previous tenant context. Tenant context switching without refreshing the document list.

Related errors


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/07aa87a95037b7b1. Report an issue: GitHub.