|
1 | 1 | #!/usr/bin/env python |
2 | | -# Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +# Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
3 | 3 | # |
4 | 4 | # Redistribution and use in source and binary forms, with or without |
5 | 5 | # modification, are permitted provided that the following conditions |
@@ -288,5 +288,76 @@ def inference_helper(model_name, batch_size=1): |
288 | 288 | inference_helper(model_name="plan_zero_1_float32_int32", batch_size=8) |
289 | 289 |
|
290 | 290 |
|
| 291 | +class ModelNameValidationTest(unittest.TestCase): |
| 292 | + INVALID_TRAVERSAL_NAMES = [ |
| 293 | + "../etc", |
| 294 | + "a/../b", |
| 295 | + "../../etc/passwd", |
| 296 | + "../../../../etc", |
| 297 | + "model/..", |
| 298 | + "..", |
| 299 | + "/etc/passwd", |
| 300 | + "model/subdir", |
| 301 | + "model/", |
| 302 | + " ..", |
| 303 | + ".. ", |
| 304 | + ] |
| 305 | + |
| 306 | + def test_model_name_invalid_load(self): |
| 307 | + client = tritongrpcclient.InferenceServerClient("localhost:8001") |
| 308 | + for model_name in self.INVALID_TRAVERSAL_NAMES: |
| 309 | + with self.assertRaises(InferenceServerException) as cm: |
| 310 | + client.load_model(model_name) |
| 311 | + self.assertIn( |
| 312 | + "model name must not contain path traversal characters", |
| 313 | + str(cm.exception), |
| 314 | + f"Expected traversal rejection for model name: {model_name!r}", |
| 315 | + ) |
| 316 | + |
| 317 | + def test_model_name_empty_load(self): |
| 318 | + client = tritongrpcclient.InferenceServerClient("localhost:8001") |
| 319 | + with self.assertRaises(InferenceServerException) as cm: |
| 320 | + client.load_model("") |
| 321 | + self.assertIn("model name must not be empty", str(cm.exception)) |
| 322 | + |
| 323 | + def test_model_name_whitespace_only_load(self): |
| 324 | + client = tritongrpcclient.InferenceServerClient("localhost:8001") |
| 325 | + whitespace_names = [" ", " ", "\t", "\n", "\r", "\f", "\v", " \t \n "] |
| 326 | + for model_name in whitespace_names: |
| 327 | + with self.assertRaises(InferenceServerException) as cm: |
| 328 | + client.load_model(model_name) |
| 329 | + self.assertIn( |
| 330 | + "model name must not contain only whitespace", |
| 331 | + str(cm.exception), |
| 332 | + f"Expected whitespace-only rejection for model name: {model_name!r}", |
| 333 | + ) |
| 334 | + |
| 335 | + def test_model_name_invalid_unload(self): |
| 336 | + # Unload should not trigger traversal check |
| 337 | + client = tritongrpcclient.InferenceServerClient("localhost:8001") |
| 338 | + for model_name in self.INVALID_TRAVERSAL_NAMES: |
| 339 | + try: |
| 340 | + client.unload_model(model_name) |
| 341 | + except InferenceServerException as e: |
| 342 | + self.assertNotIn( |
| 343 | + "model name must not contain path traversal characters", |
| 344 | + str(e), |
| 345 | + f"Unload should not trigger traversal rejection for model name: {model_name!r}", |
| 346 | + ) |
| 347 | + |
| 348 | + def test_model_name_valid(self): |
| 349 | + """Verify that a syntactically valid model name is not rejected by |
| 350 | + the traversal check -- it should fail with a model not found error instead.""" |
| 351 | + client = tritongrpcclient.InferenceServerClient("localhost:8001") |
| 352 | + with self.assertRaises(InferenceServerException) as cm: |
| 353 | + client.load_model("nonexistent_model") |
| 354 | + self.assertNotIn( |
| 355 | + "path traversal characters", |
| 356 | + str(cm.exception), |
| 357 | + "Valid model name should not trigger path traversal rejection", |
| 358 | + ) |
| 359 | + self.assertIn("failed to poll from model repository", str(cm.exception)) |
| 360 | + |
| 361 | + |
291 | 362 | if __name__ == "__main__": |
292 | 363 | unittest.main() |
0 commit comments