mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-11 14:38:38 +00:00
339 lines
12 KiB
Python
339 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
from datetime import date
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
import review_changed_public_skills as runner
|
|
import skill_review_waivers as waiver_support
|
|
from jsonschema import Draft202012Validator, FormatChecker
|
|
from skill_review_waivers import (
|
|
EMPTY_MANIFEST,
|
|
SCHEMA_VERSION,
|
|
SkillReviewWaiver,
|
|
WaiverManifest,
|
|
WaiverManifestError,
|
|
matching_waiver,
|
|
parse_manifest,
|
|
validate_manifest_against_facts,
|
|
)
|
|
|
|
from deerflow.skills.review.analyzer import analyze_skill_package
|
|
from deerflow.skills.review.readers import LocalDirectoryReader
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def _write_target(repo_root: Path, content: bytes = b"safe subprocess invocation\n") -> tuple[Path, str]:
|
|
target = repo_root / "skills/public/demo/scripts/run.py"
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_bytes(content)
|
|
return target, f"sha256:{hashlib.sha256(content).hexdigest()}"
|
|
|
|
|
|
def _waiver(
|
|
*,
|
|
digest: str,
|
|
expires_on: date = date(2027, 2, 28),
|
|
preapproved_file_sha256s: tuple[str, ...] = (),
|
|
) -> SkillReviewWaiver:
|
|
return SkillReviewWaiver(
|
|
package="skills/public/demo",
|
|
source="skillscan",
|
|
rule_id="python-subprocess",
|
|
path="scripts/run.py",
|
|
line=12,
|
|
evidence="subprocess.run",
|
|
file_sha256=digest,
|
|
reason="Fixed executable and argv invocation with shell disabled.",
|
|
expires_on=expires_on,
|
|
preapproved_file_sha256s=preapproved_file_sha256s,
|
|
)
|
|
|
|
|
|
def _finding(*, severity: str = "error", line: int = 12) -> dict[str, object]:
|
|
return {
|
|
"source": "skillscan",
|
|
"rule_id": "python-subprocess",
|
|
"path": "scripts/run.py",
|
|
"line": line,
|
|
"evidence": "subprocess.run",
|
|
"severity": severity,
|
|
"message": "Subprocess usage detected.",
|
|
}
|
|
|
|
|
|
def _payload(
|
|
*,
|
|
digest: str,
|
|
path: str = "scripts/run.py",
|
|
duplicate: bool = False,
|
|
preapproved_file_sha256s: object | None = None,
|
|
) -> bytes:
|
|
entry = {
|
|
"package": "skills/public/demo",
|
|
"source": "skillscan",
|
|
"rule_id": "python-subprocess",
|
|
"path": path,
|
|
"line": 12,
|
|
"evidence": "subprocess.run",
|
|
"file_sha256": digest,
|
|
"reason": "Fixed executable and argv invocation with shell disabled.",
|
|
"expires_on": "2027-02-28",
|
|
}
|
|
if preapproved_file_sha256s is not None:
|
|
entry["preapproved_file_sha256s"] = preapproved_file_sha256s
|
|
return json.dumps({"schema_version": SCHEMA_VERSION, "waivers": [entry, entry] if duplicate else [entry]}).encode()
|
|
|
|
|
|
def test_committed_manifest_matches_schema_and_strict_parser() -> None:
|
|
manifest_path = REPO_ROOT / ".github/skill-review-waivers.v1.json"
|
|
schema = json.loads((REPO_ROOT / "contracts/skill_review/waiver_manifest.v1.schema.json").read_text(encoding="utf-8"))
|
|
payload = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
|
|
Draft202012Validator(schema, format_checker=FormatChecker()).validate(payload)
|
|
parsed = parse_manifest(manifest_path.read_bytes(), source=str(manifest_path))
|
|
|
|
assert len(parsed.waivers) == 2
|
|
assert parsed.waivers[0].preapproved_file_sha256s == ("sha256:2877bde08bf3f437b9dae3d57585a0840b9b1024736d2e5c6c657b71899269d0",)
|
|
assert parsed.waivers[1].preapproved_file_sha256s == ("sha256:ea2521ba41c8fd16b2900758c890bb6c6d2b4b01da10b4a860facb8587ed0bde",)
|
|
|
|
|
|
def test_skill_creator_waivers_match_current_error_findings() -> None:
|
|
package = REPO_ROOT / "skills/public/skill-creator"
|
|
manifest = parse_manifest((REPO_ROOT / ".github/skill-review-waivers.v1.json").read_bytes(), source="committed manifest")
|
|
facts = analyze_skill_package(LocalDirectoryReader(package).read(), profile="deerflow")
|
|
|
|
validation_errors = validate_manifest_against_facts(
|
|
manifest,
|
|
facts_by_package={"skills/public/skill-creator": facts},
|
|
repo_root=REPO_ROOT,
|
|
today=date(2026, 8, 31),
|
|
)
|
|
assert validation_errors == []
|
|
|
|
|
|
@pytest.mark.parametrize("path", ["../run.py", "/tmp/run.py", "scripts\\run.py", "scripts/../run.py"])
|
|
def test_parser_rejects_noncanonical_or_traversing_paths(tmp_path: Path, path: str) -> None:
|
|
_, digest = _write_target(tmp_path)
|
|
|
|
with pytest.raises(WaiverManifestError, match="canonical relative POSIX path|backslashes"):
|
|
parse_manifest(_payload(digest=digest, path=path), source="test manifest")
|
|
|
|
|
|
def test_parser_rejects_duplicate_exact_waivers(tmp_path: Path) -> None:
|
|
_, digest = _write_target(tmp_path)
|
|
|
|
with pytest.raises(WaiverManifestError, match="duplicates an earlier waiver"):
|
|
parse_manifest(_payload(digest=digest, duplicate=True), source="test manifest")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("preapproved", "error"),
|
|
[
|
|
("sha256:" + "1" * 64, "must be an array"),
|
|
(["sha256:" + "1" * 63], "64 lowercase hex characters"),
|
|
(["sha256:" + "1" * 64] * 2, "entries must be unique"),
|
|
(["PRIMARY"], "must not repeat file_sha256"),
|
|
([f"sha256:{index:064x}" for index in range(9)], "at most 8 entries"),
|
|
],
|
|
)
|
|
def test_parser_rejects_invalid_preapproved_hashes(tmp_path: Path, preapproved: object, error: str) -> None:
|
|
_, digest = _write_target(tmp_path)
|
|
value = [digest] if preapproved == ["PRIMARY"] else preapproved
|
|
|
|
with pytest.raises(WaiverManifestError, match=error):
|
|
parse_manifest(
|
|
_payload(digest=digest, preapproved_file_sha256s=value),
|
|
source="test manifest",
|
|
)
|
|
|
|
|
|
def test_missing_manifest_at_ref_means_no_waivers(tmp_path: Path, monkeypatch) -> None:
|
|
monkeypatch.setattr(
|
|
waiver_support.subprocess,
|
|
"run",
|
|
lambda *args, **kwargs: waiver_support.subprocess.CompletedProcess(args[0], 128, stdout=b"", stderr=b"missing"),
|
|
)
|
|
|
|
manifest = waiver_support.load_manifest_at_ref(tmp_path, "a" * 40, label="trusted base")
|
|
|
|
assert manifest is EMPTY_MANIFEST
|
|
|
|
|
|
def test_matching_waiver_requires_exact_finding_and_current_file_hash(tmp_path: Path) -> None:
|
|
target, digest = _write_target(tmp_path)
|
|
manifest = WaiverManifest((_waiver(digest=digest),))
|
|
|
|
assert matching_waiver(_finding(), package="skills/public/demo", manifest=manifest, repo_root=tmp_path, today=date(2026, 8, 31)) is manifest.waivers[0]
|
|
assert matching_waiver(_finding(line=13), package="skills/public/demo", manifest=manifest, repo_root=tmp_path, today=date(2026, 8, 31)) is None
|
|
|
|
target.write_bytes(b"changed\n")
|
|
assert matching_waiver(_finding(), package="skills/public/demo", manifest=manifest, repo_root=tmp_path, today=date(2026, 8, 31)) is None
|
|
|
|
|
|
def test_preapproved_file_hash_authorizes_a_later_file_revision(tmp_path: Path) -> None:
|
|
target, current_digest = _write_target(tmp_path)
|
|
future_content = b"safe subprocess invocation with explicit UTF-8\n"
|
|
future_digest = f"sha256:{hashlib.sha256(future_content).hexdigest()}"
|
|
waiver = _waiver(digest=current_digest, preapproved_file_sha256s=(future_digest,))
|
|
manifest = WaiverManifest((waiver,))
|
|
facts = {"findings": [_finding()]}
|
|
|
|
assert (
|
|
validate_manifest_against_facts(
|
|
manifest,
|
|
facts_by_package={waiver.package: facts},
|
|
repo_root=tmp_path,
|
|
today=date(2026, 8, 31),
|
|
)
|
|
== []
|
|
)
|
|
|
|
target.write_bytes(future_content)
|
|
|
|
assert (
|
|
matching_waiver(
|
|
_finding(),
|
|
package=waiver.package,
|
|
manifest=manifest,
|
|
repo_root=tmp_path,
|
|
today=date(2026, 8, 31),
|
|
)
|
|
is waiver
|
|
)
|
|
assert (
|
|
validate_manifest_against_facts(
|
|
manifest,
|
|
facts_by_package={waiver.package: facts},
|
|
repo_root=tmp_path,
|
|
today=date(2026, 8, 31),
|
|
)
|
|
== []
|
|
)
|
|
|
|
|
|
def test_matching_waiver_rejects_symlinked_package_outside_repository(tmp_path: Path) -> None:
|
|
external_root = tmp_path.parent / f"{tmp_path.name}-external"
|
|
external_target, digest = _write_target(external_root)
|
|
public_root = tmp_path / "skills/public"
|
|
public_root.mkdir(parents=True)
|
|
(public_root / "demo").symlink_to(external_target.parents[1], target_is_directory=True)
|
|
|
|
assert (
|
|
matching_waiver(
|
|
_finding(),
|
|
package="skills/public/demo",
|
|
manifest=WaiverManifest((_waiver(digest=digest),)),
|
|
repo_root=tmp_path,
|
|
today=date(2026, 8, 31),
|
|
)
|
|
is None
|
|
)
|
|
|
|
|
|
def test_expired_waiver_is_rejected_and_does_not_match(tmp_path: Path) -> None:
|
|
_, digest = _write_target(tmp_path)
|
|
waiver = _waiver(digest=digest, expires_on=date(2026, 8, 30))
|
|
manifest = WaiverManifest((waiver,))
|
|
facts = {"findings": [_finding()]}
|
|
|
|
errors = validate_manifest_against_facts(
|
|
manifest,
|
|
facts_by_package={waiver.package: facts},
|
|
repo_root=tmp_path,
|
|
today=date(2026, 8, 31),
|
|
)
|
|
|
|
assert len(errors) == 1
|
|
assert "expired on 2026-08-30" in errors[0]
|
|
assert matching_waiver(_finding(), package=waiver.package, manifest=manifest, repo_root=tmp_path, today=date(2026, 8, 31)) is None
|
|
|
|
|
|
def test_manifest_validation_refuses_blocker_waiver(tmp_path: Path) -> None:
|
|
_, digest = _write_target(tmp_path)
|
|
waiver = _waiver(digest=digest)
|
|
|
|
errors = validate_manifest_against_facts(
|
|
WaiverManifest((waiver,)),
|
|
facts_by_package={waiver.package: {"findings": [_finding(severity="blocker")]}},
|
|
repo_root=tmp_path,
|
|
today=date(2026, 8, 31),
|
|
)
|
|
|
|
assert "blockers can never be waived" in errors[0]
|
|
|
|
|
|
def test_pr_head_manifest_is_validated_but_cannot_self_apply(tmp_path: Path, monkeypatch) -> None:
|
|
_, digest = _write_target(tmp_path)
|
|
proposed = WaiverManifest((_waiver(digest=digest),))
|
|
loaded: list[tuple[str, str]] = []
|
|
|
|
def fake_load(repo_root: Path, ref: str, *, label: str) -> WaiverManifest:
|
|
loaded.append((ref, label))
|
|
return EMPTY_MANIFEST if label == "trusted base" else proposed
|
|
|
|
monkeypatch.setattr(runner, "load_manifest_at_ref", fake_load)
|
|
args = SimpleNamespace(base_ref="base-sha", head_ref="head-sha", before=None, after=None)
|
|
|
|
effective, head = runner.load_waiver_manifests(args, tmp_path)
|
|
|
|
assert effective is EMPTY_MANIFEST
|
|
assert head is proposed
|
|
assert loaded == [("base-sha", "trusted base"), ("head-sha", "proposed head")]
|
|
|
|
|
|
def test_main_fails_closed_on_malformed_head_manifest(tmp_path: Path, monkeypatch, capsys) -> None:
|
|
def fail_load(args, repo_root):
|
|
raise WaiverManifestError("proposed head: invalid JSON")
|
|
|
|
monkeypatch.setattr(runner, "load_waiver_manifests", fail_load)
|
|
monkeypatch.setattr(runner.subprocess, "run", lambda *args, **kwargs: pytest.fail("diff must not run"))
|
|
|
|
exit_code = runner.main(["--base-ref", "base", "--head-ref", "head", "--repo-root", str(tmp_path)])
|
|
|
|
assert exit_code == 1
|
|
assert "Invalid waiver manifest" in capsys.readouterr().err
|
|
|
|
|
|
def test_workflow_triggers_on_waiver_implementation_and_manifest() -> None:
|
|
workflow = (REPO_ROOT / ".github/workflows/skill-review-ci.yml").read_text(encoding="utf-8")
|
|
|
|
assert workflow.count('"scripts/skill_review_waivers.py"') == 2
|
|
assert workflow.count('".github/skill-review-waivers.v1.json"') == 2
|
|
|
|
|
|
def test_run_review_keeps_waived_error_visible_and_passes(tmp_path: Path, monkeypatch, capsys) -> None:
|
|
package = tmp_path / "skills/public/demo"
|
|
_, digest = _write_target(tmp_path)
|
|
facts = {
|
|
"summary": {"blockers": 0, "errors": 1, "warnings": 0, "infos": 0},
|
|
"completeness": {"not_assessed": []},
|
|
"findings": [_finding()],
|
|
}
|
|
monkeypatch.setattr(runner, "collect_review_facts", lambda *args: facts)
|
|
|
|
exit_code = runner.run_review(package, tmp_path, "python", WaiverManifest((_waiver(digest=digest),)))
|
|
|
|
output = capsys.readouterr().out
|
|
assert exit_code == 0
|
|
assert "- error python-subprocess" in output
|
|
assert "[WAIVED until 2027-02-28:" in output
|
|
assert "Passed: skills/public/demo (1 waived finding(s))" in output
|
|
|
|
|
|
def test_run_review_still_fails_for_unwaived_error(tmp_path: Path, monkeypatch) -> None:
|
|
package = tmp_path / "skills/public/demo"
|
|
_write_target(tmp_path)
|
|
facts = {
|
|
"summary": {"blockers": 0, "errors": 1, "warnings": 0, "infos": 0},
|
|
"completeness": {"not_assessed": []},
|
|
"findings": [_finding()],
|
|
}
|
|
monkeypatch.setattr(runner, "collect_review_facts", lambda *args: facts)
|
|
|
|
assert runner.run_review(package, tmp_path, "python", EMPTY_MANIFEST) == 1
|