Skip to content

Commit aa61b47

Browse files
authored
Merge pull request #1207 from Kiln-AI/leonard/kil-509-fix-skill-tool-should-validate-reference-path-is-file
fix: error when Skill ref or asset path points to a dir
2 parents 5de6094 + 3777b3a commit aa61b47

File tree

2 files changed

+28
-6
lines changed

2 files changed

+28
-6
lines changed

libs/core/kiln_ai/datamodel/skill.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def skill_md_raw(self) -> str:
5656
md_path = self.skill_md_path()
5757
if not md_path.exists():
5858
raise FileNotFoundError(f"SKILL.md not found at {md_path}")
59+
if md_path.is_dir():
60+
raise FileNotFoundError(f"SKILL.md path is a folder, not a file: {md_path}")
5961
return md_path.read_text(encoding="utf-8")
6062

6163
def body(self) -> str:
@@ -77,11 +79,11 @@ def assets_dir(self) -> Path:
7779
return self.path.parent / "assets"
7880

7981
def read_reference(self, relative_path: str) -> str:
80-
"""Read a reference file. Raises ValueError for path traversal or non-text, FileNotFoundError if missing."""
82+
"""Read a reference file. Raises ValueError for path traversal, non-text, or if the path is a folder, FileNotFoundError if missing."""
8183
return self._read_resource(self.references_dir(), relative_path)
8284

8385
def read_asset(self, relative_path: str) -> str:
84-
"""Read an asset file. Raises ValueError for path traversal or non-text, FileNotFoundError if missing."""
86+
"""Read an asset file. Raises ValueError for path traversal, non-text, or if the path is a folder, FileNotFoundError if missing."""
8587
return self._read_resource(self.assets_dir(), relative_path)
8688

8789
def _read_resource(self, base_dir: Path, relative_path: str) -> str:
@@ -96,6 +98,9 @@ def _read_resource(self, base_dir: Path, relative_path: str) -> str:
9698
except ValueError:
9799
raise ValueError("Path traversal is not allowed") from None
98100

101+
if resolved.is_dir():
102+
raise ValueError(f"Path is a folder, not a file: {relative_path}")
103+
99104
try:
100105
return resolved.read_text(encoding="utf-8")
101106
except FileNotFoundError:

libs/core/kiln_ai/datamodel/test_skill.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
from pydantic import ValidationError
55

66
from kiln_ai.datamodel.project import Project
7-
from kiln_ai.datamodel.skill import (
8-
Skill,
9-
_parse_skill_md_body,
10-
)
7+
from kiln_ai.datamodel.skill import Skill, _parse_skill_md_body
118

129

1310
@pytest.fixture
@@ -109,6 +106,14 @@ def test_body_raises_without_skill_md(mock_project):
109106
skill.body()
110107

111108

109+
def test_skill_md_path_is_directory(mock_project):
110+
skill = make_skill(parent=mock_project)
111+
skill.save_to_file()
112+
skill.skill_md_path().mkdir()
113+
with pytest.raises(FileNotFoundError, match=r"SKILL\.md path is a folder"):
114+
skill.skill_md_raw()
115+
116+
112117
def test_save_skill_md_empty_body_rejected(mock_project):
113118
skill = make_skill(parent=mock_project)
114119
skill.save_to_file()
@@ -409,6 +414,12 @@ def test_binary_file_rejected(self, mock_project):
409414
with pytest.raises(ValueError, match="not a readable text file"):
410415
skill.read_reference("image.png")
411416

417+
def test_read_reference_path_is_directory(self, mock_project):
418+
skill = save_skill_with_body(mock_project)
419+
(skill.references_dir() / "subdir").mkdir()
420+
with pytest.raises(ValueError, match="folder, not a file"):
421+
skill.read_reference("subdir")
422+
412423
def test_references_dir_requires_saved_skill(self):
413424
skill = make_skill()
414425
with pytest.raises(ValueError, match="Skill must be saved"):
@@ -446,6 +457,12 @@ def test_asset_binary_file_rejected(self, mock_project):
446457
with pytest.raises(ValueError, match="not a readable text file"):
447458
skill.read_asset("photo.jpg")
448459

460+
def test_read_asset_path_is_directory(self, mock_project):
461+
skill = save_skill_with_body(mock_project)
462+
(skill.assets_dir() / "data_dir").mkdir()
463+
with pytest.raises(ValueError, match="folder, not a file"):
464+
skill.read_asset("data_dir")
465+
449466
def test_assets_dir_requires_saved_skill(self):
450467
skill = make_skill()
451468
with pytest.raises(ValueError, match="Skill must be saved"):

0 commit comments

Comments
 (0)