Skip to content

Commit 9f5659f

Browse files
committed
Enforce Daytona computer-use evidence
1 parent 179bdfe commit 9f5659f

3 files changed

Lines changed: 159 additions & 6 deletions

File tree

pithos/runtime_plugins.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from .computer_use import (
2424
COMPUTER_USE_DAYTONA,
2525
COMPUTER_USE_NONE,
26+
DAYTONA_COMMANDS,
2627
computer_use_agent_env,
2728
install_computer_use_commands,
2829
normalize_computer_use_mode,
@@ -1048,6 +1049,25 @@ def _verdict_from_agent_result(
10481049
)
10491050
verdict = _verdict_from_payload(finding, plan, verdict_data, plugin=self.name)
10501051
verdict.artifacts["transcript"] = str(finding_dir / "live-agent-transcript.jsonl")
1052+
if _profile_computer_use_mode(self.profile) == COMPUTER_USE_DAYTONA and not (
1053+
_agent_result_has_daytona_command(result)
1054+
):
1055+
return RuntimeVerdict(
1056+
finding_id=finding.id,
1057+
title=finding.title,
1058+
plugin=self.name,
1059+
status=RUNTIME_BLOCKED,
1060+
confidence="low",
1061+
evidence=[
1062+
"Daytona computer use was requested, but the live-agent transcript did not "
1063+
"include a bash tool call invoking any cu-* command.",
1064+
"The verifier requires computer-use evidence before accepting a Daytona "
1065+
"live-agent verdict.",
1066+
],
1067+
error="missing Daytona computer-use evidence",
1068+
artifacts={"transcript": str(finding_dir / "live-agent-transcript.jsonl")},
1069+
plan=plan.to_dict(),
1070+
)
10511071
return verdict
10521072

10531073
def _blocked(
@@ -1280,6 +1300,25 @@ def _localize_live_agent_paths(text: str, *, repo_path: Path, artifacts_path: Pa
12801300
)
12811301

12821302

1303+
def _agent_result_has_daytona_command(result) -> bool:
1304+
for msg in getattr(result, "messages", []) or []:
1305+
if msg.get("role") != "assistant":
1306+
continue
1307+
content = msg.get("content")
1308+
if not isinstance(content, list):
1309+
continue
1310+
for block in content:
1311+
if not isinstance(block, dict) or block.get("type") != "toolCall":
1312+
continue
1313+
if block.get("name") != "bash":
1314+
continue
1315+
args = block.get("arguments")
1316+
command = args.get("command") if isinstance(args, dict) else ""
1317+
if any(cmd in str(command) for cmd in DAYTONA_COMMANDS):
1318+
return True
1319+
return False
1320+
1321+
12831322
def _extract_verdict_payload(text: str) -> dict[str, object] | None:
12841323
raw = parse_xml_tag(text, "runtime_verdict_json") or text.strip()
12851324
if raw.startswith("```"):
@@ -1359,8 +1398,11 @@ def _live_agent_system_prompt(computer_use: str = COMPUTER_USE_NONE) -> str:
13591398
- Do not start Xvfb, XFCE, x11vnc, noVNC, or a nested Daytona sandbox yourself.
13601399
- Use bash-accessible local commands to control the existing desktop:
13611400
`cu-info`, `cu-screenshot`, `cu-click`, `cu-type`, `cu-key`, `cu-scroll`, and `cu-drag`.
1362-
- Take a screenshot before and after desktop actions, and use the image evidence to decide
1363-
whether the action affected the expected screen.
1401+
- Your first bash actions must run `cu-info` and `cu-screenshot` before reading source, installing
1402+
dependencies, running tests, or writing reproduction scripts.
1403+
- Take another screenshot after desktop/UI actions, and use the image evidence to decide whether the
1404+
action affected the expected screen.
1405+
- Include the `cu-*` commands you ran and their artifact paths in the final runtime verdict evidence.
13641406
- Treat coordinates as screenshot/API-space coordinates when display scaling is reported.
13651407
- If UI actions do not affect the expected screen, check `DISPLAY`, run `cu-info`, and use
13661408
diagnostics such as `xdpyinfo` before trying more actions.

pithos/runtime_verifier.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ def _environment_preflight(
554554
)
555555
if execute_app and computer_use_mode == COMPUTER_USE_DAYTONA:
556556
if backend != "local":
557-
warnings.append(
557+
issues.append(
558558
"Daytona computer use controls the outer Superagent desktop; use "
559559
"--sandbox-mode local so live agents can run the generated cu-* commands "
560560
"in that environment"

tests/test_runtime_verifier.py

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,15 +273,35 @@ def test_runtime_preflight_records_daytona_computer_use(tmp_path):
273273
allow_inferred_runtime=True,
274274
provider="test-provider",
275275
model="test-model",
276-
sandbox_mode="docker",
276+
sandbox_mode="local",
277277
computer_use="daytona",
278278
)
279279

280280
assert result.preflight["computer_use"] == "daytona"
281+
assert result.preflight["ready"] is True
281282
assert result.preflight["computer_use_daytona"]["local_api_url"] == DAYTONA_LOCAL_API_URL
282283
assert "cu-screenshot" in result.preflight["computer_use_daytona"]["commands"]
283284
assert result.profile.verification["computer_use"] == "daytona"
284-
assert any("Daytona computer use controls the outer" in w for w in result.preflight["warnings"])
285+
286+
287+
def test_runtime_preflight_rejects_daytona_without_local_runtime(tmp_path):
288+
repo = tmp_path / "app"
289+
repo.mkdir()
290+
out_dir = tmp_path / "results" / "app" / "run" / "verify"
291+
292+
result = run_runtime_preflight(
293+
repo_path=repo,
294+
results_dir=out_dir,
295+
execute_app=True,
296+
allow_inferred_runtime=True,
297+
provider="test-provider",
298+
model="test-model",
299+
sandbox_mode="docker",
300+
computer_use="daytona",
301+
)
302+
303+
assert result.preflight["ready"] is False
304+
assert any("Daytona computer use controls the outer" in i for i in result.preflight["issues"])
285305

286306

287307
def test_runtime_preflight_reports_discovered_env_sources(tmp_path, monkeypatch):
@@ -709,11 +729,23 @@ async def fake_run_agent_process(prompt, **kwargs):
709729
{
710730
"status": "not_reproduced",
711731
"confidence": "high",
712-
"evidence": ["ran Daytona computer-use verification"],
732+
"evidence": ["ran cu-info and cu-screenshot before verification"],
713733
}
714734
)
715735
return SimpleNamespace(
716736
error=None,
737+
messages=[
738+
{
739+
"role": "assistant",
740+
"content": [
741+
{
742+
"type": "toolCall",
743+
"name": "bash",
744+
"arguments": {"command": "cu-info && cu-screenshot before.png"},
745+
}
746+
],
747+
}
748+
],
717749
last_assistant_message=payload,
718750
find_tagged_message=lambda _tag: payload,
719751
)
@@ -740,6 +772,85 @@ async def fake_run_agent_process(prompt, **kwargs):
740772
assert summary["environment"]["computer_use"] == "daytona"
741773

742774

775+
def test_daytona_computer_use_blocks_live_agent_without_cu_evidence(tmp_path, monkeypatch):
776+
repo = tmp_path / "app"
777+
repo.mkdir()
778+
profile = repo / ".pithos" / "runtime.yaml"
779+
profile.parent.mkdir()
780+
profile.write_text(
781+
"""
782+
verification:
783+
execute_app: true
784+
environment:
785+
sandbox: local
786+
""",
787+
encoding="utf-8",
788+
)
789+
results = tmp_path / "results" / "app" / "run"
790+
results.mkdir(parents=True)
791+
(results / "run-summary.json").write_text(json.dumps({"repo": {"path": str(repo)}}))
792+
(results / "VULN-FINDINGS.json").write_text(
793+
json.dumps(
794+
[
795+
{
796+
"id": "F001",
797+
"title": "Generic live authorization bypass",
798+
"category": "authorization bypass",
799+
"files": ["src/server.ts"],
800+
}
801+
]
802+
)
803+
)
804+
(results / "TRIAGE.json").write_text(
805+
json.dumps({"findings": [{"id": "F001", "title": "Generic live authorization bypass"}]})
806+
)
807+
808+
async def fake_run_agent_process(prompt, **kwargs):
809+
payload = json.dumps(
810+
{
811+
"status": "not_reproduced",
812+
"confidence": "high",
813+
"evidence": ["ran source-only verification"],
814+
}
815+
)
816+
return SimpleNamespace(
817+
error=None,
818+
messages=[
819+
{
820+
"role": "assistant",
821+
"content": [
822+
{
823+
"type": "toolCall",
824+
"name": "bash",
825+
"arguments": {"command": "npm test"},
826+
}
827+
],
828+
}
829+
],
830+
last_assistant_message=payload,
831+
find_tagged_message=lambda _tag: payload,
832+
)
833+
834+
monkeypatch.setattr(runtime_plugins, "run_agent_process", fake_run_agent_process)
835+
monkeypatch.setattr(
836+
runtime_plugins.docker_ops,
837+
"run",
838+
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("docker was called")),
839+
)
840+
841+
result = run_verify_repo(
842+
triage_path=results,
843+
execute_app=True,
844+
provider="anthropic",
845+
model="claude-sonnet-4-5",
846+
sandbox_mode="local",
847+
computer_use="daytona",
848+
)
849+
850+
assert result.verdicts[0].status == "blocked"
851+
assert result.verdicts[0].error == "missing Daytona computer-use evidence"
852+
853+
743854
def test_verify_repo_confirms_polar_style_source_oracles(tmp_path):
744855
repo = tmp_path / "polar"
745856
(repo / "server/polar/oauth2/grants").mkdir(parents=True)

0 commit comments

Comments
 (0)