from pathlib import Path from zipfile import ZipFile from deerflow.skill_knowledge import build_skill_ir, scan_skill def test_scanner_excludes_code_blocks_source_and_sensitive_files(tmp_path: Path) -> None: (tmp_path / "SKILL.md").write_text( "# 示例\n本技能使用 客户画像数据。\n```python\nPASSWORD = 'must-not-enter-ir'\n```\n", encoding="utf-8", ) (tmp_path / "worker.py").write_text("# uses hidden system\nSECRET='never-read'", encoding="utf-8") (tmp_path / ".env").write_text("TOKEN=super-secret-value", encoding="utf-8") scan = scan_skill("example", tmp_path) ir = build_skill_ir(scan) assert scan.skipped_code_count == 1 assert scan.blocked_files == [{"path": ".env", "reason": "疑似密钥或敏感配置文件"}] assert "must-not-enter-ir" not in str(ir) assert "never-read" not in str(ir) assert any(relation["predicate"] == "uses" for relation in ir["relations"]) def test_code_only_change_does_not_change_digest(tmp_path: Path) -> None: (tmp_path / "SKILL.md").write_text("# Stable knowledge", encoding="utf-8") code = tmp_path / "worker.py" code.write_text("print(1)", encoding="utf-8") before = scan_skill("example", tmp_path).source_digest code.write_text("print(2)", encoding="utf-8") after = scan_skill("example", tmp_path).source_digest assert before == after def test_zip_is_inspected_and_only_safe_knowledge_members_are_read(tmp_path: Path) -> None: archive = tmp_path / "materials.zip" with ZipFile(archive, "w") as output: output.writestr("guide.md", "# 操作手册\n仅提炼这段知识。") output.writestr("worker.py", "SECRET = 'code-must-not-be-read'") output.writestr("../escape.md", "path traversal") scan = scan_skill("example", tmp_path) assert [file.path for file in scan.files] == ["materials.zip!/guide.md"] assert scan.skipped_code_count == 1 assert scan.blocked_files == [ { "path": "materials.zip!/../escape.md", "reason": "压缩包包含路径穿越或符号链接", } ] assert "code-must-not-be-read" not in str(scan.manifest)