Skip to content

Commit 7629c74

Browse files
authored
fix(core): handle symlinks in deprecated prompt save path (#36585)
Resolve symlinks before validating file extensions in the deprecated `save()` method on prompt classes. Credit to Jeff Ponte (@JDP-Security) for reporting the symlink resolution issue.
1 parent ce21bf4 commit 7629c74

2 files changed

Lines changed: 21 additions & 4 deletions

File tree

libs/core/langchain_core/prompts/base.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,12 @@ def save(self, file_path: Path | str) -> None:
389389
directory_path = save_path.parent
390390
directory_path.mkdir(parents=True, exist_ok=True)
391391

392-
if save_path.suffix == ".json":
393-
with save_path.open("w", encoding="utf-8") as f:
392+
resolved_path = save_path.resolve()
393+
if resolved_path.suffix == ".json":
394+
with resolved_path.open("w", encoding="utf-8") as f:
394395
json.dump(prompt_dict, f, indent=4)
395-
elif save_path.suffix.endswith((".yaml", ".yml")):
396-
with save_path.open("w", encoding="utf-8") as f:
396+
elif resolved_path.suffix.endswith((".yaml", ".yml")):
397+
with resolved_path.open("w", encoding="utf-8") as f:
397398
yaml.dump(prompt_dict, f, default_flow_style=False)
398399
else:
399400
msg = f"{save_path} must be json or yaml"

libs/core/tests/unit_tests/prompts/test_loading.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,22 @@ def test_symlink_jinja2_rce_is_blocked(tmp_path: Path) -> None:
325325
load_prompt_from_config(config, allow_dangerous_paths=True)
326326

327327

328+
def test_save_symlink_to_py_is_blocked(tmp_path: Path) -> None:
329+
"""Test that save() resolves symlinks before checking the file extension."""
330+
target = tmp_path / "malicious.py"
331+
symlink = tmp_path / "output.json"
332+
symlink.symlink_to(target)
333+
334+
prompt = PromptTemplate(input_variables=["name"], template="Hello {name}")
335+
with (
336+
suppress_langchain_deprecation_warning(),
337+
pytest.raises(ValueError, match="must be json or yaml"),
338+
):
339+
prompt.save(symlink)
340+
341+
assert not target.exists()
342+
343+
328344
def test_loading_few_shot_prompt_from_yaml() -> None:
329345
"""Test loading few shot prompt from yaml."""
330346
with change_directory(EXAMPLE_DIR), suppress_langchain_deprecation_warning():

0 commit comments

Comments
 (0)