00d42267e5
- Executors and relay helpers get an environment without any configured Plane token / webhook secret (design 8: executors hold no Plane credentials). - Windows runner: Set-Location -LiteralPath, and a failed cd writes exit=97 instead of running the agent in the wrong directory. - schtasks launch: escape apostrophes and pass the script path as \"...\" so paths with spaces survive PowerShell 5.1 native quoting. - State and heartbeat files are written temp + fsync + rename. - Tests for the above plus codex run/resume command shapes and unique run dirs.
123 lines
5.8 KiB
Python
123 lines
5.8 KiB
Python
"""Transport hardening: secrets never reach executors, Windows script quoting, command shapes, atomic state files."""
|
|
import json
|
|
import os
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from agentd import ROOT, config
|
|
from agentd.config import Config
|
|
from agentd.executors import Run, make_executor
|
|
from agentd.store import Store
|
|
from agentd.transports.local import LocalTransport
|
|
from agentd.transports.warpgate_ssh import WarpgateSshTransport
|
|
from tests.fakes import config_data
|
|
|
|
|
|
class RecordingWin(WarpgateSshTransport):
|
|
"""Windows transport with the relay replaced by an in-memory recorder."""
|
|
def __init__(self, cfg, terminal):
|
|
super().__init__(cfg, terminal)
|
|
self.files, self.launched = {}, []
|
|
|
|
def mkdir(self, path, clean=False):
|
|
pass
|
|
|
|
def write_text(self, path, text):
|
|
self.files[path] = text
|
|
|
|
def launch(self, task, script_path, run_dir):
|
|
self.launched.append((task, script_path, run_dir))
|
|
|
|
|
|
class LocalSecretsTest(unittest.TestCase):
|
|
def test_executor_env_has_no_plane_tokens(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
data = config_data()
|
|
data["agentd"]["state_dir"] = str(Path(tmp) / "state")
|
|
data["terminals"]["oregon"]["repo"] = tmp
|
|
data["projects"]["GWENT"]["webhook_secret_key"] = "HOOK_GW"
|
|
cfg = Config(data)
|
|
self.assertEqual(cfg.secret_keys(), {"TOK_OR", "TOK_W10", "HOOK_GW"})
|
|
t = LocalTransport(cfg, "oregon")
|
|
ex = make_executor(cfg, "oregon", "shell", t)
|
|
script = 'echo "or=${TOK_OR:-none} w10=${TOK_W10:-none} hook=${HOOK_GW:-none} keep=${KEEP_ME:-none}"'
|
|
with mock.patch.dict(os.environ, {"TOK_OR": "s1", "TOK_W10": "s2", "HOOK_GW": "s3", "KEEP_ME": "yes"}):
|
|
h = ex.start(Run("GW-9-1", tmp, "", script=script))
|
|
end = time.time() + 10
|
|
while ex.poll(h) != "finished" and time.time() < end:
|
|
time.sleep(0.05)
|
|
self.assertEqual(ex.collect(h).last_message, "or=none w10=none hook=none keep=yes")
|
|
|
|
|
|
class WindowsScriptTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.cfg = config.load(ROOT / "config" / "agentd.example.yaml")
|
|
self.t = RecordingWin(self.cfg, "win10")
|
|
self.ex = make_executor(self.cfg, "win10", "codex", self.t)
|
|
|
|
def test_codex_run_and_resume_commands(self):
|
|
run_dir = self.t.join(self.ex.run_root(), "GW-5-1")
|
|
fresh = self.ex.command(Run("GW-5-1", self.t.repo, "p", "gpt-6-astra", "high"), run_dir)
|
|
self.assertTrue(fresh.startswith("& 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\OpenAI\\Codex\\bin\\codex.exe' exec --json"))
|
|
self.assertIn("-C '%s' -s workspace-write" % self.t.repo, fresh)
|
|
self.assertIn("-o '%s\\last.md'" % run_dir, fresh)
|
|
resumed = self.ex.command(Run("GW-5-2", self.t.repo, "p", "gpt-6-astra", "high", thread_id="th-9"), run_dir)
|
|
self.assertIn(" exec resume th-9 --json -m gpt-6-astra", resumed)
|
|
self.assertNotIn(" -C ", resumed) # `codex exec resume` rejects -C/-s; the runner already cd's into the repo
|
|
self.assertNotIn(" -s ", resumed)
|
|
tiered = self.ex.command(Run("GW-5-3", self.t.repo, "p", "gpt-6.1-sol", "medium", tier="fast"), run_dir)
|
|
self.assertIn("-c 'service_tier=\"fast\"'", tiered)
|
|
|
|
def test_run_dirs_are_unique_per_run(self):
|
|
a = self.ex.start(Run("GW-5-1", self.t.repo, "p", "m", "e"))
|
|
b = self.ex.start(Run("GW-5-2", self.t.repo, "p", "m", "e"))
|
|
self.assertNotEqual(a["run_dir"], b["run_dir"])
|
|
self.assertEqual([x[0] for x in self.t.launched], ["agentd-GW-5-1", "agentd-GW-5-2"])
|
|
self.assertTrue(a["run_dir"].endswith("\\runtime\\agentd\\GW-5-1"))
|
|
|
|
def test_runner_quotes_paths_with_spaces_and_apostrophes(self):
|
|
_, script = self.t.runner(r"C:\a b\O'Neil\run", r"C:\my repo\O'Neil", "& 'C:\\x\\codex.exe' exec -", "events.jsonl")
|
|
self.assertIn(r"Set-Location -LiteralPath 'C:\my repo\O''Neil' -ErrorAction Stop", script)
|
|
self.assertIn(r"'C:\a b\O''Neil\run\prompt.md'", script)
|
|
self.assertIn(r"'C:\a b\O''Neil\run\exit.txt' -Value ('exit=97", script) # cd failure still ends the run
|
|
self.assertIn(r"Add-Content -Path 'C:\a b\O''Neil\run\events.jsonl'", script)
|
|
|
|
def test_launch_script_quoting(self):
|
|
s = self.t.launch_script("agentd-GW-1-1", r"C:\My Docs\O'Neil\run.ps1")
|
|
self.assertIn(r"-File \"C:\My Docs\O''Neil\run.ps1\"'", s)
|
|
self.assertIn("/TN 'agentd-GW-1-1'", s)
|
|
|
|
def test_relay_env_has_no_plane_tokens(self):
|
|
seen = {}
|
|
|
|
def fake_run(args, **kw):
|
|
seen.update(kw["env"])
|
|
return mock.Mock(returncode=0, stdout="X=ok", stderr="")
|
|
with mock.patch.dict(os.environ, {"PLANE_WINDOW10_API_TOKEN": "s"}), \
|
|
mock.patch("agentd.transports.warpgate_ssh.subprocess.run", fake_run):
|
|
WarpgateSshTransport(self.cfg, "win10").ps("'X=ok'")
|
|
self.assertNotIn("PLANE_WINDOW10_API_TOKEN", seen)
|
|
self.assertIn("WIN10_TIMEOUT", seen)
|
|
|
|
|
|
class StoreTest(unittest.TestCase):
|
|
def test_save_is_atomic_and_heartbeat_is_complete_json(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
st = Store(tmp, echo=False)
|
|
st.save({"projects": {"A": {"issues": {}}}})
|
|
with mock.patch("agentd.store.os.replace", side_effect=OSError("disk full")):
|
|
with self.assertRaises(OSError):
|
|
st.save({"projects": {"B": {}}})
|
|
self.assertIn("A", st.load()["projects"]) # old state survives a failed write
|
|
st.heartbeat({"errors": [], "inflight": []})
|
|
hb = json.loads((Path(tmp) / "heartbeat.json").read_text())
|
|
self.assertEqual(hb["errors"], [])
|
|
self.assertIn("ts", hb)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|