This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 20ce15e04a5 Fix fail-open authorization for pipe config plans (#18488)
20ce15e04a5 is described below
commit 20ce15e04a56f37ad7721c95f023a3adf3f467a0
Author: Caideyipi <[email protected]>
AuthorDate: Mon Aug 24 12:28:58 2026 +0800
Fix fail-open authorization for pipe config plans (#18488)
* Fix fail-open authorization for pipe config plans
* Fix pipe device deletion authorization
* Harden pipe plugin path handling
* Fix pipe plugin metadata test import
* Deduplicate pipe plugin path validation
---
.../iotdb/pipe/it/single/IoTDBPipeReceiverIT.java | 19 ++++
.../receiver/protocol/IoTDBConfigNodeReceiver.java | 21 +++-
.../pipe/agent/plugin/meta/PipePluginMeta.java | 13 ++-
.../service/PipePluginExecutableManager.java | 60 +++++++----
.../org/apache/iotdb/commons/utils/FileUtils.java | 16 +++
.../service/PipePluginExecutableManagerTest.java | 111 +++++++++++++++++++++
.../pipe/plugin/meta/PipePluginMetaTest.java | 17 ++++
7 files changed, 234 insertions(+), 23 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeReceiverIT.java
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeReceiverIT.java
index 5ca62ebb15f..136943f00b0 100644
---
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeReceiverIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeReceiverIT.java
@@ -24,12 +24,15 @@ import
org.apache.iotdb.commons.client.property.ThriftClientProperty;
import org.apache.iotdb.commons.conf.CommonDescriptor;
import org.apache.iotdb.commons.conf.IoTDBConstant;
import org.apache.iotdb.commons.path.MeasurementPath;
+import org.apache.iotdb.commons.pipe.agent.plugin.meta.PipePluginMeta;
import org.apache.iotdb.commons.pipe.sink.client.IoTDBSyncClient;
import
org.apache.iotdb.commons.pipe.sink.payload.thrift.common.PipeTransferHandshakeConstant;
import
org.apache.iotdb.commons.pipe.sink.payload.thrift.request.IoTDBSinkRequestVersion;
import
org.apache.iotdb.commons.pipe.sink.payload.thrift.request.PipeRequestType;
+import
org.apache.iotdb.confignode.consensus.request.write.pipe.plugin.CreatePipePluginPlan;
import
org.apache.iotdb.confignode.manager.pipe.sink.payload.PipeTransferConfigNodeHandshakeV1Req;
import
org.apache.iotdb.confignode.manager.pipe.sink.payload.PipeTransferConfigNodeHandshakeV2Req;
+import
org.apache.iotdb.confignode.manager.pipe.sink.payload.PipeTransferConfigPlanReq;
import
org.apache.iotdb.db.pipe.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV1Req;
import
org.apache.iotdb.db.pipe.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV2Req;
import
org.apache.iotdb.db.pipe.sink.payload.evolvable.request.PipeTransferTabletRawReq;
@@ -56,6 +59,7 @@ import
org.apache.iotdb.service.rpc.thrift.TSyncTransportMetaInfo;
import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.external.commons.io.FileUtils;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.tsfile.utils.Binary;
import org.apache.tsfile.utils.ReadWriteIOUtils;
import org.apache.tsfile.write.record.Tablet;
import org.apache.tsfile.write.schema.MeasurementSchema;
@@ -256,6 +260,21 @@ public class IoTDBPipeReceiverIT {
Assert.assertNotEquals(
TSStatusCode.NOT_LOGIN.getStatusCode(),
client.pipeTransfer(buildEmptyConfigPlanReq()).getStatus().getCode());
+ Assert.assertEquals(
+ TSStatusCode.NO_PERMISSION.getStatusCode(),
+ client
+ .pipeTransfer(
+ PipeTransferConfigPlanReq.toTPipeTransferReq(
+ new CreatePipePluginPlan(
+ new PipePluginMeta(
+ "receiver-security-test-plugin",
+ "attacker.Plugin",
+ false,
+ "attacker.jar",
+ "deadbeef"),
+ new Binary(new byte[] {1}))))
+ .getStatus()
+ .getCode());
}
}
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/protocol/IoTDBConfigNodeReceiver.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/protocol/IoTDBConfigNodeReceiver.java
index 281867d0ec5..d76a1d2943c 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/protocol/IoTDBConfigNodeReceiver.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/protocol/IoTDBConfigNodeReceiver.java
@@ -88,6 +88,7 @@ import
org.apache.iotdb.confignode.consensus.request.write.table.view.SetViewCom
import
org.apache.iotdb.confignode.consensus.request.write.table.view.SetViewPropertiesPlan;
import
org.apache.iotdb.confignode.consensus.request.write.template.CommitSetSchemaTemplatePlan;
import
org.apache.iotdb.confignode.consensus.request.write.template.CreateSchemaTemplatePlan;
+import
org.apache.iotdb.confignode.consensus.request.write.template.DropSchemaTemplatePlan;
import
org.apache.iotdb.confignode.consensus.request.write.template.ExtendSchemaTemplatePlan;
import
org.apache.iotdb.confignode.consensus.request.write.trigger.DeleteTriggerInTablePlan;
import
org.apache.iotdb.confignode.consensus.request.write.trigger.UpdateTriggerStateInTablePlan;
@@ -413,6 +414,12 @@ public class IoTDBConfigNodeReceiver extends
IoTDBFileReceiver {
case CreateSchemaTemplate:
templateName = ((CreateSchemaTemplatePlan)
plan).getTemplate().getName();
return checkGlobalStatus(userEntity, PrivilegeType.SYSTEM,
templateName, true);
+ case DropSchemaTemplate:
+ return checkGlobalStatus(
+ userEntity,
+ PrivilegeType.SYSTEM,
+ ((DropSchemaTemplatePlan) plan).getTemplateName(),
+ true);
case CommitSetSchemaTemplate:
templateName = ((CommitSetSchemaTemplatePlan) plan).getName();
return checkGlobalStatus(userEntity, PrivilegeType.SYSTEM,
templateName, true);
@@ -552,6 +559,12 @@ public class IoTDBConfigNodeReceiver extends
IoTDBFileReceiver {
PrivilegeType.DELETE,
((CommitDeleteTablePlan) plan).getDatabase(),
((CommitDeleteTablePlan) plan).getTableName());
+ case PipeDeleteDevices:
+ return checkTableStatus(
+ userEntity,
+ PrivilegeType.DELETE,
+ ((PipeDeleteDevicesPlan) plan).getDatabase(),
+ ((PipeDeleteDevicesPlan) plan).getTableName());
case GrantRole:
case GrantUser:
case RevokeUser:
@@ -747,7 +760,7 @@ public class IoTDBConfigNodeReceiver extends
IoTDBFileReceiver {
return checkGlobalStatus(
userEntity, PrivilegeType.MANAGE_ROLE, ((AuthorPlan)
plan).getRoleName(), true);
default:
- return StatusUtils.OK;
+ return RpcUtils.getStatus(TSStatusCode.NO_PERMISSION);
}
}
@@ -1204,10 +1217,14 @@ public class IoTDBConfigNodeReceiver extends
IoTDBFileReceiver {
.getPermissionManager()
.operatePermission((AuthorPlan) plan,
shouldMarkAsPipeRequest.get());
case CreateSchemaTemplate:
- default:
+ case DropSchemaTemplate:
+ // Only explicitly supported config-region pipe plans may be written
to consensus. New plan
+ // types must be added to an explicit case after their authorization
is implemented.
return configManager
.getConsensusManager()
.write(shouldMarkAsPipeRequest.get() ? new PipeEnrichedPlan(plan)
: plan);
+ default:
+ return RpcUtils.getStatus(TSStatusCode.NO_PERMISSION);
}
}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/meta/PipePluginMeta.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/meta/PipePluginMeta.java
index 19f6863be3c..dce904b532b 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/meta/PipePluginMeta.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/meta/PipePluginMeta.java
@@ -19,6 +19,8 @@
package org.apache.iotdb.commons.pipe.agent.plugin.meta;
+import org.apache.iotdb.commons.utils.FileUtils;
+
import org.apache.tsfile.utils.PublicBAOS;
import org.apache.tsfile.utils.ReadWriteIOUtils;
@@ -26,6 +28,7 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
+import java.util.Locale;
import java.util.Objects;
public class PipePluginMeta {
@@ -52,22 +55,24 @@ public class PipePluginMeta {
String jarName,
String jarMD5,
String pluginLoadingExceptionMessage) {
- this.pluginName = Objects.requireNonNull(pluginName).toUpperCase();
+ this.pluginName =
+
FileUtils.validatePathSegment(Objects.requireNonNull(pluginName)).toUpperCase(Locale.ROOT);
this.className = Objects.requireNonNull(className);
this.isBuiltin = isBuiltin;
if (isBuiltin) {
- this.jarName = jarName;
+ this.jarName = jarName == null ? null :
FileUtils.validatePathSegment(jarName);
this.jarMD5 = jarMD5;
} else {
- this.jarName = Objects.requireNonNull(jarName);
+ this.jarName =
FileUtils.validatePathSegment(Objects.requireNonNull(jarName));
this.jarMD5 = Objects.requireNonNull(jarMD5);
}
this.pluginLoadingExceptionMessage = pluginLoadingExceptionMessage;
}
public PipePluginMeta(String pluginName, String className) {
- this.pluginName = Objects.requireNonNull(pluginName).toUpperCase();
+ this.pluginName =
+
FileUtils.validatePathSegment(Objects.requireNonNull(pluginName)).toUpperCase(Locale.ROOT);
this.className = Objects.requireNonNull(className);
this.isBuiltin = true;
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginExecutableManager.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginExecutableManager.java
index 42b559f30d5..fade4351569 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginExecutableManager.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginExecutableManager.java
@@ -36,6 +36,7 @@ import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Locale;
public class PipePluginExecutableManager extends ExecutableManager {
@@ -46,7 +47,7 @@ public class PipePluginExecutableManager extends
ExecutableManager {
}
public boolean isLocalJarMatched(PipePluginMeta pipePluginMeta) throws
PipeException {
- final String pluginName = pipePluginMeta.getPluginName();
+ final String pluginName =
FileUtils.validatePathSegment(pipePluginMeta.getPluginName());
final String md5FilePath = pluginName + ".txt";
if (hasFileUnderTemporaryRoot(md5FilePath)) {
@@ -94,40 +95,33 @@ public class PipePluginExecutableManager extends
ExecutableManager {
}
public boolean hasPluginFileUnderInstallDir(String pluginName, String
fileName) {
- return Files.exists(Paths.get(getPluginInstallPathV2(pluginName,
fileName)));
+ return Files.exists(getPluginInstallPathV2Path(pluginName, fileName));
}
public String getPluginsDirPath(String pluginName) {
- return this.libRoot + File.separator + INSTALL_DIR + File.separator +
pluginName.toUpperCase();
+ return getPluginDirectoryPath(pluginName).toString();
}
public void removePluginFileUnderLibRoot(String pluginName, String fileName)
throws IOException {
- String pluginPath = getPluginInstallPathV2(pluginName, fileName);
- Path path = Paths.get(pluginPath);
+ final Path path = getPluginInstallPathV2Path(pluginName, fileName);
Files.deleteIfExists(path);
Files.deleteIfExists(path.getParent());
}
public String getPluginInstallPathV2(String pluginName, String fileName) {
- return this.libRoot
- + File.separator
- + INSTALL_DIR
- + File.separator
- + pluginName.toUpperCase()
- + File.separator
- + fileName;
+ return getPluginInstallPathV2Path(pluginName, fileName).toString();
}
public String getPluginInstallPathV1(String fileName) {
- return this.libRoot + File.separator + INSTALL_DIR + File.separator +
fileName;
+ return resolvePathUnderDirectory(getInstallDirectoryPath(),
fileName).toString();
}
public void linkExistedPlugin(
final String oldPluginName, final String newPluginName, final String
fileName)
throws IOException {
FileUtils.createHardLink(
- new File(getPluginsDirPath(oldPluginName), fileName),
- new File(getPluginsDirPath(newPluginName), fileName));
+ getPluginInstallPathV2Path(oldPluginName, fileName).toFile(),
+ getPluginInstallPathV2Path(newPluginName, fileName).toFile());
}
/**
@@ -138,7 +132,39 @@ public class PipePluginExecutableManager extends
ExecutableManager {
*/
public void savePluginToInstallDir(ByteBuffer byteBuffer, String pluginName,
String fileName)
throws IOException {
- String destination = getPluginInstallPathV2(pluginName, fileName);
- saveToDir(byteBuffer, destination);
+ saveToDir(byteBuffer, getPluginInstallPathV2Path(pluginName,
fileName).toString());
+ }
+
+ private Path getPluginInstallPathV2Path(final String pluginName, final
String fileName) {
+ return resolvePathUnderDirectory(getPluginDirectoryPath(pluginName),
fileName);
+ }
+
+ private Path getPluginDirectoryPath(final String pluginName) {
+ final String validatedPluginName =
FileUtils.validatePathSegment(pluginName);
+ return resolvePathUnderDirectory(
+ getInstallDirectoryPath(),
validatedPluginName.toUpperCase(Locale.ROOT));
+ }
+
+ private Path getInstallDirectoryPath() {
+ return Paths.get(libRoot, INSTALL_DIR).toAbsolutePath().normalize();
+ }
+
+ /**
+ * Resolves a single untrusted path segment below {@code baseDirectory}.
+ *
+ * <p>The segment validation rejects separators and dot segments, while the
normalized containment
+ * check remains as a defense in depth for absolute paths and future callers.
+ */
+ private Path resolvePathUnderDirectory(final Path baseDirectory, final
String pathSegment) {
+ FileUtils.validatePathSegment(pathSegment);
+
+ final Path normalizedBaseDirectory =
baseDirectory.toAbsolutePath().normalize();
+ final Path normalizedTargetPath =
+
normalizedBaseDirectory.resolve(pathSegment).toAbsolutePath().normalize();
+ if (!normalizedTargetPath.startsWith(normalizedBaseDirectory)) {
+ throw new IllegalArgumentException(
+ String.format(PipeMessages.ILLEGAL_FILENAME_PATH_TRAVERSAL,
pathSegment));
+ }
+ return normalizedTargetPath;
}
}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/FileUtils.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/FileUtils.java
index a0611a8cfc6..d5b1b76a7eb 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/FileUtils.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/FileUtils.java
@@ -42,6 +42,7 @@ import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.text.CharacterIterator;
@@ -659,4 +660,19 @@ public class FileUtils {
}
return null;
}
+
+ /**
+ * Validates a path segment before it is used to construct a path.
+ *
+ * <p>In addition to the application-level checks above, constructing a
{@link Path} validates
+ * platform-specific path syntax (for example, NUL characters on Unix).
+ */
+ public static String validatePathSegment(final String pathSegment) {
+ final String pathError = getIllegalError4Directory(pathSegment);
+ if (pathError != null) {
+ throw new IllegalArgumentException(pathError);
+ }
+ Paths.get(pathSegment);
+ return pathSegment;
+ }
}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginExecutableManagerTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginExecutableManagerTest.java
new file mode 100644
index 00000000000..129c850d99e
--- /dev/null
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginExecutableManagerTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.pipe.agent.plugin.service;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.stream.Stream;
+
+public class PipePluginExecutableManagerTest {
+
+ @Test
+ public void testPluginPathsAreConfinedToInstallDirectory() throws Exception {
+ final Path root =
Files.createTempDirectory("pipe-plugin-executable-manager-test");
+ final Path temporaryLibRoot = root.resolve("temporary");
+ final Path libRoot = root.resolve("lib");
+ final PipePluginExecutableManager manager =
+ new PipePluginExecutableManager(temporaryLibRoot.toString(),
libRoot.toString());
+ final String traversalFileName = ".." + File.separator + ".." +
File.separator + "outside.jar";
+ final Path outsideFile = libRoot.resolve("outside.jar");
+ final byte[] pluginContent = "plugin".getBytes(StandardCharsets.UTF_8);
+
+ try {
+ Assert.assertEquals(
+ libRoot
+ .toAbsolutePath()
+ .normalize()
+ .resolve("install")
+ .resolve("TEST-PLUGIN")
+ .resolve("test.jar"),
+ Path.of(manager.getPluginInstallPathV2("test-plugin", "test.jar")));
+ manager.savePluginToInstallDir(ByteBuffer.wrap(pluginContent),
"test-plugin", "test.jar");
+ Assert.assertArrayEquals(
+ pluginContent,
+
Files.readAllBytes(Path.of(manager.getPluginInstallPathV2("test-plugin",
"test.jar"))));
+
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () -> manager.getPluginsDirPath(".." + File.separator + "outside"));
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () -> manager.getPluginsDirPath(".." + otherFileSeparator() +
"outside"));
+ Assert.assertThrows(
+ IllegalArgumentException.class, () ->
manager.getPluginInstallPathV1(traversalFileName));
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ manager.savePluginToInstallDir(
+ ByteBuffer.wrap(new byte[] {1}), "plugin",
traversalFileName));
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ manager.savePluginToInstallDir(
+ ByteBuffer.wrap(new byte[] {1}),
+ ".." + otherFileSeparator() + "plugin",
+ "test.jar"));
+ Assert.assertFalse(Files.exists(outsideFile));
+
+ Files.createDirectories(outsideFile.getParent());
+ Files.write(outsideFile, "preserve".getBytes(StandardCharsets.UTF_8));
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () -> manager.removePluginFileUnderLibRoot("plugin",
traversalFileName));
+ Assert.assertArrayEquals(
+ "preserve".getBytes(StandardCharsets.UTF_8),
Files.readAllBytes(outsideFile));
+
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () -> manager.linkExistedPlugin("source", "target",
traversalFileName));
+ } finally {
+ deleteRecursively(root);
+ }
+ }
+
+ private static String otherFileSeparator() {
+ return File.separatorChar == '/' ? "\\" : "/";
+ }
+
+ private static void deleteRecursively(final Path path) throws IOException {
+ try (final Stream<Path> stream = Files.walk(path)) {
+ for (final Path subPath :
+ (Iterable<Path>) stream.sorted(Comparator.reverseOrder())::iterator)
{
+ Files.deleteIfExists(subPath);
+ }
+ }
+ }
+}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/plugin/meta/PipePluginMetaTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/plugin/meta/PipePluginMetaTest.java
index b5b854adc4d..4aa8c5739a9 100644
---
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/plugin/meta/PipePluginMetaTest.java
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/plugin/meta/PipePluginMetaTest.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.commons.pipe.plugin.meta;
import org.apache.iotdb.commons.pipe.agent.plugin.builtin.BuiltinPipePlugin;
import
org.apache.iotdb.commons.pipe.agent.plugin.meta.ConfigNodePipePluginMetaKeeper;
import
org.apache.iotdb.commons.pipe.agent.plugin.meta.DataNodePipePluginMetaKeeper;
+import org.apache.iotdb.commons.pipe.agent.plugin.meta.PipePluginMeta;
import org.junit.Assert;
import org.junit.Test;
@@ -58,4 +59,20 @@ public class PipePluginMetaTest {
BuiltinPipePlugin.IOTDB_EXTRACTOR.getPipePluginClass(),
keeper.getBuiltinPluginClass(BuiltinPipePlugin.IOTDB_EXTRACTOR.getPipePluginName()));
}
+
+ @Test
+ public void testRejectPathTraversalInPluginMetadata() {
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () -> new PipePluginMeta("../plugin", "test.Plugin", false,
"test.jar", "md5"));
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () -> new PipePluginMeta("plugin", "test.Plugin", false,
"../test.jar", "md5"));
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () -> new PipePluginMeta("plugin", "test.Plugin", false,
"..\\test.jar", "md5"));
+ Assert.assertThrows(
+ IllegalArgumentException.class,
+ () -> new PipePluginMeta("plugin\0", "test.Plugin", false, "test.jar",
"md5"));
+ }
}