{"record":{"id":"07aa87a95037b7b1","repo":"jeecgboot/JeecgBoot","slug":"ai-ai","errorCode":null,"errorMessage":"删除AI知识库文档失败，不能删除其他租户的AI知识库文档！","messagePattern":"删除AI知识库文档失败，不能删除其他租户的AI知识库文档！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"critical","filePath":"jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/controller/AiragKnowledgeController.java","lineNumber":282,"sourceCode":"     * @param ids\n     * @return\n     * @author chenrui\n     * @date 2025/2/18 17:09\n     */\n    @Transactional(rollbackFor = Exception.class)\n    @DeleteMapping(value = \"/doc/deleteBatch\")\n    @RequiresPermissions(\"airag:knowledge:doc:deleteBatch\")\n    public Result<String> deleteDocumentBatch(HttpServletRequest request, @RequestParam(name = \"ids\", required = true) String ids) {\n        List<String> idsList = Arrays.asList(ids.split(\",\"));\n        //update-begin---author:chenrui ---date:20250606  for：[issues/8337]关于ai工作列表的数据权限问题 #8337------------\n        //如果是saas隔离的情况下，判断当前租户id是否是当前租户下的\n        if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {\n            List<AiragKnowledgeDoc> docList = airagKnowledgeDocService.listByIds(idsList);\n            //获取当前租户\n            String currentTenantId = TokenUtils.getTenantIdByRequest(request);\n            docList.forEach(airagKnowledgeDoc -> {\n                if (null == airagKnowledgeDoc || !airagKnowledgeDoc.getTenantId().equals(currentTenantId)) {\n                    throw new IllegalArgumentException(\"删除AI知识库文档失败，不能删除其他租户的AI知识库文档！\");\n                }\n            });\n        }\n        //update-end---author:chenrui ---date:20250606  for：[issues/8337]关于ai工作列表的数据权限问题 #8337------------\n        airagKnowledgeDocService.removeDocByIds(idsList);\n        return Result.OK(\"批量删除成功！\");\n    }\n\n    /**\n     * 清空知识库文档\n     *\n     * @param\n     * @return\n     */\n    @Transactional(rollbackFor = Exception.class)\n    @DeleteMapping(value = \"/doc/deleteAll\")\n    @RequiresPermissions(\"airag:knowledge:doc:deleteAll\")\n    public Result<?> deleteDocumentAll(HttpServletRequest request, @RequestParam(name = \"knowId\") String knowId) {","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/controller/AiragKnowledgeController.java#L264-L300","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// Not applicable — this is a security guard. The fix is on the client side:\n// before — sending IDs from mixed tenants\nfetch('/doc/deleteBatch?ids=id_tenant_a,id_tenant_b');\n\n// after — only send IDs belonging to the current tenant\nfetch('/doc/deleteBatch?ids=id_tenant_a,id_tenant_a2');","handlingStrategy":"validation","validationCode":"// Before calling deleteBatch, verify all IDs belong to the current tenant\nString currentTenantId = TokenUtils.getTenantIdByRequest(request);\nList<AiragKnowledgeDoc> docs = airagKnowledgeDocService.listByIds(idsList);\nboolean allOwned = docs.stream().allMatch(d -> d != null && currentTenantId.equals(d.getTenantId()));\nif (!allOwned) {\n    return Result.error(\"部分文档不属于当前租户，无法删除\");\n}","typeGuard":"public static boolean isOwnedByTenant(AiragKnowledgeDoc doc, String tenantId) {\n    return doc != null && doc.getTenantId() != null && doc.getTenantId().equals(tenantId);\n}","tryCatchPattern":"try {\n    airagKnowledgeController.deleteDocumentBatch(request, ids);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"不能删除其他租户\")) {\n        // Filter IDs to current tenant only and retry, or show user error\n        log.warn(\"Cross-tenant deletion attempt blocked. IDs: {}\", ids);\n        return Result.error(\"无法删除其他租户的文档\");\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["security","multi-tenant","data-isolation","airag","authorization"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}