This is an automated email from the ASF dual-hosted git repository.
gaborgsomogyi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
The following commit(s) were added to refs/heads/master by this push:
new cf2df298f22 [FLINK-40332][table] Prevent path traversal in
FileCatalogStore catalog names
cf2df298f22 is described below
commit cf2df298f22f18e9d104e80597ca69b05006bc4c
Author: Gabor Somogyi <[email protected]>
AuthorDate: Wed Aug 5 17:11:09 2026 +0200
[FLINK-40332][table] Prevent path traversal in FileCatalogStore catalog
names
---
.../flink/table/catalog/FileCatalogStore.java | 23 +++++++++++++++-
.../flink/table/catalog/FileCatalogStoreTest.java | 32 ++++++++++++++++++++++
2 files changed, 54 insertions(+), 1 deletion(-)
diff --git
a/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/catalog/FileCatalogStore.java
b/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/catalog/FileCatalogStore.java
index 50763a828ea..0e1fb69ee38 100644
---
a/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/catalog/FileCatalogStore.java
+++
b/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/catalog/FileCatalogStore.java
@@ -260,6 +260,27 @@ public class FileCatalogStore extends AbstractCatalogStore
{
}
private Path getCatalogPath(String catalogName) {
- return new Path(catalogStorePath, catalogName + FILE_EXTENSION);
+ Path catalogPath;
+ try {
+ catalogPath = new Path(catalogStorePath, catalogName +
FILE_EXTENSION);
+ } catch (Exception e) {
+ // e.g. catalogName embeds its own scheme-qualified URI (like
"file:///etc/passwd"),
+ // which Path may reject outright while merging it against
catalogStorePath.
+ throw new CatalogException(String.format("Invalid catalog name
'%s'.", catalogName), e);
+ }
+
+ // catalogName is caller-supplied and may try to escape
catalogStorePath, e.g. via ".."
+ // segments. Path's own resolution above already fully normalizes the
result (RFC 3986
+ // dot-segment removal), so checking that the *resolved* path's parent
is still
+ // catalogStorePath is sufficient to reject every variant of escape,
without needing to
+ // inspect catalogName itself.
+ if (!catalogStorePath.equals(catalogPath.getParent())) {
+ throw new CatalogException(
+ String.format(
+ "Invalid catalog name '%s'. It resolves to '%s',
which is outside of "
+ + "the catalog store directory '%s'.",
+ catalogName, catalogPath, catalogStorePath));
+ }
+ return catalogPath;
}
}
diff --git
a/flink-table/flink-table-api-java/src/test/java/org/apache/flink/table/catalog/FileCatalogStoreTest.java
b/flink-table/flink-table-api-java/src/test/java/org/apache/flink/table/catalog/FileCatalogStoreTest.java
index 011ce881bb8..e22f3c7d89c 100644
---
a/flink-table/flink-table-api-java/src/test/java/org/apache/flink/table/catalog/FileCatalogStoreTest.java
+++
b/flink-table/flink-table-api-java/src/test/java/org/apache/flink/table/catalog/FileCatalogStoreTest.java
@@ -106,6 +106,38 @@ class FileCatalogStoreTest {
assertThat(storedCatalogs.contains(DUMMY)).isTrue();
}
+ @Test
+ void testStoreCatalogRejectsPathTraversal() throws Exception {
+ CatalogStore catalogStore = initCatalogStore();
+ catalogStore.open();
+
+ // A malicious catalog name that, if not validated, resolves outside
of the
+ // catalog store directory:
tempDir/dummy-catalog-store/../escaped.yaml ->
+ // tempDir/escaped.yaml.
+ String maliciousName = "../escaped";
+ File escapedFile = tempDir.resolve("escaped" +
FileCatalogStore.FILE_EXTENSION).toFile();
+
+ assertThatThrownBy(() -> catalogStore.storeCatalog(maliciousName,
DUMMY_CATALOG))
+ .isInstanceOf(CatalogException.class);
+ assertThat(escapedFile).doesNotExist();
+ }
+
+ @Test
+ void testStoreCatalogRejectsAbsoluteSchemeOverride() throws Exception {
+ CatalogStore catalogStore = initCatalogStore();
+ catalogStore.open();
+
+ // A catalog name that embeds its own absolute file:// URI. Per RFC
3986 ยง5.3, resolving
+ // an absolute reference against a base URI discards the base
entirely, so if this isn't
+ // rejected, the catalog store directory is bypassed altogether.
+ File escapedFile = tempDir.resolve("escaped" +
FileCatalogStore.FILE_EXTENSION).toFile();
+ String maliciousName = "file://" +
escapedFile.getAbsolutePath().replace(".yaml", "");
+
+ assertThatThrownBy(() -> catalogStore.storeCatalog(maliciousName,
DUMMY_CATALOG))
+ .isInstanceOf(CatalogException.class);
+ assertThat(escapedFile).doesNotExist();
+ }
+
@Test
void testRemoveExisting() {
CatalogStore catalogStore = initCatalogStore();