mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
105 lines
3.4 KiB
Python
105 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Regression tests for skill packaging security behavior.
|
|
"""
|
|
|
|
import sys
|
|
import tempfile
|
|
import types
|
|
import zipfile
|
|
from pathlib import Path
|
|
from unittest import TestCase, main
|
|
|
|
|
|
fake_quick_validate = types.ModuleType("quick_validate")
|
|
fake_quick_validate.validate_skill = lambda _path: (True, "Skill is valid!")
|
|
sys.modules["quick_validate"] = fake_quick_validate
|
|
|
|
from package_skill import package_skill
|
|
|
|
|
|
class TestPackageSkillSecurity(TestCase):
|
|
def setUp(self):
|
|
self.temp_dir = Path(tempfile.mkdtemp(prefix="test_skill_"))
|
|
|
|
def tearDown(self):
|
|
import shutil
|
|
|
|
if self.temp_dir.exists():
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def create_skill(self, name="test-skill"):
|
|
skill_dir = self.temp_dir / name
|
|
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
(skill_dir / "SKILL.md").write_text("---\nname: test-skill\ndescription: test\n---\n")
|
|
(skill_dir / "script.py").write_text("print('ok')\n")
|
|
return skill_dir
|
|
|
|
def test_packages_normal_files(self):
|
|
skill_dir = self.create_skill("normal-skill")
|
|
out_dir = self.temp_dir / "out"
|
|
out_dir.mkdir()
|
|
|
|
result = package_skill(str(skill_dir), str(out_dir))
|
|
|
|
self.assertIsNotNone(result)
|
|
skill_file = out_dir / "normal-skill.skill"
|
|
self.assertTrue(skill_file.exists())
|
|
with zipfile.ZipFile(skill_file, "r") as archive:
|
|
names = set(archive.namelist())
|
|
self.assertIn("normal-skill/SKILL.md", names)
|
|
self.assertIn("normal-skill/script.py", names)
|
|
|
|
def test_rejects_symlink_to_external_file(self):
|
|
skill_dir = self.create_skill("symlink-file-skill")
|
|
outside = self.temp_dir / "outside-secret.txt"
|
|
outside.write_text("super-secret\n")
|
|
link = skill_dir / "loot.txt"
|
|
out_dir = self.temp_dir / "out"
|
|
out_dir.mkdir()
|
|
|
|
try:
|
|
link.symlink_to(outside)
|
|
except (OSError, NotImplementedError):
|
|
self.skipTest("symlink unsupported on this platform")
|
|
|
|
result = package_skill(str(skill_dir), str(out_dir))
|
|
self.assertIsNone(result)
|
|
|
|
def test_rejects_symlink_directory(self):
|
|
skill_dir = self.create_skill("symlink-dir-skill")
|
|
outside_dir = self.temp_dir / "outside"
|
|
outside_dir.mkdir()
|
|
(outside_dir / "secret.txt").write_text("secret\n")
|
|
link = skill_dir / "docs"
|
|
out_dir = self.temp_dir / "out"
|
|
out_dir.mkdir()
|
|
|
|
try:
|
|
link.symlink_to(outside_dir, target_is_directory=True)
|
|
except (OSError, NotImplementedError):
|
|
self.skipTest("symlink unsupported on this platform")
|
|
|
|
result = package_skill(str(skill_dir), str(out_dir))
|
|
self.assertIsNone(result)
|
|
|
|
def test_allows_nested_regular_files(self):
|
|
skill_dir = self.create_skill("nested-skill")
|
|
nested = skill_dir / "lib" / "helpers"
|
|
nested.mkdir(parents=True, exist_ok=True)
|
|
(nested / "util.py").write_text("def run():\n return 1\n")
|
|
out_dir = self.temp_dir / "out"
|
|
out_dir.mkdir()
|
|
|
|
result = package_skill(str(skill_dir), str(out_dir))
|
|
|
|
self.assertIsNotNone(result)
|
|
skill_file = out_dir / "nested-skill.skill"
|
|
with zipfile.ZipFile(skill_file, "r") as archive:
|
|
names = set(archive.namelist())
|
|
self.assertIn("nested-skill/lib/helpers/util.py", names)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|