From 77d1514e7d11bad69533d6965787b40054b937ed Mon Sep 17 00:00:00 2001 From: bikegeek Date: Wed, 29 Jul 2026 15:06:11 -0600 Subject: [PATCH 1/4] Issue #457 replace original regex with one that produces same results and addresses a potential Denial of Service as a result of polynomial runtime due to backtracking. This newer regular expression also finds matches in fewer steps. --- metcalcpy/util/read_env_vars_in_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metcalcpy/util/read_env_vars_in_config.py b/metcalcpy/util/read_env_vars_in_config.py index 5c96cfaf..3b39b44e 100644 --- a/metcalcpy/util/read_env_vars_in_config.py +++ b/metcalcpy/util/read_env_vars_in_config.py @@ -32,7 +32,7 @@ def parse_config(path=None, data=None, tag='!ENV',logger=None): :param str tag: the tag to look for """ # pattern for global vars: look for ${word} - pattern = re.compile(r'.*?\${(\w+)}.*?') + pattern = re.compile(r'\$\{([^}^{]+)\}') loader = yaml.SafeLoader # the tag will be used to mark where to start searching for the pattern From 30696e3286ac50684c637303cf12ee0807b215be Mon Sep 17 00:00:00 2001 From: bikegeek Date: Wed, 29 Jul 2026 15:08:33 -0600 Subject: [PATCH 2/4] Config file used in testing the read_env_vars_in_config.py changes --- test/data/bad_input.yaml | 1 + 1 file changed, 1 insertion(+) create mode 100644 test/data/bad_input.yaml diff --git a/test/data/bad_input.yaml b/test/data/bad_input.yaml new file mode 100644 index 00000000..e95f088d --- /dev/null +++ b/test/data/bad_input.yaml @@ -0,0 +1 @@ +malformed: !env '${USER}' From 51cf853e0ee54ebdb5a17bde5293e69f7a688803 Mon Sep 17 00:00:00 2001 From: bikegeek Date: Wed, 29 Jul 2026 15:09:01 -0600 Subject: [PATCH 3/4] Tests to verify changes to regex in read_env_vars_in_config produce the expected results --- test/test_read_env_vars_in_config.py | 64 ++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 test/test_read_env_vars_in_config.py diff --git a/test/test_read_env_vars_in_config.py b/test/test_read_env_vars_in_config.py new file mode 100644 index 00000000..fc3b27de --- /dev/null +++ b/test/test_read_env_vars_in_config.py @@ -0,0 +1,64 @@ +import os +import re +import yaml.constructor +from metcalcpy.util import read_env_vars_in_config as read_env +import pytest + +def test_with_yaml_string(): + """ + Pass in a YAML key:value string + """ + + os.environ['USER'] ='usr' + os.environ['SRCDIR'] = 'HOME' + os.environ['DATE'] = "20260729" + valid_yaml_str = "some_value: !ENV '${USER}/${SRCDIR}/data/${DATE}'" + valid_expected = os.environ['USER'] + '/' + \ + os.environ['SRCDIR'] + '/data/' + os.environ['DATE'] + + # test a valid entry + returned = read_env.parse_config(data=valid_yaml_str) + + print(f"expected: {returned}\n") + assert returned['some_value'] == valid_expected + + # incorrect tag + incorrect_tag_yaml_str = "some_value: !env '${HOME}'" + with pytest.raises(yaml.constructor.ConstructorError): + _ = read_env.parse_config(data=incorrect_tag_yaml_str) + + # missing braces + missing_brace_yaml_str = "some_value: !env '$HOME'" + with pytest.raises(yaml.constructor.ConstructorError): + _ = read_env.parse_config(data=missing_brace_yaml_str) + + # if no env var set, will return the NAME of the env_var + no_env_var_yaml_str = "some_value: !ENV '${DOESNT_EXIST}'" + _ = read_env.parse_config(data=no_env_var_yaml_str) + expected = 'DOESNT_EXIST' + assert _['some_value'] == expected + + # no env used, return the same yaml str + plain_yaml_str = "some_value: /home/username_1/data" + plain_yaml_dict = {'some_value': '/home/username_1/data'} + _ = read_env.parse_config(data=plain_yaml_str) + assert _['some_value'] == plain_yaml_dict['some_value'] + +def test_yaml_file(): + """ + Test parse_config by passing in a yaml config file. Not necessary + to test all the same as the above test, simply testing that a specified yaml + config file works. + """ + + # test with incorrect tag value (!env instead of !ENV) + cwd = os.getcwd() + print(f"current working dir: {cwd}") + yaml_file = os.path.join(cwd, './data/bad_input.yaml') + with pytest.raises(yaml.constructor.ConstructorError): + _ = read_env.parse_config(path=yaml_file) + + # non-existent yaml config file specified + with pytest.raises(FileNotFoundError): + _ = read_env.parse_config(path='./nonexistent.yaml') + From bef944ffe558ff781da7015efc9988dce27e7d09 Mon Sep 17 00:00:00 2001 From: bikegeek Date: Wed, 29 Jul 2026 15:21:03 -0600 Subject: [PATCH 4/4] Fix path to the test yaml file --- test/test_read_env_vars_in_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_read_env_vars_in_config.py b/test/test_read_env_vars_in_config.py index fc3b27de..b1fa6d9b 100644 --- a/test/test_read_env_vars_in_config.py +++ b/test/test_read_env_vars_in_config.py @@ -54,7 +54,7 @@ def test_yaml_file(): # test with incorrect tag value (!env instead of !ENV) cwd = os.getcwd() print(f"current working dir: {cwd}") - yaml_file = os.path.join(cwd, './data/bad_input.yaml') + yaml_file = os.path.join(cwd, './test/data/bad_input.yaml') with pytest.raises(yaml.constructor.ConstructorError): _ = read_env.parse_config(path=yaml_file)