VYPR
High severityNVD Advisory· Published Oct 2, 2025· Updated Nov 4, 2025

Apache Kylin: improper restriction of file read

CVE-2025-61734

Description

Files or Directories Accessible to External Parties vulnerability in Apache Kylin. You are fine as long as the Kylin's system and project admin access is well protected.

This issue affects Apache Kylin: from 4.0.0 through 5.0.2.

Users are recommended to upgrade to version 5.0.3, which fixes the issue.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CVE-2025-61734 is a vulnerability in Apache Kylin versions 4.0.0 through 5.0.2 that allows files or directories to be accessed by external parties if system and project admin access is not properly protected.

Description

CVE-2025-61734 is a vulnerability in Apache Kylin, an open-source OLAP engine for Big Data [1]. The issue allows files or directories to be accessible to external parties, which could lead to unauthorized information disclosure. The official description notes that the risk exists only if system and project admin access are not well protected [2].

Exploitation

The vulnerability is present in Apache Kylin versions from 4.0.0 through 5.0.2. Exploitation requires that an attacker gains or already has system or project admin access, as the vulnerability relates to file and directory accessibility from externally accessible parties [2]. The fix, included in version 5.0.3, involves adding host checks and fixing API endpoints as seen in the related pull request and commit [3][4].

Impact

If exploited, an attacker with admin access could access files or directories that should not be exposed to external parties. This could lead to unauthorized reading of sensitive data, configuration files, or other resources, potentially compromising the confidentiality of the Kylin deployment [2].

Mitigation

Apache Software Foundation recommends upgrading to Apache Kylin version 5.0.3, which fixes this vulnerability [2]. Users should ensure that system and project admin access is properly controlled, as the advisory states that users are fine as long as those accesses are well protected. No other workarounds are mentioned in the available references.

AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.kylin:kylinMaven
>= 4.0.0, < 5.0.35.0.3
org.apache.kylin:kylin-common-serverMaven
>= 4.0.0, < 5.0.35.0.3
org.apache.kylin:kylin-common-serviceMaven
>= 4.0.0, < 5.0.35.0.3
org.apache.kylin:kylin-core-commonMaven
>= 4.0.0, < 5.0.35.0.3
org.apache.kylin:kylin-core-metadataMaven
>= 4.0.0, < 5.0.35.0.3
org.apache.kylin:kylin-ops-serverMaven
>= 4.0.0, < 5.0.35.0.3
org.apache.kylin:kylin-serverMaven
>= 4.0.0, < 5.0.35.0.3

Affected products

2
  • Apache/Kylinllm-fuzzy
    Range: >=4.0.0, <=5.0.2
  • Apache Software Foundation/Apache Kylinv5
    Range: 4.0.0

Patches

1
22eb8fd5dfde

[MINOR] fix API And add host check

