177 lines
5.5 KiB
Python
177 lines
5.5 KiB
Python
import importlib.util
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
TOOLS_SCRIPTS_DIR = REPO_ROOT / "tools" / "scripts"
|
|
if str(TOOLS_SCRIPTS_DIR) not in sys.path:
|
|
sys.path.insert(0, str(TOOLS_SCRIPTS_DIR))
|
|
|
|
|
|
def load_module(relative_path: str, module_name: str):
|
|
module_path = REPO_ROOT / relative_path
|
|
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec.loader is not None
|
|
sys.modules[module_name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
sync_risk_labels = load_module(
|
|
"tools/scripts/sync_risk_labels.py",
|
|
"sync_risk_labels_test",
|
|
)
|
|
|
|
|
|
class SyncRiskLabelsTests(unittest.TestCase):
|
|
def test_choose_synced_risk_promotes_git_mutation_to_critical(self):
|
|
content = """---
|
|
name: commit
|
|
description: commit changes safely
|
|
risk: unknown
|
|
source: community
|
|
---
|
|
|
|
Use `git commit` and `git push` once the branch is ready.
|
|
"""
|
|
metadata = {"name": "commit", "description": "commit changes safely", "risk": "unknown"}
|
|
|
|
decision = sync_risk_labels.choose_synced_risk(content, metadata, skill_id="commit")
|
|
|
|
self.assertIsNotNone(decision)
|
|
assert decision is not None
|
|
self.assertEqual(decision[0], "critical")
|
|
self.assertIn("git mutation", decision[1])
|
|
|
|
def test_choose_synced_risk_promotes_read_only_skill_to_safe(self):
|
|
content = """---
|
|
name: seo-fundamentals
|
|
description: Learn the core principles of SEO.
|
|
risk: unknown
|
|
source: community
|
|
---
|
|
|
|
## Overview
|
|
|
|
Review search quality signals and analyze page structure.
|
|
"""
|
|
metadata = {"name": "seo-fundamentals", "description": "Learn the core principles of SEO.", "risk": "unknown"}
|
|
|
|
decision = sync_risk_labels.choose_synced_risk(content, metadata, skill_id="seo-fundamentals")
|
|
|
|
self.assertIsNotNone(decision)
|
|
assert decision is not None
|
|
self.assertEqual(decision[0], "safe")
|
|
|
|
def test_choose_synced_risk_keeps_unknown_when_safe_text_mentions_install(self):
|
|
content = """---
|
|
name: package-setup
|
|
description: Explain how to inspect package setup.
|
|
risk: unknown
|
|
source: community
|
|
---
|
|
|
|
Use this skill to analyze package setup and install dependencies if needed.
|
|
"""
|
|
metadata = {"name": "package-setup", "description": "Explain how to inspect package setup.", "risk": "unknown"}
|
|
|
|
decision = sync_risk_labels.choose_synced_risk(content, metadata, skill_id="package-setup")
|
|
|
|
self.assertIsNone(decision)
|
|
|
|
def test_choose_synced_risk_promotes_explicit_offensive_skill(self):
|
|
content = """---
|
|
name: pentest-checklist
|
|
description: penetration testing checklist
|
|
risk: unknown
|
|
source: community
|
|
---
|
|
|
|
Plan a penetration testing engagement and define red team scope.
|
|
"""
|
|
metadata = {"name": "pentest-checklist", "description": "penetration testing checklist", "risk": "unknown"}
|
|
|
|
decision = sync_risk_labels.choose_synced_risk(content, metadata, skill_id="pentest-checklist")
|
|
|
|
self.assertIsNotNone(decision)
|
|
assert decision is not None
|
|
self.assertEqual(decision[0], "offensive")
|
|
|
|
def test_choose_synced_risk_promotes_explicit_none_skill(self):
|
|
content = """---
|
|
name: architecture-patterns
|
|
description: backend architecture patterns
|
|
risk: unknown
|
|
source: community
|
|
---
|
|
|
|
Explain architecture trade-offs and design principles.
|
|
"""
|
|
metadata = {"name": "architecture-patterns", "description": "backend architecture patterns", "risk": "unknown"}
|
|
|
|
decision = sync_risk_labels.choose_synced_risk(content, metadata, skill_id="architecture-patterns")
|
|
|
|
self.assertIsNotNone(decision)
|
|
assert decision is not None
|
|
self.assertEqual(decision[0], "none")
|
|
|
|
def test_update_skill_file_rewrites_frontmatter(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
skill_path = Path(temp_dir) / "SKILL.md"
|
|
skill_path.write_text(
|
|
"""---
|
|
name: commit
|
|
description: commit changes safely
|
|
risk: unknown
|
|
source: community
|
|
---
|
|
|
|
Use `git commit` before `git push`.
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
changed, new_risk, reasons = sync_risk_labels.update_skill_file(skill_path)
|
|
|
|
self.assertTrue(changed)
|
|
self.assertEqual(new_risk, "critical")
|
|
self.assertIn("git mutation", reasons)
|
|
updated = skill_path.read_text(encoding="utf-8")
|
|
self.assertIn("risk: critical", updated)
|
|
self.assertNotIn("risk: unknown", updated)
|
|
|
|
def test_update_skill_file_adds_offensive_disclaimer_when_needed(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
skill_path = Path(temp_dir) / "pentest-checklist" / "SKILL.md"
|
|
skill_path.parent.mkdir(parents=True, exist_ok=True)
|
|
skill_path.write_text(
|
|
"""---
|
|
name: pentest-checklist
|
|
description: penetration testing checklist
|
|
risk: unknown
|
|
source: community
|
|
---
|
|
|
|
# Pentest Checklist
|
|
|
|
Plan a penetration testing engagement and define red team scope.
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
changed, new_risk, _ = sync_risk_labels.update_skill_file(skill_path)
|
|
|
|
self.assertTrue(changed)
|
|
self.assertEqual(new_risk, "offensive")
|
|
updated = skill_path.read_text(encoding="utf-8")
|
|
self.assertIn("risk: offensive", updated)
|
|
self.assertIn("AUTHORIZED USE ONLY", updated)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|