Skip to content

Commit a04d5db

Browse files
authored
Merge branch 'master' into Add-isort-functionality
2 parents 25c5a4c + f472320 commit a04d5db

File tree

21 files changed

+2302
-179
lines changed

21 files changed

+2302
-179
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ src/sagemaker/modules/train/container_drivers/sourcecode.json
3737
src/sagemaker/modules/train/container_drivers/distributed.json
3838
tests/data/**/_repack_model.py
3939
tests/data/experiment/sagemaker-dev-1.0.tar.gz
40-
src/sagemaker/serve/tmp_workspace
40+
src/sagemaker/serve/tmp_workspace
41+
test-examples

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
# Changelog
22

3+
## v2.248.1 (2025-07-16)
4+
5+
### Bug Fixes and Other Changes
6+
7+
* Nova training support
8+
9+
## v2.248.0 (2025-07-15)
10+
11+
### Features
12+
13+
* integrate amtviz for visualization of tuning jobs
14+
15+
### Bug Fixes and Other Changes
16+
17+
* build(deps): bump requests in /tests/data/serve_resources/mlflow/pytorch
18+
* build(deps): bump protobuf from 4.25.5 to 4.25.8 in /requirements/extras
19+
* build(deps): bump mlflow in /tests/data/serve_resources/mlflow/xgboost
20+
* build(deps): bump torch in /tests/data/modules/script_mode
21+
* sanitize git clone repo input url
22+
* Adding Hyperpod feature to enable hyperpod telemetry
23+
* Adding Hyperpod feature to enable hyperpod telemetry
24+
* Bump SMD version to enable custom workflow deployment.
25+
* Update TF DLC python version to py312
26+
* update image_uri_configs 07-04-2025 07:18:27 PST
27+
* update image_uri_configs 06-26-2025 07:18:35 PST
28+
* relax protobuf to <6.32
29+
330
## v2.247.1 (2025-06-23)
431

532
### Bug Fixes and Other Changes

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.247.2.dev0
1+
2.248.2.dev0

requirements/extras/test_requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ PyYAML>=6.0.1
3232
xgboost>=1.6.2,<=1.7.6
3333
pillow>=10.0.1,<=11
3434
opentelemetry-proto==1.27.0
35-
protobuf==4.25.5
35+
protobuf==4.25.8
3636
tensorboard>=2.16.2,<=2.18.0
3737
transformers==4.48.0
3838
sentencepiece==0.1.99

src/sagemaker/estimator.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,30 @@ def _json_encode_hyperparameters(hyperparameters: Dict[str, Any]) -> Dict[str, A
905905
}
906906
return hyperparameters
907907

908+
@staticmethod
909+
def _nova_encode_hyperparameters(hyperparameters: Dict[str, Any]) -> Dict[str, Any]:
910+
"""Applies JSON encoding for Nova job hyperparameters, preserving string values.
911+
912+
For Nova jobs, string values should not be JSON-encoded.
913+
914+
Args:
915+
hyperparameters (dict): Dictionary of hyperparameters.
916+
917+
Returns:
918+
dict: Dictionary with encoded hyperparameters.
919+
"""
920+
current_hyperparameters = hyperparameters
921+
if current_hyperparameters is not None:
922+
hyperparameters = {}
923+
for k, v in current_hyperparameters.items():
924+
if is_pipeline_variable(v):
925+
hyperparameters[str(k)] = v.to_string()
926+
elif isinstance(v, str):
927+
hyperparameters[str(k)] = v
928+
else:
929+
hyperparameters[str(k)] = json.dumps(v)
930+
return hyperparameters
931+
908932
def _prepare_for_training(self, job_name=None):
909933
"""Set any values in the estimator that need to be set before training.
910934
@@ -938,7 +962,11 @@ def _prepare_for_training(self, job_name=None):
938962
self.source_dir = updated_paths["source_dir"]
939963
self.dependencies = updated_paths["dependencies"]
940964

941-
if self.source_dir or self.entry_point or self.dependencies:
965+
if (
966+
self.source_dir
967+
or self.entry_point
968+
or (self.dependencies and len(self.dependencies) > 0)
969+
):
942970
# validate source dir will raise a ValueError if there is something wrong with
943971
# the source directory. We are intentionally not handling it because this is a
944972
# critical error.
@@ -3579,7 +3607,11 @@ def __init__(
35793607
git_config=git_config,
35803608
enable_network_isolation=enable_network_isolation,
35813609
)
3582-
if not is_pipeline_variable(entry_point) and entry_point.startswith("s3://"):
3610+
if (
3611+
not is_pipeline_variable(entry_point)
3612+
and entry_point is not None
3613+
and entry_point.startswith("s3://")
3614+
):
35833615
raise ValueError(
35843616
"Invalid entry point script: {}. Must be a path to a local file.".format(
35853617
entry_point
@@ -3599,6 +3631,7 @@ def __init__(
35993631
self.checkpoint_s3_uri = checkpoint_s3_uri
36003632
self.checkpoint_local_path = checkpoint_local_path
36013633
self.enable_sagemaker_metrics = enable_sagemaker_metrics
3634+
self.is_nova_job = kwargs.get("is_nova_job", False)
36023635

36033636
def _prepare_for_training(self, job_name=None):
36043637
"""Set hyperparameters needed for training. This method will also validate ``source_dir``.
@@ -3713,7 +3746,10 @@ def _model_entry_point(self):
37133746

