#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Compare the first Go detector port with the Python reference oracle.""" from __future__ import annotations import json import os import subprocess import sys from pathlib import Path from types import SimpleNamespace from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from enodia_sentinel import __version__, event # noqa: E402 from enodia_sentinel.config import Config # noqa: E402 from enodia_sentinel.detectors import ( # noqa: E402 credential_access, deleted_exe, input_snooper, ld_preload, ) from enodia_sentinel.system import SystemState # noqa: E402 HOST = "parity-host" TIMESTAMP = "2026-07-10T00:00:00-07:00" def main() -> int: fixture = ROOT / "tests/fixtures/go/process-detectors.json" fixture_data = json.loads(fixture.read_text()) env = os.environ.copy() env.pop("ENODIA_CONFIG", None) env["GOCACHE"] = "/tmp/enodia-go-cache" command = [ "go", "run", "./cmd/enodia-sentinel-go", "--config", str(ROOT / "tests/fixtures/go/no-such-config.toml"), "--once", "--fixture", str(fixture), "--host", HOST, "--timestamp", TIMESTAMP, ] result = subprocess.run( command, cwd=ROOT / "go-agent", env=env, capture_output=True, text=True, timeout=60, ) if result.returncode: print(result.stderr, file=sys.stderr, end="") return result.returncode go_events = [json.loads(line) for line in result.stdout.splitlines() if line] go_alerts = [record for record in go_events if record["event_type"] == "alert"] processes = [] for raw_process in fixture_data["processes"]: process = dict(raw_process) process.setdefault("environ", {}) process.setdefault("fd_targets", {}) processes.append(SimpleNamespace(**process)) cfg = Config() state = SystemState(processes=processes, sockets=[]) with patch.object(ld_preload, "Path") as path_class: preload = path_class.return_value preload.is_file.return_value = bool(fixture_data.get("ld_preload")) preload.read_text.return_value = fixture_data.get("ld_preload", "") alerts = list(ld_preload.detect(state, cfg)) alerts.extend(deleted_exe.detect(state, cfg)) alerts.extend(input_snooper.detect(state, cfg)) alerts.extend(credential_access.detect(state, cfg)) python_alerts = [ event.from_alert(alert, host=HOST, timestamp=TIMESTAMP) for alert in alerts ] if go_alerts != python_alerts: print("Go/Python alert parity mismatch", file=sys.stderr) print(json.dumps({"go": go_alerts, "python": python_alerts}, indent=2), file=sys.stderr) return 1 status_events = [record for record in go_events if record["event_type"] == "status"] if len(status_events) != 1 or status_events[0].get("schema") != "enodia.event.v1": print("Go sidecar did not emit one enodia.event.v1 status record", file=sys.stderr) return 1 status = status_events[0].get("status", {}) if status.get("schema") != "enodia.status.v1" or status.get("version") != __version__: print("Go status schema/version drifted from Python", file=sys.stderr) return 1 signatures = sorted({record["alert"]["signature"] for record in go_alerts}) print(f"parity ok: {len(go_alerts)} alerts across {', '.join(signatures)}") return 0 if __name__ == "__main__": raise SystemExit(main())