Skip to content

Commit 04af2a6

Browse files
authored
fix: Block path traversal attacks via the deployment API for MLflow-Triton (#8665)
1 parent 0a98c75 commit 04af2a6

3 files changed

Lines changed: 83 additions & 11 deletions

File tree

deploy/mlflow-triton-plugin/mlflow_triton/deployments.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22

3-
# Copyright 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# Copyright 2021-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
44
#
55
# Redistribution and use in source and binary forms, with or without
66
# modification, are permitted provided that the following conditions
@@ -90,7 +90,7 @@ def create_deployment(self, name, model_uri, flavor=None, config=None):
9090
"""
9191
Deploy the model at the model_uri to the Triton model repo. Associated config.pbtxt and *labels* files will be deployed.
9292
93-
:param name: Name of the of the model
93+
:param name: Name of the model
9494
:param model_uri: Model uri in format model:/<model-name>/<version-or-stage>
9595
:param flavor: Flavor of the deployed model
9696
:param config: Configuration parameters
@@ -99,6 +99,9 @@ def create_deployment(self, name, model_uri, flavor=None, config=None):
9999
"""
100100
self._validate_flavor(flavor)
101101

102+
# Validate model name
103+
self._validate_model_name(name)
104+
102105
# Verify model does not already exist in Triton
103106
if self._model_exists(name):
104107
raise Exception(
@@ -513,6 +516,18 @@ def _validate_flavor(self, flavor):
513516
if flavor not in self.supported_flavors:
514517
raise Exception("{} model flavor not supported by Triton".format(flavor))
515518

519+
def _validate_model_name(self, name):
520+
# Check if the model name is empty or only contains whitespace, tabs, or newlines
521+
if name.strip() == "":
522+
raise Exception(
523+
"Model name cannot be empty. Please enter a valid name to deploy."
524+
)
525+
# Path traversal protection
526+
if "/" in name or name == "..":
527+
raise Exception(
528+
"Path traversal is not allowed in model's name: {}".format(name)
529+
)
530+
516531
def _model_exists(self, name):
517532
deploys = self.list_deployments()
518533
exists = False

qa/L0_mlflow/plugin_test.py

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/python
22

3-
# Copyright 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# Copyright 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
44
#
55
# Redistribution and use in source and binary forms, with or without
66
# modification, are permitted provided that the following conditions
@@ -33,7 +33,9 @@
3333
import json
3434
import unittest
3535

36+
import mlflow.onnx
3637
import numpy as np
38+
import onnx
3739
import test_util as tu
3840
from mlflow.deployments import get_deploy_client
3941

@@ -45,7 +47,7 @@ def setUp(self):
4547
def _validate_deployment(self, model_name):
4648
# create
4749
self.client_.create_deployment(
48-
model_name, "models:/{}/1".format(model_name), flavor="onnx"
50+
model_name, f"models:/{model_name}/1", flavor="onnx"
4951
)
5052

5153
# list
@@ -79,8 +81,6 @@ def _validate_deployment(self, model_name):
7981

8082
def test_onnx_flavor(self):
8183
# Log the ONNX model to MLFlow
82-
import mlflow.onnx
83-
import onnx
8484

8585
model = onnx.load(
8686
"./mlflow-triton-plugin/examples/onnx_float32_int32_int32/1/model.onnx"
@@ -92,8 +92,6 @@ def test_onnx_flavor(self):
9292

9393
def test_onnx_flavor_with_files(self):
9494
# Log the ONNX model and additional Triton config file to MLFlow
95-
import mlflow.onnx
96-
import onnx
9795

9896
model = onnx.load(
9997
"./mlflow-triton-plugin/examples/onnx_float32_int32_int32/1/model.onnx"
@@ -116,6 +114,65 @@ def test_onnx_flavor_with_files(self):
116114
filecmp.cmp(config_path, "./models/onnx_model_with_files/config.pbtxt")
117115
)
118116

117+
def test_model_name(self):
118+
EMPTY_MODEL_NAMES = [
119+
"",
120+
" ",
121+
" ",
122+
"\n",
123+
"\t",
124+
"\r",
125+
"\v",
126+
"\f",
127+
]
128+
INVALID_PATH_TRAVERSAL_NAMES = [
129+
"/opt/sys/",
130+
"../../etc/passwd",
131+
"../outside/repo",
132+
"test_models/../identity_py",
133+
"..",
134+
]
135+
VALID_MODEL_NAMES = [
136+
"model123",
137+
# "model OAI", TRI-769: Fix this test case
138+
"model.version1",
139+
"...",
140+
"..my_model",
141+
"model..1",
142+
"model....1",
143+
]
144+
145+
for model_name in EMPTY_MODEL_NAMES:
146+
model_uri = f"models:/{model_name}/1"
147+
with self.assertRaises(Exception) as e:
148+
self.client_.create_deployment(model_name, model_uri, flavor="onnx")
149+
self.assertIn(
150+
"Model name cannot be empty. Please enter a valid name to deploy.",
151+
str(e.exception),
152+
)
153+
154+
for model_name in INVALID_PATH_TRAVERSAL_NAMES:
155+
model_uri = f"models:/{model_name}/1"
156+
with self.assertRaises(Exception) as e:
157+
self.client_.create_deployment(model_name, model_uri, flavor="onnx")
158+
self.assertIn(
159+
f"Path traversal is not allowed in model's name: {model_name}",
160+
str(e.exception),
161+
)
162+
163+
for model_name in VALID_MODEL_NAMES:
164+
model = onnx.load(
165+
"./mlflow-triton-plugin/examples/onnx_float32_int32_int32/1/model.onnx"
166+
)
167+
168+
# Use a different name to ensure the plugin operates on correct model
169+
mlflow.onnx.log_model(
170+
model, "triton", registered_model_name=f"{model_name}"
171+
)
172+
173+
# Validate deployment functionalities - create, list, get, predict, delete
174+
self._validate_deployment(model_name)
175+
119176

120177
if __name__ == "__main__":
121178
unittest.main()

qa/L0_mlflow/test.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
# Copyright 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# Copyright 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
#
44
# Redistribution and use in source and binary forms, with or without
55
# modification, are permitted provided that the following conditions
@@ -187,7 +187,7 @@ if [ $? -ne 0 ]; then
187187
echo -e "\n***\n*** Python Test Failed\n***"
188188
RET=1
189189
else
190-
check_test_results $TEST_RESULT_FILE 2
190+
check_test_results $TEST_RESULT_FILE 3
191191
if [ $? -ne 0 ]; then
192192
cat $PY_LOG
193193
echo -e "\n***\n*** Test Result Verification Failed\n***"
@@ -256,7 +256,7 @@ if [ $? -ne 0 ]; then
256256
echo -e "\n***\n*** Python Test Failed\n***"
257257
RET=1
258258
else
259-
check_test_results $TEST_RESULT_FILE 2
259+
check_test_results $TEST_RESULT_FILE 3
260260
if [ $? -ne 0 ]; then
261261
cat $PY_LOG
262262
echo -e "\n***\n*** Test Result Verification Failed\n***"

0 commit comments

Comments
 (0)