Files
hermes-webui/tests/test_skill_save_symlink_guard.py
T
4abf3bf5ef fix(skills): reject symlinked SKILL.md on save (#4240) (#4245)
* fix(skills): reject symlinked skill saves

* chore(release): stamp v0.51.434 (Release OU) for #4240 skill-save symlink guard

---------

Co-authored-by: 峯岸 亮 <1920071390@campus.ouj.ac.jp>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 04:22:17 -07:00

61 lines
1.7 KiB
Python

import os
import pytest
import api.routes as routes
class _FakeHandler:
pass
def _patch_skill_routes(monkeypatch, skills_dir):
cap = {}
monkeypatch.setattr(routes, "_active_skills_dir", lambda: skills_dir)
monkeypatch.setattr(routes, "j", lambda h, o: (cap.__setitem__("ok", o), True)[1])
monkeypatch.setattr(
routes,
"bad",
lambda h, m, c=400: (cap.__setitem__("bad", (m, c)), True)[1],
)
return cap
def test_skill_save_rejects_symlinked_skill_file(tmp_path, monkeypatch):
skills_dir = tmp_path / "skills"
skill_dir = skills_dir / "demo"
skill_dir.mkdir(parents=True)
outside = tmp_path / "outside.md"
outside.write_text("important", encoding="utf-8")
link = skill_dir / "SKILL.md"
try:
os.symlink(str(outside), str(link))
except (OSError, NotImplementedError):
pytest.skip("platform does not support symlinks")
cap = _patch_skill_routes(monkeypatch, skills_dir)
routes._handle_skill_save(
_FakeHandler(),
{"name": "demo", "content": "changed"},
)
assert "bad" in cap, f"expected 400, got {cap}"
assert cap["bad"][1] == 400
assert "Cannot save to a symlinked skill file" in cap["bad"][0]
assert outside.read_text(encoding="utf-8") == "important"
def test_skill_save_real_file_still_works(tmp_path, monkeypatch):
skills_dir = tmp_path / "skills"
cap = _patch_skill_routes(monkeypatch, skills_dir)
routes._handle_skill_save(
_FakeHandler(),
{"name": "Demo Skill", "content": "# Demo\n"},
)
skill_file = skills_dir / "demo-skill" / "SKILL.md"
assert "ok" in cap, f"expected success, got {cap}"
assert cap["ok"]["ok"] is True
assert skill_file.read_text(encoding="utf-8") == "# Demo\n"