https://github.com/apache/kylinjlfAug 27, 2025via ghsa
19 files changed · +662 34
  • src/common-server/pom.xml+6 0 modified
    @@ -38,6 +38,12 @@
                 <groupId>org.apache.kylin</groupId>
                 <artifactId>kylin-core-common</artifactId>
             </dependency>
    +        <dependency>
    +            <groupId>org.apache.kylin</groupId>
    +            <artifactId>kylin-core-metadata</artifactId>
    +            <type>test-jar</type>
    +            <scope>test</scope>
    +        </dependency>
             <dependency>
                 <groupId>org.apache.kylin</groupId>
                 <artifactId>kylin-core-metadata</artifactId>
    
  • src/common-server/src/main/java/org/apache/kylin/rest/controller/NBasicController.java+26 0 modified
    @@ -79,6 +79,7 @@
     import org.apache.kylin.common.KylinConfig;
     import org.apache.kylin.common.KylinConfigBase;
     import org.apache.kylin.common.exception.KylinException;
    +import org.apache.kylin.common.exception.KylinRuntimeException;
     import org.apache.kylin.common.exception.ServerErrorCode;
     import org.apache.kylin.common.msg.Message;
     import org.apache.kylin.common.msg.MsgPicker;
    @@ -96,7 +97,9 @@
     import org.apache.kylin.metadata.model.TableDesc;
     import org.apache.kylin.metadata.project.NProjectManager;
     import org.apache.kylin.metadata.project.ProjectInstance;
    +import org.apache.kylin.metadata.resourcegroup.ResourceGroupManager;
     import org.apache.kylin.metadata.streaming.KafkaConfigManager;
    +import org.apache.kylin.rest.cluster.ClusterManager;
     import org.apache.kylin.rest.constant.Constant;
     import org.apache.kylin.rest.exception.ForbiddenException;
     import org.apache.kylin.rest.exception.NotFoundException;
    @@ -160,6 +163,9 @@ public class NBasicController {
         @Autowired
         protected UserService userService;
     
    +    @Autowired
    +    protected ClusterManager clusterManager;
    +
         protected Logger getLogger() {
             return logger;
         }
    @@ -701,4 +707,24 @@ private void initBinder(WebDataBinder binder) {
             int autoGrowCollectionLimit = KylinConfig.getInstanceFromEnv().getDataBinderAutoGrowCollectionLimit();
             binder.setAutoGrowCollectionLimit(autoGrowCollectionLimit);
         }
    +
    +    public void checkServer(String host) {
    +        if (StringUtils.isBlank(host)) {
    +            throw new KylinRuntimeException("Server cannot be null or empty");
    +        }
    +
    +        val rgManager = ResourceGroupManager.getInstance(KylinConfig.getInstanceFromEnv());
    +        val serverNotFoundInCluster = clusterManager.checkServer(host);
    +
    +        if (rgManager.isResourceGroupEnabled()) {
    +            val serverNotFoundInResourceGroup = rgManager.checkServer(host);
    +            if (serverNotFoundInCluster && serverNotFoundInResourceGroup) {
    +                throw new KylinRuntimeException(String.format(Locale.ROOT, "Server <%s> not found", host));
    +            }
    +        } else {
    +            if (serverNotFoundInCluster) {
    +                throw new KylinRuntimeException(String.format(Locale.ROOT, "Server <%s> not found", host));
    +            }
    +        }
    +    }
     }
    
  • src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java+1 0 modified
    @@ -190,6 +190,7 @@ public EnvelopeResponse<String> simulateInsertMeta(
         public EnvelopeResponse<String> broadcastMetadataBackup(@RequestBody MetadataBackupRequest request) {
             log.info("ResourceGroup[{}] broadcastMetadataBackup tmpFilePath : {}", request.getResourceGroupId(),
                     request.getTmpFilePath());
    +        checkServer(request.getFromHost());
             fileService.saveBroadcastMetadataBackup(request.getBackupDir(), request.getTmpFilePath(),
                     request.getTmpFileSize(), request.getResourceGroupId(), request.getFromHost());
             return new EnvelopeResponse<>(CODE_SUCCESS, "", "");
    
  • src/common-server/src/test/java/org/apache/kylin/rest/controller/NBasicControllerTest.java+71 0 modified
    @@ -40,12 +40,17 @@
     
     import org.apache.commons.lang3.StringUtils;
     import org.apache.kylin.common.exception.KylinException;
    +import org.apache.kylin.common.exception.KylinRuntimeException;
     import org.apache.kylin.common.extension.KylinInfoExtension;
     import org.apache.kylin.common.msg.MsgPicker;
    +import org.apache.kylin.common.util.AddressUtil;
     import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
     import org.apache.kylin.common.util.Pair;
     import org.apache.kylin.metadata.model.PartitionDesc;
     import org.apache.kylin.metadata.model.TableDesc;
    +import org.apache.kylin.metadata.resourcegroup.ResourceGroupManagerTest;
    +import org.apache.kylin.rest.cluster.ClusterManager;
    +import org.apache.kylin.rest.cluster.MockClusterManager;
     import org.apache.kylin.rest.controller.fixture.FixtureController;
     import org.apache.kylin.rest.exception.ForbiddenException;
     import org.apache.kylin.rest.exception.NotFoundException;
    @@ -56,6 +61,7 @@
     import org.junit.Before;
     import org.junit.Rule;
     import org.junit.Test;
    +import org.junit.jupiter.api.Assertions;
     import org.junit.rules.ExpectedException;
     import org.junit.runner.RunWith;
     import org.mockito.InjectMocks;
    @@ -64,6 +70,7 @@
     import org.powermock.core.classloader.annotations.PrepareForTest;
     import org.powermock.modules.junit4.PowerMockRunner;
     import org.springframework.security.access.AccessDeniedException;
    +import org.springframework.test.util.ReflectionTestUtils;
     import org.springframework.test.web.servlet.MockMvc;
     import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
     import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
    @@ -333,4 +340,68 @@ public void testEncodeAndDecodeHost() {
             Assert.assertEquals("ip", nBasicController.decodeHost("ip"));
         }
     
    +    @Test
    +    public void testCheckServerWithResourceGroupEnable() {
    +        ClusterManager clusterManager = new MockClusterManager();
    +
    +        String project = "default";
    +        ResourceGroupManagerTest.mockResourceGroup(AddressUtil.getLocalInstance(), project);
    +        ReflectionTestUtils.setField(nBasicController, "clusterManager", clusterManager);
    +
    +        // Test valid server - should not throw exception
    +        Assertions.assertDoesNotThrow(() -> nBasicController.checkServer(AddressUtil.getLocalInstance()));
    +        Assertions.assertDoesNotThrow(() -> nBasicController.checkServer("127.0.0.1:7070"));
    +
    +        // Test null host - should throw KylinRuntimeException
    +        KylinRuntimeException nullException = Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> nBasicController.checkServer(null));
    +        Assertions.assertEquals("Server cannot be null or empty", nullException.getMessage());
    +
    +        // Test empty host - should throw KylinRuntimeException
    +        KylinRuntimeException emptyException = Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> nBasicController.checkServer(""));
    +        Assertions.assertEquals("Server cannot be null or empty", emptyException.getMessage());
    +        KylinRuntimeException emptyException2 = Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> nBasicController.checkServer(" "));
    +        Assertions.assertEquals("Server cannot be null or empty", emptyException2.getMessage());
    +
    +        // Test host not found in servers - should throw KylinRuntimeException
    +        KylinRuntimeException notFoundException = Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> nBasicController.checkServer("192.168.1.1:8080"));
    +        Assertions.assertEquals("Server <192.168.1.1:8080> not found", notFoundException.getMessage());
    +    }
    +
    +    @Test
    +    public void testCheckServerWithoutResourceGroupEnable() {
    +        ClusterManager clusterManager = new MockClusterManager();
    +
    +        ReflectionTestUtils.setField(nBasicController, "clusterManager", clusterManager);
    +
    +        // Test valid server - should not throw exception
    +        Assertions.assertDoesNotThrow(() -> nBasicController.checkServer("127.0.0.1:7070"));
    +
    +        // Test null host - should throw KylinRuntimeException
    +        KylinRuntimeException nullException = Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> nBasicController.checkServer(null));
    +        Assertions.assertEquals("Server cannot be null or empty", nullException.getMessage());
    +
    +        // Test empty host - should throw KylinRuntimeException
    +        KylinRuntimeException emptyException = Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> nBasicController.checkServer(""));
    +        Assertions.assertEquals("Server cannot be null or empty", emptyException.getMessage());
    +        KylinRuntimeException emptyException2 = Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> nBasicController.checkServer(" "));
    +        Assertions.assertEquals("Server cannot be null or empty", emptyException2.getMessage());
    +
    +        // Test host not found in servers - should throw KylinRuntimeException
    +        KylinRuntimeException notFoundException = Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> nBasicController.checkServer("192.168.1.1:8080"));
    +        Assertions.assertEquals("Server <192.168.1.1:8080> not found", notFoundException.getMessage());
    +
    +        // Test current host not found in servers - should throw KylinRuntimeException
    +        KylinRuntimeException notFoundException2 = Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> nBasicController.checkServer(AddressUtil.getLocalInstance()));
    +        Assertions.assertEquals("Server <" + AddressUtil.getLocalInstance() + "> not found",
    +                notFoundException2.getMessage());
    +    }
     }
    
  • src/common-service/src/main/java/org/apache/kylin/rest/service/FileService.java+24 3 modified
    @@ -20,6 +20,7 @@
     
     import static org.apache.kylin.common.constant.Constants.BACKSLASH;
     import static org.apache.kylin.common.constant.Constants.METADATA_FILE;
    +import static org.apache.kylin.common.constant.Constants.SYSTEM_TMP_DIR;
     import static org.apache.kylin.common.constant.HttpConstant.HTTP_VND_APACHE_KYLIN_V4_PUBLIC_JSON;
     
     import java.io.File;
    @@ -28,6 +29,7 @@
     import java.io.IOException;
     import java.io.InputStream;
     import java.nio.file.Files;
    +import java.nio.file.Paths;
     import java.util.Arrays;
     import java.util.Locale;
     
    @@ -60,12 +62,31 @@
     @Slf4j
     @Service
     public class FileService extends BasicService {
    +    protected static final String METADATA_TMP_PREFIX = "KylinMetadataBackupTmp-";
    +
         @Autowired
         @Qualifier("normalRestTemplate")
         private RestTemplate restTemplate;
     
    +    public static String getSafeAbsolutePath(String filePath) {
    +        val basePath = Paths.get(SYSTEM_TMP_DIR).toAbsolutePath().normalize();
    +        val targetPath = Paths.get(filePath).toAbsolutePath().normalize();
    +
    +        if (!targetPath.startsWith(basePath)) {
    +            throw new SecurityException("Path outside base directory: " + filePath);
    +        }
    +
    +        val metadataTmpPrefix = Paths.get(basePath.toString(), METADATA_TMP_PREFIX).toAbsolutePath().normalize();
    +        if (!StringUtils.startsWith(targetPath.toString(), metadataTmpPrefix.toString())) {
    +            throw new SecurityException("Path not kylin metadata tmp directory: " + filePath);
    +        }
    +
    +        return targetPath.toString();
    +    }
    +
         public InputStream getMetadataBackupFromTmpPath(String tmpFilePath, Long fileSize) throws IOException {
    -        val metadataBackupTmp = new File(tmpFilePath);
    +        val realTempPath = getSafeAbsolutePath(tmpFilePath);
    +        val metadataBackupTmp = new File(realTempPath);
             if (metadataBackupTmp.isFile()) {
                 if (metadataBackupTmp.length() != fileSize) {
                     throw new FileNotFoundException("Metadata backup temp file length does not right: " + tmpFilePath
    @@ -84,7 +105,7 @@ public Pair<String, Long> saveMetadataBackupInTmpPath(String path) throws IOExce
             val filePath = new Path(path);
             if (fileSystem.isFile(filePath)) {
                 val fileStatus = fileSystem.getFileStatus(filePath);
    -            val tempDirectory = Files.createTempDirectory("MetadataBackupTmp-").toFile();
    +            val tempDirectory = Files.createTempDirectory(Paths.get(SYSTEM_TMP_DIR), METADATA_TMP_PREFIX).toFile();
                 fileSystem.copyToLocalFile(false, filePath, new Path(tempDirectory.getAbsolutePath()), true);
                 val tmpFile = new File(tempDirectory, METADATA_FILE);
                 if (fileStatus.getLen() != tmpFile.length()) {
    @@ -99,7 +120,7 @@ public Pair<String, Long> saveMetadataBackupInTmpPath(String path) throws IOExce
         }
     
         public String saveMetadataBackupTmpFromRequest(Long fileSize, InputStream inputStream) throws IOException {
    -        val tmpDirectory = Files.createTempDirectory("MetadataBackupTmp-").toFile();
    +        val tmpDirectory = Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
             val tmpFile = new File(tmpDirectory, METADATA_FILE);
             try (val os = new FileOutputStream(tmpFile)) {
                 IOUtils.copy(inputStream, os);
    
  • src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java+21 2 modified
    @@ -26,13 +26,15 @@
     import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_SOURCE_ENABLE_KEY;
     import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_SOURCE_NAME_KEY;
     import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_USER_KEY;
    +import static org.apache.kylin.common.constant.Constants.MAX_FILENAME_LENGTH;
     import static org.apache.kylin.common.constant.NonCustomProjectLevelConfig.DATASOURCE_TYPE;
     import static org.apache.kylin.common.exception.ServerErrorCode.DATABASE_NOT_EXIST;
     import static org.apache.kylin.common.exception.ServerErrorCode.DUPLICATE_PROJECT_NAME;
     import static org.apache.kylin.common.exception.ServerErrorCode.EMPTY_EMAIL;
     import static org.apache.kylin.common.exception.ServerErrorCode.EMPTY_PARAMETER;
     import static org.apache.kylin.common.exception.ServerErrorCode.FILE_TYPE_MISMATCH;
     import static org.apache.kylin.common.exception.ServerErrorCode.INVALID_JDBC_SOURCE_CONFIG;
    +import static org.apache.kylin.common.exception.ServerErrorCode.INVALID_KERBEROS_FILE;
     import static org.apache.kylin.common.exception.ServerErrorCode.INVALID_PARAMETER;
     import static org.apache.kylin.common.exception.ServerErrorCode.PERMISSION_DENIED;
     import static org.apache.kylin.common.exception.ServerErrorCode.PROJECT_DROP_FAILED;
    @@ -172,6 +174,8 @@ public class ProjectService extends BasicService {
         private static final String SNAPSHOT_AUTO_REFRESH_TIME_MODES = "DAY, HOURS, MINUTE";
         private static final String KYLIN_QUERY_PUSHDOWN_RUNNER_CLASS_NAME = "kylin.query.pushdown.runner-class-name";
         private static final String DEFAULT_VAL = "default";
    +    private static final Pattern INVALID_FILENAME_CHARS = Pattern.compile("[\\\\/:*?\"'<>|\\u0000-\\u001F\\s]|^[.]+$|[.]$");
    +
         @Autowired
         UserService userService;
         @Autowired
    @@ -1242,11 +1246,26 @@ public File backupAndDeleteKeytab(String principal) throws IOException {
             return kFile;
         }
     
    -    public File generateTempKeytab(String principal, MultipartFile keytabFile) throws IOException {
    -        Message msg = MsgPicker.getMsg();
    +    public static void checkPrincipal(String principal, Message msg) {
             if (null == principal || principal.isEmpty()) {
                 throw new KylinException(EMPTY_PARAMETER, msg.getPrincipalEmpty());
             }
    +
    +        // principal length
    +        if (principal.length() > MAX_FILENAME_LENGTH - KerberosLoginManager.TMP_KEYTAB_SUFFIX.length()) {
    +            throw new KylinException(INVALID_KERBEROS_FILE, msg.getKerberosInfoError());
    +        }
    +
    +        // invalid characters
    +        if (INVALID_FILENAME_CHARS.matcher(principal).find()) {
    +            throw new KylinException(INVALID_KERBEROS_FILE, msg.getKerberosInfoError());
    +        }
    +    }
    +
    +    public File generateTempKeytab(String principal, MultipartFile keytabFile) throws IOException {
    +        Message msg = MsgPicker.getMsg();
    +        checkPrincipal(principal, msg);
    +
             val originalFilename = keytabFile.getOriginalFilename();
             if (originalFilename == null || !originalFilename.endsWith(".keytab")) {
                 throw new KylinException(FILE_TYPE_MISMATCH, msg.getKeytabFileTypeMismatch());
    
  • src/common-service/src/test/java/org/apache/kylin/rest/service/FileServiceTest.java+215 13 modified
    @@ -19,10 +19,15 @@
     package org.apache.kylin.rest.service;
     
     import static java.nio.charset.StandardCharsets.UTF_8;
    +import static org.apache.kylin.common.constant.Constants.BACKSLASH;
     import static org.apache.kylin.common.constant.Constants.METADATA_FILE;
    +import static org.apache.kylin.common.constant.Constants.SYSTEM_TMP_DIR;
    +import static org.apache.kylin.rest.service.FileService.METADATA_TMP_PREFIX;
     import static org.junit.jupiter.api.Assertions.assertEquals;
     import static org.junit.jupiter.api.Assertions.assertFalse;
    +import static org.junit.jupiter.api.Assertions.assertInstanceOf;
     import static org.junit.jupiter.api.Assertions.assertNull;
    +import static org.junit.jupiter.api.Assertions.assertThrows;
     import static org.junit.jupiter.api.Assertions.assertTrue;
     import static org.junit.jupiter.api.Assertions.fail;
     import static org.mockito.ArgumentMatchers.any;
    @@ -34,6 +39,8 @@
     import java.io.IOException;
     import java.io.InputStream;
     import java.nio.file.Files;
    +import java.nio.file.Paths;
    +import java.util.Locale;
     import java.util.Objects;
     
     import org.apache.hadoop.fs.Path;
    @@ -63,14 +70,14 @@ class FileServiceTest {
         @Test
         void getMetadataBackupFromTmpPath() throws IOException {
             InputStream is = null;
    -        val tmpDirectory = Files.createTempDirectory("MetadataBackupTmp-").toFile();
    +        val tmpDirectory = Files.createTempDirectory(Paths.get(SYSTEM_TMP_DIR), METADATA_TMP_PREFIX).toFile();
             val tmpFile = new File(tmpDirectory, METADATA_FILE);
             val tmpFilePath = tmpFile.getAbsolutePath();
             try {
                 is = fileService.getMetadataBackupFromTmpPath(tmpFilePath, 0L);
                 fail();
             } catch (IOException e) {
    -            assertTrue(e instanceof FileNotFoundException);
    +            assertInstanceOf(FileNotFoundException.class, e);
                 assertEquals("Metadata backup temp file is not a file: " + tmpFilePath, e.getMessage());
             } finally {
                 close(is);
    @@ -88,7 +95,7 @@ void getMetadataBackupFromTmpPath() throws IOException {
                 is = fileService.getMetadataBackupFromTmpPath(tmpFilePath, 0L);
                 fail();
             } catch (IOException e) {
    -            assertTrue(e instanceof FileNotFoundException);
    +            assertInstanceOf(FileNotFoundException.class, e);
                 assertEquals("Metadata backup temp file length does not right: " + tmpFilePath + ", length :" + 3,
                         e.getMessage());
             } finally {
    @@ -115,7 +122,7 @@ void saveMetadataBackupInTmpPath() throws IOException {
                 result = fileService.saveMetadataBackupInTmpPath(path.toString());
                 fail();
             } catch (IOException e) {
    -            assertTrue(e instanceof FileNotFoundException);
    +            assertInstanceOf(FileNotFoundException.class, e);
                 assertEquals("Metadata backup file is not a file: " + path, e.getMessage());
                 assertNull(result);
             }
    @@ -141,15 +148,15 @@ void saveMetadataBackupInTmpPathWithoutMetadataZipFile() throws IOException {
                 fileService.saveMetadataBackupInTmpPath(path.toString());
                 fail();
             } catch (IOException e) {
    -            assertTrue(e instanceof FileNotFoundException);
    +            assertInstanceOf(FileNotFoundException.class, e);
                 assertTrue(e.getMessage().endsWith("HDFS backup file:" + path + ", len: " + 3));
             }
         }
     
         @Test
         void saveMetadataBackupTmpFromRequest() throws IOException {
             InputStream is = null;
    -        val tmpDirectory = Files.createTempDirectory("MetadataBackupTmp-").toFile();
    +        val tmpDirectory = Files.createTempDirectory(Paths.get(SYSTEM_TMP_DIR), METADATA_TMP_PREFIX).toFile();
             val tmpFile = new File(tmpDirectory, METADATA_FILE);
             val tmpFilePath = tmpFile.getAbsolutePath();
     
    @@ -161,7 +168,7 @@ void saveMetadataBackupTmpFromRequest() throws IOException {
                 fileService.saveMetadataBackupTmpFromRequest(0L, is);
                 fail();
             } catch (IOException e) {
    -            assertTrue(e instanceof FileNotFoundException);
    +            assertInstanceOf(FileNotFoundException.class, e);
                 assertTrue(e.getMessage().startsWith("Metadata backup temp file length does not right: "));
                 assertTrue(e.getMessage().endsWith(" length :" + 3));
             } finally {
    @@ -184,7 +191,7 @@ void saveMetadataBackupInHDFS() throws IOException {
             val fileSystem = HadoopUtil.getWorkingFileSystem();
             val path = new Path(HadoopUtil.getBackupFolder(KylinConfig.getInstanceFromEnv()),
                     new Path(RandomUtil.randomUUIDStr(), METADATA_FILE));
    -        val tmpDirectory = Files.createTempDirectory("MetadataBackupTmp-").toFile();
    +        val tmpDirectory = Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
             val tmpFile = new File(tmpDirectory, METADATA_FILE);
             val tmpFilePath = tmpFile.getAbsolutePath();
             try (val os = new FileOutputStream(tmpFile)) {
    @@ -195,7 +202,7 @@ void saveMetadataBackupInHDFS() throws IOException {
                 fileService.saveMetadataBackupInHDFS(path.toString(), tmpFilePath, 0L);
                 fail();
             } catch (IOException e) {
    -            assertTrue(e instanceof FileNotFoundException);
    +            assertInstanceOf(FileNotFoundException.class, e);
                 assertEquals("Metadata backup temp file length does not right.\n Tmp file: " + tmpFilePath + " length: " + 0
                         + "\n DFS file: " + path + " length: " + 3, e.getMessage());
             }
    @@ -207,7 +214,7 @@ void saveMetadataBackupInHDFS() throws IOException {
     
         @Test
         void deleteTmpDir() throws IOException {
    -        val tmpDirectory = Files.createTempDirectory("MetadataBackupTmp-").toFile();
    +        val tmpDirectory = Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
             val tmpFile = new File(tmpDirectory, METADATA_FILE);
             val tmpFilePath = tmpFile.getAbsolutePath();
             try (val os = new FileOutputStream(tmpFile)) {
    @@ -219,7 +226,7 @@ void deleteTmpDir() throws IOException {
     
         @Test
         void downloadMetadataBackTmpFile() throws IOException {
    -        val tmpDirectory = Files.createTempDirectory("MetadataBackupTmp-").toFile();
    +        val tmpDirectory = Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
             val tmpFile = new File(tmpDirectory, METADATA_FILE);
             val tmpFilePath = tmpFile.getAbsolutePath();
             try (val os = new FileOutputStream(tmpFile)) {
    @@ -236,7 +243,7 @@ void downloadMetadataBackTmpFile() throws IOException {
     
         @Test
         void saveBroadcastMetadataBackup() throws IOException {
    -        val tmpDirectory = Files.createTempDirectory("MetadataBackupTmp-").toFile();
    +        val tmpDirectory = Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
             val tmpFile = new File(tmpDirectory, METADATA_FILE);
             val tmpFilePath = tmpFile.getAbsolutePath();
             try (val os = new FileOutputStream(tmpFile)) {
    @@ -257,4 +264,199 @@ void saveBroadcastMetadataBackup() throws IOException {
             val fileStatus = fileSystem.getFileStatus(path);
             assertEquals(3, fileStatus.getLen());
         }
    -}
    +
    +    @Test
    +    void saveAndGet() throws IOException {
    +        InputStream is = null;
    +        val tmpDirectory = Files.createTempDirectory(Paths.get(SYSTEM_TMP_DIR), METADATA_TMP_PREFIX).toFile();
    +        val tmpFile = new File(tmpDirectory, METADATA_FILE);
    +        val tmpFilePath = tmpFile.getAbsolutePath();
    +
    +        try (val os = new FileOutputStream(tmpFile)) {
    +            os.write("123".getBytes(UTF_8));
    +            is = fileService.getMetadataBackupFromTmpPath(tmpFilePath, 3L);
    +        }
    +
    +        try {
    +            is = fileService.getMetadataBackupFromTmpPath(tmpFilePath, 3L);
    +
    +            val tempPath = fileService.saveMetadataBackupTmpFromRequest(3L, is);
    +            val tempFile = new File(tempPath);
    +            assertEquals(3, tempFile.length());
    +
    +            try (val iss = fileService.getMetadataBackupFromTmpPath(tmpFilePath, 3L)) {
    +                val tempPath2 = fileService.saveMetadataBackupTmpFromRequest(3L, iss);
    +                val tempFile2 = new File(tempPath2);
    +                assertEquals(3, tempFile2.length());
    +            }
    +        } finally {
    +            close(is);
    +        }
    +    }
    +
    +    @Test
    +    void testGetSafeAbsolutePath() {
    +        val tmpPath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX);
    +        val tmpDir = tmpPath.toAbsolutePath().normalize().toString();
    +        val validFilePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "validFile.txt");
    +        {
    +            String filePath = tmpPath.toString();
    +            String result = FileService.getSafeAbsolutePath(filePath);
    +
    +            assertEquals(Paths.get(filePath).toAbsolutePath().normalize().toString(), result);
    +            assertTrue(result.startsWith(tmpDir));
    +        }
    +        {
    +            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "subdir", "file.txt").toString();
    +            String result = FileService.getSafeAbsolutePath(filePath);
    +
    +            assertEquals(Paths.get(filePath).toAbsolutePath().normalize().toString(), result);
    +            assertTrue(result.startsWith(tmpDir));
    +        }
    +        {
    +            String result = FileService.getSafeAbsolutePath(SYSTEM_TMP_DIR + BACKSLASH + METADATA_TMP_PREFIX);
    +
    +            assertEquals(tmpDir, result);
    +        }
    +        {
    +            String filePath = SYSTEM_TMP_DIR + BACKSLASH + METADATA_TMP_PREFIX + "/./validFile.txt";
    +            String result = FileService.getSafeAbsolutePath(filePath);
    +
    +            assertEquals(validFilePath.toAbsolutePath().normalize().toString(), result);
    +            assertTrue(result.startsWith(tmpDir));
    +        }
    +        {
    +            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "subdir1", "..", "subdir2", "file.txt")
    +                    .toString();
    +            String result = FileService.getSafeAbsolutePath(filePath);
    +
    +            assertEquals(Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "subdir2", "file.txt").toAbsolutePath()
    +                    .normalize().toString(), result);
    +            assertTrue(result.startsWith(tmpDir));
    +        }
    +        {
    +            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "level1", "level2", "level3", "file.txt")
    +                    .toString();
    +            String result = FileService.getSafeAbsolutePath(filePath);
    +
    +            assertEquals(Paths.get(filePath).toAbsolutePath().normalize().toString(), result);
    +            assertTrue(result.startsWith(tmpDir));
    +        }
    +        {
    +            String filePath = Paths
    +                    .get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "..", tmpPath.getFileName().toString(), "validFile.txt")
    +                    .toString();
    +            String result = FileService.getSafeAbsolutePath(filePath);
    +
    +            assertEquals(validFilePath.toAbsolutePath().normalize().toString(), result);
    +            assertTrue(result.startsWith(tmpDir));
    +        }
    +        {
    +            String filePath = Paths
    +                    .get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "..", "..", tmpPath.toString(), "validFile.txt")
    +                    .toString();
    +            String result = FileService.getSafeAbsolutePath(filePath);
    +
    +            assertEquals(validFilePath.toAbsolutePath().normalize().toString(), result);
    +            assertTrue(result.startsWith(tmpDir));
    +        }
    +    }
    +
    +    @Test
    +    void testGetSafeAbsolutePathErrorWithDot() {
    +        {
    +            String filePath = Paths.get(SYSTEM_TMP_DIR, "..", "..", "etc", "passwd").toString();
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path outside base directory"));
    +            assertTrue(exception.getMessage().contains(filePath));
    +        }
    +        {
    +            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "..", "..", "..", "etc", "passwd")
    +                    .toString();
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path outside base directory"));
    +            assertTrue(exception.getMessage().contains(filePath));
    +        }
    +        {
    +            String filePath = Paths.get(SYSTEM_TMP_DIR, "..", "malicious.txt").toString();
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path outside base directory"));
    +        }
    +        {
    +            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "..", "malicious.txt").toString();
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path not kylin metadata tmp directory"));
    +        }
    +        {
    +            String filePath = String.format(Locale.ROOT, "%s/%s/subdir/../../../../../../etc/passwd", SYSTEM_TMP_DIR,
    +                    METADATA_TMP_PREFIX);
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path outside base directory"));
    +        }
    +        {
    +            String filePath = "../../../etc/passwd";
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path outside base directory"));
    +        }
    +    }
    +
    +    @Test
    +    void testGetSafeAbsolutePathError() {
    +        {
    +            String filePath = "/etc/passwd";
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path outside base directory"));
    +            assertTrue(exception.getMessage().contains(filePath));
    +        }
    +        {
    +            String filePath = "";
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path outside base directory"));
    +        }
    +        {
    +            // Test with null path
    +            assertThrows(Exception.class, () -> {
    +                FileService.getSafeAbsolutePath(null);
    +            });
    +        }
    +        {
    +            // Test path with only dots (current directory reference)
    +            String filePath = ".";
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path outside base directory"));
    +        }
    +        {
    +            // Test path with only double dots (parent directory reference)
    +            String filePath = "..";
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path outside base directory"));
    +        }
    +        {
    +            String filePath = Paths.get(SYSTEM_TMP_DIR, "malicious.txt").toString();
    +            SecurityException exception = assertThrows(SecurityException.class,
    +                    () -> FileService.getSafeAbsolutePath(filePath));
    +
    +            assertTrue(exception.getMessage().contains("Path not kylin metadata tmp directory"));
    +        }
    +    }
    +}
    \ No newline at end of file
    
  • src/core-common/src/main/java/org/apache/kylin/common/constant/Constants.java+3 0 modified
    @@ -52,10 +52,13 @@ private Constants() {
         public static final String ASYNC = "ASYNC";
     
         public static final String METADATA_FILE = "metadata.zip";
    +    public static final String SYSTEM_TMP_DIR = "/tmp";
     
         public static final String CORE_META_DIR = "core_meta";
     
         public static final String CACHE_MODEL_COMMAND = "CACHE FILES %s SELECT * FROM '%s' CACHEPROPERTIES (recursive=true)";
         public static final String CACHE_TABLE_COMMAND = "CACHE DATA %s SELECT %s FROM '%s' %s";
         public static final String FILTER_COMMAND = "AFTER %s AS OF '%s'";
    +
    +    public static final int MAX_FILENAME_LENGTH = 255;
     }
    
  • src/core-common/src/main/java/org/apache/kylin/common/util/AddressUtil.java+18 0 modified
    @@ -18,10 +18,14 @@
     package org.apache.kylin.common.util;
     
     import java.net.InetAddress;
    +import java.net.MalformedURLException;
    +import java.net.URL;
     import java.net.UnknownHostException;
    +import java.util.Locale;
     
     import org.apache.commons.lang3.StringUtils;
     import org.apache.kylin.common.KylinConfig;
    +import org.apache.kylin.common.exception.KylinRuntimeException;
     import org.springframework.cloud.commons.util.InetUtils;
     import org.springframework.cloud.commons.util.InetUtilsProperties;
     
    @@ -127,4 +131,18 @@ public static void validateHost(String host) {
                 throw new IllegalArgumentException("Url contains disallowed chars, host: " + host);
             }
         }
    +
    +    public static String extractIpAndPort(String urlString) {
    +        try {
    +            URL url = new URL(urlString);
    +            String host = url.getHost();
    +            int port = url.getPort();
    +            if (StringUtils.isEmpty(host)) {
    +                throw new KylinRuntimeException(String.format(Locale.ROOT, "Invalid or illegal URL: %s", urlString));
    +            }
    +            return host + ":" + port;
    +        } catch (MalformedURLException e) {
    +            throw new KylinRuntimeException(String.format(Locale.ROOT, "Invalid or illegal URL: %s", urlString), e);
    +        }
    +    }
     }
    
  • src/core-common/src/test/java/org/apache/kylin/common/util/AddressUtilTest.java+53 0 modified
    @@ -20,6 +20,7 @@
     import static org.apache.kylin.common.util.TestUtils.getTestConfig;
     
     import org.apache.commons.lang3.StringUtils;
    +import org.apache.kylin.common.exception.KylinRuntimeException;
     import org.apache.kylin.junit.annotation.MetadataInfo;
     import org.junit.Rule;
     import org.junit.jupiter.api.Assertions;
    @@ -106,4 +107,56 @@ void testIsSameHost() {
             Assertions.assertTrue(AddressUtil.isSameHost(hostInfoFetcher.getHostname()));
             Assertions.assertFalse(AddressUtil.isSameHost("unknown"));
         }
    +
    +    @Test
    +    void testExtractIpAndPort() {
    +        // Test valid HTTP URL with port
    +        Assertions.assertEquals("example.com:8080", AddressUtil.extractIpAndPort("http://example.com:8080/path"));
    +
    +        // Test valid HTTPS URL with port
    +        Assertions.assertEquals("example.com:443", AddressUtil.extractIpAndPort("https://example.com:443/path"));
    +
    +        // Test URL without explicit port (default port -1)
    +        Assertions.assertEquals("example.com:-1", AddressUtil.extractIpAndPort("http://example.com/path"));
    +
    +        // Test IP address with port
    +        Assertions.assertEquals("192.168.1.1:9090", AddressUtil.extractIpAndPort("http://192.168.1.1:9090"));
    +
    +        // Test localhost with port
    +        Assertions.assertEquals("localhost:7070", AddressUtil.extractIpAndPort("http://localhost:7070"));
    +
    +        // Test URL with query parameters
    +        Assertions.assertEquals("example.com:8080",
    +                AddressUtil.extractIpAndPort("http://example.com:8080/path?param=value"));
    +
    +        // Test URL with fragment
    +        Assertions.assertEquals("example.com:8080",
    +                AddressUtil.extractIpAndPort("http://example.com:8080/path#section"));
    +
    +        Assertions.assertEquals("example .com:8080",
    +                AddressUtil.extractIpAndPort("http://example .com:8080/path#section"));
    +    }
    +
    +    @Test
    +    void testExtractIpAndPortMalformedUrl() {
    +        // Test malformed URL - missing protocol
    +        Assertions.assertThrows(KylinRuntimeException.class, () -> AddressUtil.extractIpAndPort("example.com:8080"));
    +
    +        // Test malformed URL - empty string
    +        Assertions.assertThrows(KylinRuntimeException.class, () -> AddressUtil.extractIpAndPort(""));
    +
    +        // Test malformed URL - null (will throw NPE before reaching method)
    +        Assertions.assertThrows(Exception.class, () -> AddressUtil.extractIpAndPort(null));
    +
    +        // Test malformed URL - invalid protocol format
    +        Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> AddressUtil.extractIpAndPort("http:example.com:8080"));
    +
    +        // Test malformed URL - incomplete protocol
    +        Assertions.assertThrows(KylinRuntimeException.class,
    +                () -> AddressUtil.extractIpAndPort("http:/example.com:8080"));
    +
    +        // Test malformed URL - unclosed bracket in host
    +        Assertions.assertThrows(KylinRuntimeException.class, () -> AddressUtil.extractIpAndPort("http://[invalid"));
    +    }
     }
    
  • src/core-metadata/src/main/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManager.java+13 1 modified
    @@ -21,6 +21,7 @@
     import java.util.List;
     import java.util.stream.Collectors;
     
    +import org.apache.commons.lang3.StringUtils;
     import org.apache.kylin.common.KylinConfig;
     import org.apache.kylin.common.persistence.MetadataType;
     import org.apache.kylin.common.persistence.ResourceStore;
    @@ -104,7 +105,7 @@ public ResourceGroup updateResourceGroup(ResourceGroupUpdater updater) {
             updater.modify(copy);
             return updateResourceGroup(copy);
         }
    -    
    +
         public List<String> getInstancesForProject(String project) {
             ResourceGroup resourceGroup = getResourceGroup();
             List<String> ids = resourceGroup.getResourceGroupMappingInfoList().stream()
    @@ -149,4 +150,15 @@ private ResourceGroup save(ResourceGroup resourceGroup) {
             crud.save(resourceGroup);
             return resourceGroup;
         }
    +
    +    public boolean checkServer(String host) {
    +        if (StringUtils.isBlank(host)) {
    +            return true;
    +        }
    +        if (isResourceGroupEnabled()) {
    +            return getResourceGroup().getKylinInstances().stream().map(KylinInstance::getInstance)
    +                    .noneMatch(server -> StringUtils.equals(server, host));
    +        }
    +        return true;
    +    }
     }
    
  • src/core-metadata/src/test/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManagerTest.java+23 1 modified
    @@ -34,6 +34,7 @@
     import org.junit.Assert;
     import org.junit.Before;
     import org.junit.Test;
    +import org.junit.jupiter.api.Assertions;
     
     import lombok.val;
     
    @@ -118,7 +119,7 @@ public void testListProjectWithPermission() {
             Assert.assertEquals(project, rgManager.listProjectWithPermission().get(0));
         }
     
    -    private void mockResourceGroup(String host, String project) {
    +    public static void mockResourceGroup(String host, String project) {
             ResourceGroupManager manager = ResourceGroupManager.getInstance(getTestConfig());
             manager.updateResourceGroup(copyForWrite -> {
                 copyForWrite.setResourceGroupEnabled(true);
    @@ -136,4 +137,25 @@ private void mockResourceGroup(String host, String project) {
                 copyForWrite.setResourceGroupMappingInfoList(Collections.singletonList(mappingInfo));
             });
         }
    +
    +    @Test
    +    public void testCheckServer() {
    +        String project = "default";
    +        mockResourceGroup(AddressUtil.getLocalInstance(), project);
    +        ResourceGroupManager manager = ResourceGroupManager.getInstance(getTestConfig());
    +
    +        // Test valid server
    +        Assertions.assertFalse(manager.checkServer(AddressUtil.getLocalInstance()));
    +
    +        // Test null host
    +        Assertions.assertTrue(manager.checkServer(null));
    +
    +        // Test empty host
    +        Assertions.assertTrue(manager.checkServer(""));
    +        Assertions.assertTrue(manager.checkServer(" "));
    +
    +        // Test host not found in servers
    +        Assertions.assertTrue(manager.checkServer("192.168.1.1:8080"));
    +    }
    +
     }
    
  • src/modeling-service/src/test/java/org/apache/kylin/rest/service/ProjectServiceTest.java+133 0 modified
    @@ -21,6 +21,7 @@
     import static org.apache.kylin.common.constant.Constants.HIDDEN_VALUE;
     import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_DRIVER_KEY;
     import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_PASS_KEY;
    +import static org.apache.kylin.common.constant.Constants.MAX_FILENAME_LENGTH;
     import static org.apache.kylin.metadata.model.MaintainModelType.MANUAL_MAINTAIN;
     
     import java.io.IOException;
    @@ -36,6 +37,8 @@
     
     import org.apache.kylin.common.KylinConfig;
     import org.apache.kylin.common.exception.KylinException;
    +import org.apache.kylin.common.msg.Message;
    +import org.apache.kylin.common.msg.MsgPicker;
     import org.apache.kylin.common.persistence.transaction.UnitOfWork;
     import org.apache.kylin.common.util.EncryptUtil;
     import org.apache.kylin.common.util.JsonUtil;
    @@ -77,6 +80,7 @@
     import org.apache.kylin.rest.response.StorageVolumeInfoResponse;
     import org.apache.kylin.rest.response.UserProjectPermissionResponse;
     import org.apache.kylin.rest.security.AclPermissionEnum;
    +import org.apache.kylin.rest.security.KerberosLoginManager;
     import org.apache.kylin.rest.service.task.QueryHistoryMetaUpdateScheduler;
     import org.apache.kylin.rest.util.AclEvaluate;
     import org.apache.kylin.rest.util.AclUtil;
    @@ -99,6 +103,7 @@
     import org.springframework.test.util.ReflectionTestUtils;
     import org.springframework.web.multipart.MultipartFile;
     
    +import alluxio.shaded.client.org.apache.commons.lang3.StringUtils;
     import lombok.val;
     import lombok.var;
     import lombok.extern.slf4j.Slf4j;
    @@ -1013,6 +1018,134 @@ public void testGenerateTempKeytab() {
             Assert.assertThrows(KylinException.class, () -> projectService.generateTempKeytab("test", multipartFile));
         }
     
    +    @Test
    +    public void testCheckPrincipal() {
    +        Message msg = MsgPicker.getMsg();
    +        testCheckPrincipalNormal(msg);
    +        testCheckPrincipalWithEmpty(msg);
    +        testCheckPrincipalWithFileNameLengthLimitation(msg);
    +        testCheckPrincipalWithIllegalPunctuationSymbols(msg);
    +        testCheckPrincipalWithControlCharacters(msg);
    +        testCheckPrincipalWithWhitespace(msg);
    +        testCheckPrincipalWithDot(msg);
    +        testCheckPrincipalWithVariousEffectiveCharacterCombinations(msg);
    +    }
    +
    +    private void testCheckPrincipalNormal(Message msg) {
    +        // Test normal condition
    +        ProjectService.checkPrincipal("validuser", msg);
    +        ProjectService.checkPrincipal("user123", msg);
    +        ProjectService.checkPrincipal("user_name", msg);
    +        ProjectService.checkPrincipal("user-name", msg);
    +    }
    +
    +    private void testCheckPrincipalWithEmpty(Message msg) {
    +        // Test null and empty string
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal(null, msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("", msg));
    +    }
    +
    +    private void testCheckPrincipalWithFileNameLengthLimitation(Message msg) {
    +        // Test length exceeds limit(MAX_FILENAME_LENGTH - TMP_KEYTAB_SUFFIX.length())
    +        int maxLength = MAX_FILENAME_LENGTH - KerberosLoginManager.TMP_KEYTAB_SUFFIX.length();
    +        String longPrincipal = StringUtils.repeat("a", maxLength + 1);
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal(longPrincipal, msg));
    +        // Test boundary length (just reached limit)
    +        String boundaryPrincipal = StringUtils.repeat("a", maxLength);
    +        ProjectService.checkPrincipal(boundaryPrincipal, msg);
    +    }
    +
    +    private void testCheckPrincipalWithIllegalPunctuationSymbols(Message msg) {
    +        // Test backslash and forward slash
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user\\name", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user/name", msg));
    +
    +        // Test colon
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user:name", msg));
    +
    +        // Test asterisks and question marks
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user*name", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user?name", msg));
    +
    +        // Test double quotes and single quotes
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user\"name", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user'name", msg));
    +
    +        // Test angle brackets and vertical line
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user<name", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user>name", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user|name", msg));
    +    }
    +
    +    private void testCheckPrincipalWithControlCharacters(Message msg) {
    +        // Test control characters(\u0000-\u001F)
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user\u0000name", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user\u0001name", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user\u000Fname", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user\u001Fname", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user\tname", msg)); // \u0009
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user\nname", msg)); //
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user\rname", msg)); //
    +    }
    +
    +    private void testCheckPrincipalWithWhitespace(Message msg) {
    +        // Test whitespace characters
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user name", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal(" username", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("username ", msg));
    +    }
    +
    +    private void testCheckPrincipalWithDot(Message msg) {
    +        // Test only the case with periods.(^[.]+$)
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal(".", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("..", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("...", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("....", msg));
    +
    +        // Test cases ending with a period([.]$)
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("username.", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("user.name.", msg));
    +        Assert.assertThrows(KylinException.class, () -> ProjectService.checkPrincipal("a.", msg));
    +
    +        // Test valid cases with dots (not all dots, not at the end)
    +        ProjectService.checkPrincipal("user.name", msg);
    +        ProjectService.checkPrincipal("user.sub.domain", msg);
    +        ProjectService.checkPrincipal(".validuser", msg); // 以点开头但不是全点
    +        ProjectService.checkPrincipal("user.123", msg);
    +    }
    +
    +    private void testCheckPrincipalWithVariousEffectiveCharacterCombinations(Message msg) {
    +        // Test various effective character combinations
    +        ProjectService.checkPrincipal("user123", msg);
    +        ProjectService.checkPrincipal("user_name", msg);
    +        ProjectService.checkPrincipal("user-name", msg);
    +        ProjectService.checkPrincipal("USER", msg);
    +        ProjectService.checkPrincipal("User", msg);
    +        ProjectService.checkPrincipal("123user", msg);
    +        ProjectService.checkPrincipal("user123name", msg);
    +        ProjectService.checkPrincipal("user_123-name", msg);
    +        ProjectService.checkPrincipal("a", msg);
    +        ProjectService.checkPrincipal("A", msg);
    +        ProjectService.checkPrincipal("1", msg);
    +        ProjectService.checkPrincipal("_", msg);
    +        ProjectService.checkPrincipal("-", msg);
    +        ProjectService.checkPrincipal("u0", msg);
    +        ProjectService.checkPrincipal("u0000", msg);
    +        ProjectService.checkPrincipal("u001F", msg);
    +        ProjectService.checkPrincipal("1F", msg);
    +
    +        // Unicode character (non-control character)
    +        ProjectService.checkPrincipal("用户名", msg);
    +        ProjectService.checkPrincipal("usuário", msg);
    +
    +        // Containing Dot but Valid
    +        ProjectService.checkPrincipal("user.domain", msg);
    +        ProjectService.checkPrincipal(".user", msg);
    +        ProjectService.checkPrincipal("u.s.e.r", msg);
    +        ProjectService.checkPrincipal("u.s..e.r", msg);
    +        ProjectService.checkPrincipal("u.s...e.r", msg);
    +    }
    +
         @Test
         public void testCleanupGarbage() throws Exception {
             QueryHistoryMetaUpdateScheduler qhMetaUpdateScheduler = QueryHistoryMetaUpdateScheduler.getInstance();
    
  • src/ops-server/src/main/java/org/apache/kylin/rest/controller/OpsController.java+6 5 modified
    @@ -43,7 +43,6 @@
     import org.apache.kylin.helper.MetadataToolHelper;
     import org.apache.kylin.metadata.asynctask.MetadataRestoreTask;
     import org.apache.kylin.metadata.project.ProjectInstance;
    -import org.apache.kylin.rest.cluster.ClusterManager;
     import org.apache.kylin.rest.request.DiagPackageRequest;
     import org.apache.kylin.rest.request.DiagProgressRequest;
     import org.apache.kylin.rest.request.MaintenanceModeRequest;
    @@ -80,16 +79,13 @@
     @Controller
     @RequestMapping(value = "/api/system", produces = { HTTP_VND_APACHE_KYLIN_JSON, HTTP_VND_APACHE_KYLIN_V4_PUBLIC_JSON })
     public class OpsController extends NBasicController {
    -    
    +
         private static String DEPRECATED_MAINTENANCE_MODE = "Maintenance mode has been deprecated.";
     
         @Autowired
         @Qualifier("systemService")
         private SystemService systemService;
     
    -    @Autowired
    -    private ClusterManager clusterManager;
    -
         @Autowired
         private AclEvaluate aclEvaluate;
     
    @@ -151,6 +147,7 @@ public EnvelopeResponse<String> getRemoteDumpDiagPackage(
                         diagPackageRequest.getJobId(), diagPackageRequest.getProject(), headers);
                 return new EnvelopeResponse<>(CODE_SUCCESS, uuid, "");
             } else {
    +            checkServer(AddressUtil.extractIpAndPort(host));
                 String url = host + "/kylin/api/system/diag";
                 return generateTaskForRemoteHost(request, url);
             }
    @@ -170,6 +167,7 @@ public EnvelopeResponse<String> getRemoteDumpQueryDiagPackage(
                         queryDiagPackageRequest.getProject(), headers);
                 return new EnvelopeResponse<>(CODE_SUCCESS, uuid, "");
             } else {
    +            checkServer(AddressUtil.extractIpAndPort(host));
                 String url = host + "/kylin/api/system/diag/query";
                 return generateTaskForRemoteHost(request, url);
             }
    @@ -195,6 +193,7 @@ public EnvelopeResponse<DiagStatusResponse> getRemotePackageStatus(
             if (StringUtils.isEmpty(host) || KylinConfig.getInstanceFromEnv().getMicroServiceMode() != null) {
                 return systemService.getExtractorStatus(id, project);
             } else {
    +            checkServer(AddressUtil.extractIpAndPort(host));
                 String url = host + "/kylin/api/system/diag/status?id=" + id;
                 if (StringUtils.isNotEmpty(project)) {
                     url = url + "&project=" + project;
    @@ -215,6 +214,7 @@ public void remoteDownloadPackage(@RequestParam(value = "host", required = false
                 setDownloadResponse(systemService.getDiagPackagePath(id, project), MediaType.APPLICATION_OCTET_STREAM_VALUE,
                         response);
             } else {
    +            checkServer(AddressUtil.extractIpAndPort(host));
                 String url = host + "/kylin/api/system/diag?id=" + id;
                 if (StringUtils.isNotEmpty(project)) {
                     url = url + "&project=" + project;
    @@ -234,6 +234,7 @@ public EnvelopeResponse<String> remoteStopPackage(@RequestParam(value = "host",
                 systemService.stopDiagTask(id);
                 return new EnvelopeResponse<>(CODE_SUCCESS, "", "");
             } else {
    +            checkServer(AddressUtil.extractIpAndPort(host));
                 String url = host + "/kylin/api/system/diag?id=" + id;
                 return generateTaskForRemoteHost(request, url);
             }
    
  • src/ops-server/src/test/java/org/apache/kylin/rest/controller/OpsControllerTest.java+15 4 modified
    @@ -25,9 +25,11 @@
     import java.time.temporal.ChronoUnit;
     import java.util.Random;
     
    +import org.apache.kylin.common.util.AddressUtil;
     import org.apache.kylin.common.util.JsonUtil;
     import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
     import org.apache.kylin.junit.rule.TransactionExceptedException;
    +import org.apache.kylin.rest.cluster.ClusterManager;
     import org.apache.kylin.rest.constant.Constant;
     import org.apache.kylin.rest.request.DiagPackageRequest;
     import org.apache.kylin.rest.request.DiagProgressRequest;
    @@ -56,6 +58,9 @@ public class OpsControllerTest extends NLocalFileMetadataTestCase {
         @Mock
         private SystemService systemService;
     
    +    @Mock
    +    private ClusterManager clusterManager;
    +
         @InjectMocks
         private OpsController opsController = Mockito.spy(new OpsController());
     
    @@ -83,7 +88,7 @@ public void testRemoteDumpDiagPackage() throws Exception {
             DiagPackageRequest request = new DiagPackageRequest();
             Mockito.doAnswer(x -> null).when(opsController).generateTaskForRemoteHost(Mockito.any(), Mockito.any());
             mockMvc.perform(MockMvcRequestBuilders.post("/api/system/diag").contentType(MediaType.APPLICATION_JSON)
    -                .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)).param("host", "ip")
    +                .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)).param("host", mockHost())
                     .content(JsonUtil.writeValueAsString(request)))
                     .andExpect(MockMvcResultMatchers.status().is5xxServerError());
     
    @@ -152,7 +157,7 @@ public void testRemoteDumpDiagPackage() throws Exception {
         public void testGetRemoteDumpDiagPackage() throws Exception {
             Mockito.doAnswer(x -> null).when(opsController).generateTaskForRemoteHost(Mockito.any(), Mockito.anyString());
             mockMvc.perform(MockMvcRequestBuilders.get("/api/system/diag/status").contentType(MediaType.APPLICATION_JSON)
    -                .param("id", "id").param("host", "ip").param("project", "project")
    +                .param("id", "id").param("host", mockHost()).param("project", "project")
                     .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
                     .andExpect(MockMvcResultMatchers.status().isOk());
             Mockito.verify(opsController).getRemotePackageStatus(Mockito.anyString(), Mockito.anyString(),
    @@ -163,7 +168,7 @@ public void testGetRemoteDumpDiagPackage() throws Exception {
         public void testRemoteDownloadPackage() throws Exception {
             Mockito.doNothing().when(opsController).downloadFromRemoteHost(Mockito.any(), Mockito.any(), Mockito.any());
             mockMvc.perform(MockMvcRequestBuilders.get("/api/system/diag").contentType(MediaType.APPLICATION_JSON)
    -                .param("id", "id").param("host", "ip").param("project", "project")
    +                .param("id", "id").param("host", mockHost()).param("project", "project")
                     .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
                     .andExpect(MockMvcResultMatchers.status().isOk());
             Mockito.verify(opsController).remoteDownloadPackage(Mockito.anyString(), Mockito.anyString(),
    @@ -185,7 +190,8 @@ public void testGetRemoteDumpQueryDiagPackage() throws Exception {
         public void testRemoteStopPackage() throws Exception {
             Mockito.doAnswer(x -> null).when(opsController).generateTaskForRemoteHost(Mockito.any(), Mockito.anyString());
             mockMvc.perform(MockMvcRequestBuilders.delete("/api/system/diag").contentType(MediaType.APPLICATION_JSON)
    -                .param("host", "ip").param("id", "id").accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
    +                .param("host", mockHost()).param("id", "id")
    +                .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
                     .andExpect(MockMvcResultMatchers.status().isOk());
             Mockito.verify(opsController).remoteStopPackage(Mockito.anyString(), Mockito.anyString(), Mockito.any());
         }
    @@ -199,4 +205,9 @@ public void testUpdateDiagProgress() throws Exception {
                     .content(JsonUtil.writeValueAsString(request))).andExpect(MockMvcResultMatchers.status().isOk());
             Mockito.verify(opsController).updateDiagProgress(Mockito.any());
         }
    +
    +    private String mockHost() {
    +        return "http://" + AddressUtil.getLocalInstance();
    +    }
    +
     }
    
  • src/ops-service/src/test/java/org/apache/kylin/rest/MockClusterManagerTest.java+25 0 added
    @@ -0,0 +1,25 @@
    +package org.apache.kylin.rest;
    +
    +import org.apache.kylin.rest.cluster.ClusterManager;
    +import org.junit.jupiter.api.Assertions;
    +import org.junit.jupiter.api.Test;
    +
    +public class MockClusterManagerTest {
    +    @Test
    +    void testCheckServer() {
    +        ClusterManager clusterManager = new MockClusterManager();
    +
    +        // Test valid server
    +        Assertions.assertFalse(clusterManager.checkServer("127.0.0.1:7070"));
    +
    +        // Test null host
    +        Assertions.assertTrue(clusterManager.checkServer(null));
    +
    +        // Test empty host
    +        Assertions.assertTrue(clusterManager.checkServer(""));
    +        Assertions.assertTrue(clusterManager.checkServer(" "));
    +
    +        // Test host not found in servers
    +        Assertions.assertTrue(clusterManager.checkServer("192.168.1.1:8080"));
    +    }
    +}
    
  • src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java+0 4 modified
    @@ -72,7 +72,6 @@
     import org.apache.kylin.metadata.querymeta.SelectedColumnMeta;
     import org.apache.kylin.metadata.querymeta.TableMetaWithType;
     import org.apache.kylin.query.plugin.profiler.AsyncProfiling;
    -import org.apache.kylin.rest.cluster.ClusterManager;
     import org.apache.kylin.rest.exception.ForbiddenException;
     import org.apache.kylin.rest.exception.InternalErrorException;
     import org.apache.kylin.rest.model.Query;
    @@ -145,9 +144,6 @@ public class NQueryController extends NBasicController {
         @Qualifier("queryHistoryService")
         private QueryHistoryService queryHistoryService;
     
    -    @Autowired
    -    private ClusterManager clusterManager;
    -
         @Autowired
         private QueryCacheManager queryCacheManager;
     
    
  • src/server/src/main/java/org/apache/kylin/rest/ResourceGroupLoadBalancer.java+1 1 modified
    @@ -73,7 +73,7 @@ private Response<ServiceInstance> getInstanceResponse() {
                     .collect(Collectors.toList());
             String project = ProjectInfoParser.parseProjectInfo(httpServletRequest).getFirst();
             if (rgManager.isResourceGroupEnabled() && !project.equals(UnitOfWork.GLOBAL_UNIT)) {
    -            jobNodes.retainAll(rgManager.getInstancesForProject(project));
    +            jobNodes = rgManager.getInstancesForProject(project);
             }
     
             if (jobNodes.isEmpty()) {
    
  • src/tool/src/main/java/org/apache/kylin/rest/cluster/ClusterManager.java+8 0 modified
    @@ -20,6 +20,7 @@
     import java.util.List;
     import java.util.Locale;
     
    +import org.apache.commons.lang3.StringUtils;
     import org.apache.kylin.common.exception.KylinRuntimeException;
     import org.apache.kylin.rest.response.ServerInfoResponse;
     import org.apache.kylin.rest.util.SpringContext;
    @@ -44,4 +45,11 @@ default ServerInfoResponse getServerById(String serverId) {
         static ClusterManager getInstance() {
             return SpringContext.getApplicationContext().getBean(ClusterManager.class);
         }
    +
    +    default boolean checkServer(String host) {
    +        if (StringUtils.isBlank(host)) {
    +            return true;
    +        }
    +        return getServers().stream().noneMatch(server -> StringUtils.equals(server.getHost(), host));
    +    }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.