37143747
def set_hyperparameters(self, **kwargs):
37153748
"""Escapes the dict argument as JSON, updates the private hyperparameter attribute."""
3716-
self._hyperparameters.update(EstimatorBase._json_encode_hyperparameters(kwargs))
3749+
if self.is_nova_job:
3750+
self._hyperparameters.update(EstimatorBase._nova_encode_hyperparameters(kwargs))
3751+
else:
3752+
self._hyperparameters.update(EstimatorBase._json_encode_hyperparameters(kwargs))
37173753

37183754
def hyperparameters(self):
37193755
"""Returns the hyperparameters as a dictionary to use for training.
@@ -3724,7 +3760,10 @@ def hyperparameters(self):
37243760
Returns:
37253761
dict[str, str]: The hyperparameters.
37263762
"""
3727-
return EstimatorBase._json_encode_hyperparameters(self._hyperparameters)
3763+
if self.is_nova_job:
3764+
return EstimatorBase._nova_encode_hyperparameters(self._hyperparameters)
3765+
else:
3766+
return EstimatorBase._json_encode_hyperparameters(self._hyperparameters)
37283767

37293768
@classmethod
37303769
def _prepare_init_params_from_job_description(cls, job_details, model_channel_name=None):

src/sagemaker/fw_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,7 @@ def validate_torch_distributed_distribution(
10631063
)
10641064

10651065
# Check entry point type
1066-
if not entry_point.endswith(".py"):
1066+
if entry_point is not None and not entry_point.endswith(".py"):
10671067
err_msg += (
10681068
"Unsupported entry point type for the distribution torch_distributed.\n"
10691069
"Only python programs (*.py) are supported."

src/sagemaker/git_utils.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,78 @@
1414
from __future__ import absolute_import
1515

1616
import os
17-
from pathlib import Path
17+
import re
1818
import subprocess
1919
import tempfile
2020
import warnings
21+
from pathlib import Path
22+
from urllib.parse import urlparse
23+
2124
import six
2225
from six.moves import urllib
2326

2427

28+
def _sanitize_git_url(repo_url):
29+
"""Sanitize Git repository URL to prevent URL injection attacks.
30+
31+
Args:
32+
repo_url (str): The Git repository URL to sanitize
33+
34+
Returns:
35+
str: The sanitized URL
36+
37+
Raises:
38+
ValueError: If the URL contains suspicious patterns that could indicate injection
39+
"""
40+
at_count = repo_url.count("@")
41+
42+
if repo_url.startswith("git@"):
43+
# git@ format requires exactly one @
44+
if at_count != 1:
45+
raise ValueError("Invalid SSH URL format: git@ URLs must have exactly one @ symbol")
46+
elif repo_url.startswith("ssh://"):
47+
# ssh:// format can have 0 or 1 @ symbols
48+
if at_count > 1:
49+
raise ValueError("Invalid SSH URL format: multiple @ symbols detected")
50+
elif repo_url.startswith("https://") or repo_url.startswith("http://"):
51+
# HTTPS format allows 0 or 1 @ symbols
52+
if at_count > 1:
53+
raise ValueError("Invalid HTTPS URL format: multiple @ symbols detected")
54+
55+
# Check for invalid characters in the URL before parsing
56+
# These characters should not appear in legitimate URLs
57+
invalid_chars = ["<", ">", "[", "]", "{", "}", "\\", "^", "`", "|"]
58+
for char in invalid_chars:
59+
if char in repo_url:
60+
raise ValueError("Invalid characters in hostname")
61+
62+
try:
63+
parsed = urlparse(repo_url)
64+
65+
# Check for suspicious characters in hostname that could indicate injection
66+
if parsed.hostname:
67+
# Check for URL-encoded characters that might be used for obfuscation
68+
suspicious_patterns = ["%25", "%40", "%2F", "%3A"] # encoded %, @, /, :
69+
for pattern in suspicious_patterns:
70+
if pattern in parsed.hostname.lower():
71+
raise ValueError(f"Suspicious URL encoding detected in hostname: {pattern}")
72+
73+
# Validate that the hostname looks legitimate
74+
if not re.match(r"^[a-zA-Z0-9.-]+$", parsed.hostname):
75+
raise ValueError("Invalid characters in hostname")
76+
77+
except Exception as e:
78+
if isinstance(e, ValueError):
79+
raise
80+
raise ValueError(f"Failed to parse URL: {str(e)}")
81+
else:
82+
raise ValueError(
83+
"Unsupported URL scheme: only https://, http://, git@, and ssh:// are allowed"
84+
)
85+
86+
return repo_url
87+
88+
2589
def git_clone_repo(git_config, entry_point, source_dir=None, dependencies=None):
2690
"""Git clone repo containing the training code and serving code.
2791
@@ -87,6 +151,10 @@ def git_clone_repo(git_config, entry_point, source_dir=None, dependencies=None):
87151
if entry_point is None:
88152
raise ValueError("Please provide an entry point.")
89153
_validate_git_config(git_config)
154+
155+
# SECURITY: Sanitize the repository URL to prevent injection attacks
156+
git_config["repo"] = _sanitize_git_url(git_config["repo"])
157+
90158
dest_dir = tempfile.mkdtemp()
91159
_generate_and_run_clone_command(git_config, dest_dir)
92160

src/sagemaker/modules/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
os.path.dirname(os.path.abspath(__file__)), "train/container_drivers"
2626
)
2727

28+
SM_RECIPE = "recipe"
29+
SM_RECIPE_YAML = "recipe.yaml"
30+
SM_RECIPE_CONTAINER_PATH = f"/opt/ml/input/data/recipe/{SM_RECIPE_YAML}"
31+
2832
SOURCE_CODE_JSON = "sourcecode.json"
2933
DISTRIBUTED_JSON = "distributed.json"
3034
TRAIN_SCRIPT = "sm_train.sh"

0 commit comments

Comments
 (0)