SimpleChatbot / scripts /audit_execution_client_paths.py
Amin
Deploy Hermes-native futures advisory migration and UI authority-contrast upgrade.
1f8cf56
Raw
History Blame Contribute Delete
4.71 kB
#!/usr/bin/env python3
"""Fail when an external Hermes client can bypass the execution service."""
from __future__ import annotations
import ast
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OVERLAY = ROOT / "hermes_overlay"
SERVICE = OVERLAY / "trading" / "domain" / "execution_service.py"
def _production_python_files() -> list[Path]:
return sorted(
path for path in OVERLAY.rglob("*.py")
if "tests" not in path.parts and "__pycache__" not in path.parts
)
def audit(root: Path = ROOT) -> list[str]:
findings: list[str] = []
overlay = root / "hermes_overlay"
service = overlay / "trading" / "domain" / "execution_service.py"
for path in sorted(
p for p in overlay.rglob("*.py")
if "tests" not in p.parts and "__pycache__" not in p.parts
):
text = path.read_text(encoding="utf-8")
rel = path.relative_to(root).as_posix()
if path != service:
for token in ("._submit_entry(", "._submit_close("):
if token in text:
findings.append(f"private execution mutation referenced by {rel}: {token}")
if path not in {service, overlay / "trading" / "adapters" / "paper_adapter.py"}:
if ".create_order(" in text:
findings.append(f"raw adapter order call outside service: {rel}")
dashboard = (overlay / "tools" / "futures_dashboard_api.py").read_text(encoding="utf-8")
if "ExecutionService().execute_approved_plan(ExecuteApprovedPlanCommand(" not in dashboard:
findings.append("dashboard/API execute route does not use identifier-only service command")
route_slice = dashboard[dashboard.find("async def futures_paper_execute"):]
route_slice = route_slice[:route_slice.find("\n\n_") if "\n\n_" in route_slice else len(route_slice)]
for token in ("._submit_entry(", "execute_futures_position(", ".create_order("):
if token in route_slice:
findings.append(f"dashboard/API execute route contains forbidden call: {token}")
telegram = (overlay / "tools" / "telegram_bot.py").read_text(encoding="utf-8")
if "execute=True" in telegram:
findings.append("Telegram may request direct execution")
for token in ("execute_approved_plan", "close_position(", "._submit_entry(", "._submit_close("):
if token in telegram:
findings.append(f"Telegram contains mutation path: {token}")
agent_impl = (overlay / "plugins" / "futures_trading" / "tools.py").read_text(encoding="utf-8")
plugin_init = (overlay / "plugins" / "futures_trading" / "__init__.py").read_text(encoding="utf-8")
for name in ("execute_futures_position", "close_futures_position", "set_leverage_and_margin"):
if f'name="{name}"' in plugin_init:
findings.append(f"agent mutation tool registered: {name}")
if "execute=False" not in agent_impl:
findings.append("agent analysis workflow does not force execute=False")
cycle_path = overlay / "trading" / "trade_cycle.py"
cycle = cycle_path.read_text(encoding="utf-8")
if "execute_futures_position" in cycle:
findings.append("analysis cycle still imports or calls raw execution")
if "Direct cycle execution is disabled" not in cycle:
findings.append("analysis cycle does not explicitly reject execute=True")
compat_path = overlay / "trading" / "futures_execution.py"
compat = compat_path.read_text(encoding="utf-8")
tree = ast.parse(compat)
raw_entry = next(
node for node in tree.body
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and node.name == "execute_futures_position"
)
raw_source = ast.get_source_segment(compat, raw_entry) or ""
if "direct raw futures execution is disabled" not in raw_source:
findings.append("legacy raw entry compatibility API is not hard-rejected")
close_fn = next(
node for node in tree.body
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and node.name == "close_futures_position"
)
close_source = ast.get_source_segment(compat, close_fn) or ""
if "ClosePositionCommand" not in close_source or ".close_position(" not in close_source:
findings.append("emergency close does not use typed service boundary")
if "._submit_close(" in close_source:
findings.append("emergency close calls private mutation method")
return findings
def main() -> int:
findings = audit()
for finding in findings:
print(f"ERROR: {finding}")
print(f"execution_client_path_findings={len(findings)}")
return 1 if findings else 0
if __name__ == "__main__":
raise SystemExit(main())