jeecgboot/JeecgBoot · critical · JeecgBootException

非法文件路径,禁止访问上传目录之外的文件:

Error message

非法文件路径,禁止访问上传目录之外的文件: 

What it means

This error is thrown by AIChatHandler.getFirstImageBase64() when the resolved canonical path of a local image file does not start with the upload directory's canonical path. This is a path traversal security guard (CWE-22, issues/9431) for the image-edit pipeline. The code resolves uploadpath + imageUrl to a canonical File, then checks if it's within the upload directory.

Source

Thrown at jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/llm/handler/AIChatHandler.java:628

                            java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream();
                            int nRead;
                            byte[] data = new byte[1024];
                            while ((nRead = in.read(data, 0, data.length)) != -1) {
                                buffer.write(data, 0, nRead);
                            }
                            buffer.flush();
                            fileContent = buffer.toByteArray();
                        }
                    } else {
                        //update-begin---author:liusq ---date:2026-03-30  for:【issues/9431】修复getFirstImageBase64路径遍历漏洞(CWE-22)-----------
                        // 本地文件
                        String filePath = uploadpath + File.separator + imageUrl;
                        SsrfFileTypeFilter.checkPathTraversal(filePath);
                        // 路径遍历校验:规范化后确保文件在uploadpath目录内
                        File uploadDir = new File(uploadpath).getCanonicalFile();
                        File targetFile = new File(filePath).getCanonicalFile();
                        if (!targetFile.toPath().startsWith(uploadDir.toPath())) {
                            throw new JeecgBootException("非法文件路径,禁止访问上传目录之外的文件: " + imageUrl);
                        }
                        fileContent = Files.readAllBytes(targetFile.toPath());
                        //update-end---author:liusq ---date:2026-03-30  for:【issues/9431】修复getFirstImageBase64路径遍历漏洞(CWE-22)-----------
                    }
                    originalImageBase64List.add(Base64.getEncoder().encodeToString(fileContent));
                } catch (Exception e) {
                    log.error("图片读取失败: {}", imageUrl, e);
                    throw new JeecgBootException("图片读取失败: " + imageUrl);
                }
            }
        }
        return originalImageBase64List;
    }
    //================================================= end 【QQYUN-12145】【AI】AI 绘画创作 ========================================

    /**
     * 将 LLM 调用异常统一翻译为友好的 JeecgBootException。
     * <p>

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. This is a security guard functioning correctly — investigate the source of the imageUrl input.
  2. Ensure client-side code only sends simple filenames (e.g. 'image_123.jpg'), not paths.
  3. Verify the uploadpath configuration is correct and images are actually stored there.
  4. Check for symlinks within the upload directory that could resolve outside it.
  5. If legitimate images are stored elsewhere, update uploadpath or move the images into the configured directory.

Example fix

// before (client) — sending traversal path
images: ['../../../data/secret.png']

// after — sending clean filename
images: ['user_upload_123.png']
Defensive patterns

Strategy: validation

Validate before calling

// Server-side: validate imageUrl is safe before path construction
public static boolean isSafeLocalImageRef(String imageUrl, String uploadpath) {
    if (imageUrl == null || imageUrl.isEmpty()) return false;
    if (imageUrl.contains("..")) return false;
    try {
        File uploadDir = new File(uploadpath).getCanonicalFile();
        File target = new File(uploadpath + File.separator + imageUrl).getCanonicalFile();
        return target.toPath().startsWith(uploadDir.toPath());
    } catch (IOException e) {
        return false;
    }
}

Type guard

public static boolean isWithinUploadDir(String uploadpath, String imageUrl) {
    try {
        File uploadDir = new File(uploadpath).getCanonicalFile();
        File target = new File(uploadpath + File.separator + imageUrl).getCanonicalFile();
        return target.toPath().startsWith(uploadDir.toPath());
    } catch (Exception e) {
        return false;
    }
}

Try / catch

// This is a security guard — do not suppress the exception.
// Log it as a security event.
try {
    List<String> base64List = getFirstImageBase64(images);
} catch (JeecgBootException e) {
    if (e.getMessage().contains("非法文件路径")) {
        log.warn("[SECURITY] Path traversal in image edit blocked: {}", imageUrl);
        auditLogService.recordSecurityEvent("IMG_PATH_TRAVERSAL", imageUrl);
    }
    throw e;
}

Prevention

When it happens

Trigger: An image-edit request provides a local imageUrl (not matching WEB_PATTERN for URLs) that resolves outside the upload directory after canonicalization. For example: imageUrl='../../../etc/passwd' resolves to /etc/passwd which does not start with the upload directory. This triggers after SsrfFileTypeFilter.checkPathTraversal but the canonical path check is a second layer of defense.

Common situations: Malicious input attempting to access files outside the upload directory via ../ sequences or symlinks. A symlink within the upload directory points outside, causing canonical resolution to escape the root. Legitimate but incorrectly configured uploadpath where image files are stored in a sibling directory.

Related errors


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