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
- Verify that all document IDs in the batch belong to the current tenant before submitting the delete request.
- Ensure the frontend filters the document list correctly when switching tenants.
- If this is a legitimate cross-tenant operation (admin), ensure the admin has the appropriate tenant context set in the request.
- 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
- Implement client-side filtering to only show and select documents from the current tenant
- Add tenant-scoped queries so documents from other tenants never appear in the UI
- Log cross-tenant access attempts for security auditing
- Use MybatisPlus tenant interceptor to automatically scope all queries
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.