fix(skills): block path traversal via skill_view name argument (#40566)
Closes #38643. Salvaged from #40521; cleaned up, re-verified against main, tests added. Co-authored-by: xy200303 <xy200303@users.noreply.github.com>
This commit is contained in:
@@ -34,6 +34,44 @@ def fake_skills(tmp_path):
|
||||
|
||||
|
||||
class TestPathTraversalBlocked:
|
||||
def test_dotdot_in_skill_name_blocked(self, fake_skills):
|
||||
"""A traversal skill name must not escape the skills search root.
|
||||
|
||||
Regression: `name` was joined onto each search dir to build the lookup
|
||||
path with no `..`/absolute guard (while file_path WAS validated), so
|
||||
name="../outside-skill" could select a sibling dir outside SKILLS_DIR.
|
||||
"""
|
||||
tmp_path = fake_skills["tmp_path"]
|
||||
outside_skill = tmp_path / "outside-skill"
|
||||
outside_skill.mkdir()
|
||||
(outside_skill / "SKILL.md").write_text("# Outside Skill\n")
|
||||
(outside_skill / ".env").write_text("ESCAPED_SECRET=do-not-leak")
|
||||
|
||||
result = json.loads(skill_view("../outside-skill", file_path=".env"))
|
||||
|
||||
assert result["success"] is False
|
||||
assert "traversal" in result["error"].lower()
|
||||
assert "do-not-leak" not in json.dumps(result)
|
||||
|
||||
def test_absolute_skill_name_blocked(self, fake_skills):
|
||||
"""An absolute skill name must not bypass the trusted search root."""
|
||||
tmp_path = fake_skills["tmp_path"]
|
||||
outside_skill = tmp_path / "outside-absolute"
|
||||
outside_skill.mkdir()
|
||||
(outside_skill / "SKILL.md").write_text("# Outside Absolute\n")
|
||||
(outside_skill / ".env").write_text("ABSOLUTE_SECRET=do-not-leak")
|
||||
|
||||
result = json.loads(skill_view(str(outside_skill), file_path=".env"))
|
||||
|
||||
assert result["success"] is False
|
||||
assert "relative path" in result["error"].lower()
|
||||
assert "do-not-leak" not in json.dumps(result)
|
||||
|
||||
def test_legit_skill_name_still_works(self, fake_skills):
|
||||
"""A normal skill name must still resolve after the name guard."""
|
||||
result = json.loads(skill_view("test-skill"))
|
||||
assert result["success"] is True
|
||||
|
||||
def test_dotdot_in_file_path(self, fake_skills):
|
||||
"""Direct .. traversal should be rejected."""
|
||||
result = json.loads(skill_view("test-skill", file_path="../../.env"))
|
||||
|
||||
Reference in New Issue
Block a user