diff --git a/.gitignore b/.gitignore index 22fafbd..be78ef6 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ Thumbs.db # Logs *.log +.clawmetry-fleet.db diff --git a/clawmetry/cli.py b/clawmetry/cli.py index dc11912..d382335 100755 --- a/clawmetry/cli.py +++ b/clawmetry/cli.py @@ -556,6 +556,7 @@ def _register_nemoclaw_sandbox_daemons() -> None: import shutil import os import platform + from xml.sax.saxutils import escape if platform.system() != "Darwin": return @@ -607,8 +608,9 @@ def _register_nemoclaw_sandbox_daemons() -> None: docker_path = shutil.which("docker") or "/usr/local/bin/docker" for pod in pods: - label = f"com.clawmetry.sandbox.{pod}" - plist_path = launch_agents / f"{label}.plist" + label = f"com.clawmetry.sandbox.{escape(pod)}" + pod_xml = escape(pod) + plist_path = launch_agents / f"com.clawmetry.sandbox.{pod}.plist" sync_script = "/usr/local/lib/python3.11/dist-packages/clawmetry/sync.py" plist = f""" @@ -624,7 +626,7 @@ def _register_nemoclaw_sandbox_daemons() -> None: exec -n openshell - {pod} + {pod_xml} -- python3 {sync_script} @@ -632,8 +634,8 @@ def _register_nemoclaw_sandbox_daemons() -> None: RunAtLoad KeepAlive ThrottleInterval 30 - StandardOutPath /tmp/clawmetry-{pod}.log - StandardErrorPath /tmp/clawmetry-{pod}.log + StandardOutPath /tmp/clawmetry-{pod_xml}.log + StandardErrorPath /tmp/clawmetry-{pod_xml}.log """ plist_path.write_text(plist) @@ -1670,8 +1672,18 @@ def _cmd_update() -> None: print("Checking for updates...") try: result = subprocess.run( - [sys.executable, "-m", "pip", "install", "--upgrade", "--break-system-packages", "clawmetry"], - capture_output=True, text=True, timeout=120, + [ + sys.executable, + "-m", + "pip", + "install", + "--upgrade", + "--break-system-packages", + "clawmetry", + ], + capture_output=True, + text=True, + timeout=120, ) if result.returncode == 0: # Check new version diff --git a/tests/test_cli.py b/tests/test_cli.py index e4513d7..4781907 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,7 @@ from pathlib import Path +from xml.etree import ElementTree as ET + +import pytest import clawmetry.cli as cli @@ -30,9 +33,51 @@ def test_print_nemoclaw_preset_hint_emits_command(monkeypatch, capsys): helper = "/tmp/add-nemoclaw-clawmetry-preset.sh" monkeypatch.setattr(cli, "_get_nemoclaw_preset_script", lambda: helper) - cli._print_nemoclaw_preset_hint(lambda text: text, lambda text: text, lambda text: text) + cli._print_nemoclaw_preset_hint( + lambda text: text, lambda text: text, lambda text: text + ) out = capsys.readouterr().out assert "NemoClaw detected" in out assert "allow your NemoClaw sandboxes to reach ClawMetry Cloud" in out assert helper in out + + +def test_pod_name_xml_escaping(): + from xml.sax.saxutils import escape + + malicious_pod = "test-pod&injectionattacker" + pod_xml = escape(malicious_pod) + docker_path = "/usr/bin/docker" + cluster = "openshell-cluster" + sync_script = "/usr/local/lib/python3.11/dist-packages/clawmetry/sync.py" + label = f"com.clawmetry.sandbox.{escape(malicious_pod)}" + + plist = f""" + + + + Label {label} + ProgramArguments + + {docker_path} + exec + {cluster} + kubectl + exec + -n + openshell + {pod_xml} + -- + python3 + {sync_script} + + RunAtLoad + KeepAlive + ThrottleInterval 30 + StandardOutPath /tmp/clawmetry-{pod_xml}.log + StandardErrorPath /tmp/clawmetry-{pod_xml}.log + +""" + + ET.fromstring(plist)