Skip to content

Commit a063c68

Browse files
authored
Merge pull request #7 from superagent-ai/homanp/remove-unsafe-to-test
feat: Remove unsafe-to-test runtime verdict
2 parents 34167e1 + 0692a06 commit a063c68

4 files changed

Lines changed: 79 additions & 53 deletions

File tree

pithos/runtime_artifacts.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@
1313
RUNTIME_NOT_REPRODUCED = "not_reproduced"
1414
RUNTIME_BLOCKED = "blocked"
1515
RUNTIME_INCONCLUSIVE = "inconclusive_runtime"
16-
RUNTIME_UNSAFE = "unsafe_to_test"
1716

1817
RUNTIME_STATUSES = {
1918
RUNTIME_CONFIRMED,
2019
RUNTIME_NOT_REPRODUCED,
2120
RUNTIME_BLOCKED,
2221
RUNTIME_INCONCLUSIVE,
23-
RUNTIME_UNSAFE,
2422
}
2523

2624

@@ -66,7 +64,6 @@ class VerificationPlan:
6664
preconditions: list[str] = field(default_factory=list)
6765
probe: dict[str, Any] = field(default_factory=dict)
6866
oracle: dict[str, Any] = field(default_factory=dict)
69-
unsafe: bool = False
7067

7168
def to_dict(self) -> dict[str, Any]:
7269
return asdict(self)

pithos/runtime_plugins.py

Lines changed: 6 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
RUNTIME_CONFIRMED,
2727
RUNTIME_INCONCLUSIVE,
2828
RUNTIME_NOT_REPRODUCED,
29-
RUNTIME_UNSAFE,
3029
ProbeArtifact,
3130
RuntimeFinding,
3231
RuntimeVerdict,
@@ -779,7 +778,7 @@ def make_plan(self, finding: RuntimeFinding) -> VerificationPlan:
779778
"Live verification was explicitly enabled.",
780779
"Runtime profile preflight completed successfully.",
781780
"The agent may run the project only in the copied sandbox workspace.",
782-
"External side effects require declared mocks or staging targets.",
781+
"Live verification uses configured staging, sandbox, mock, local, or loopback resources.",
783782
],
784783
probe={
785784
"kind": "agent_led_live_verification",
@@ -791,7 +790,6 @@ def make_plan(self, finding: RuntimeFinding) -> VerificationPlan:
791790
"confirmed_runtime": "The agent reproduced the unsafe behavior with live evidence.",
792791
"not_reproduced": "The live system rejected or prevented the unsafe behavior.",
793792
"inconclusive_runtime": "The agent could not reach a reliable runtime conclusion.",
794-
"unsafe_to_test": "The required reproduction would touch undeclared external side effects.",
795793
},
796794
)
797795

@@ -819,19 +817,6 @@ def verify(self, finding: RuntimeFinding, out_dir: Path) -> RuntimeVerdict:
819817
plan,
820818
"runtime environment preflight failed; see verify/RUNTIME-SETUP.md",
821819
)
822-
elif _unsafe_without_mock(self.profile, finding):
823-
verdict = RuntimeVerdict(
824-
finding_id=finding.id,
825-
title=finding.title,
826-
plugin=self.name,
827-
status=RUNTIME_UNSAFE,
828-
confidence="medium",
829-
evidence=[
830-
"The finding appears to require an external or destructive side effect.",
831-
"The runtime profile does not declare a safe mock or staging target for that effect.",
832-
],
833-
plan=plan.to_dict(),
834-
)
835820
else:
836821
try:
837822
verdict = self._run_live_agent(finding, finding_dir, plan)
@@ -1237,29 +1222,6 @@ def _route_from_files(files: list[str]) -> str | None:
12371222
return None
12381223

12391224

1240-
def _unsafe_without_mock(profile: AppRuntimeProfile, finding: RuntimeFinding) -> bool:
1241-
text = " ".join(
1242-
[
1243-
finding.title,
1244-
finding.category,
1245-
finding.exploit_scenario,
1246-
*finding.evidence,
1247-
*finding.files,
1248-
]
1249-
).lower()
1250-
external_keys = {
1251-
"stripe": ("stripe", "billing", "charge", "payment", "payout"),
1252-
"email": ("email", "smtp", "sendgrid", "mailgun"),
1253-
"aws": ("aws", "s3", "ses", "sns", "sqs"),
1254-
"webhook": ("webhook", "ssrf", "callback", "outbound"),
1255-
"production": ("production", "prod data", "real customer"),
1256-
}
1257-
for mock_name, needles in external_keys.items():
1258-
if any(needle in text for needle in needles) and not profile.mocks.get(mock_name):
1259-
return True
1260-
return False
1261-
1262-
12631225
def _local_pi_env(app: EphemeralAppSandbox, agent_cfg: dict[str, object]) -> dict[str, str]:
12641226
env = app.env()
12651227
env["PI_OFFLINE"] = "1"
@@ -1347,11 +1309,10 @@ def _live_agent_system_prompt() -> str:
13471309
Hard requirements:
13481310
- Do not modify the original source checkout. Work only inside /work/repo and /work/artifacts.
13491311
- Do not fix code, commit code, open PRs, or make unrelated changes.
1350-
- Do not contact real third-party services, production systems, billing providers, email providers,
1351-
cloud storage, webhook targets, or customer data unless the runtime profile explicitly declares a
1352-
safe mock or staging target.
1353-
- Prefer local tests, scripts, seeded data, local services, and loopback targets.
1354-
- If reproduction would be destructive or external, return unsafe_to_test.
1312+
- Use only the configured staging, sandbox, mock, local, or loopback resources. Do not contact
1313+
production systems or real customer data.
1314+
- Prefer local tests, scripts, seeded data, local services, and loopback targets when they provide
1315+
reliable evidence.
13551316
- Output exactly one <runtime_verdict_json>...</runtime_verdict_json> object.
13561317
"""
13571318

@@ -1384,7 +1345,7 @@ def _live_agent_prompt(profile: AppRuntimeProfile, finding: RuntimeFinding) -> s
13841345
Write any supporting evidence under /work/artifacts. Return only:
13851346
<runtime_verdict_json>
13861347
{{
1387-
"status": "confirmed_runtime|not_reproduced|inconclusive_runtime|blocked|unsafe_to_test",
1348+
"status": "confirmed_runtime|not_reproduced|inconclusive_runtime|blocked",
13881349
"confidence": "high|medium|low",
13891350
"evidence": [
13901351
"what you ran",

pithos/runtime_verifier.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ def status(self) -> str:
4040
return "runtime_findings_confirmed"
4141
if not self.profile.verification.get("execute_app"):
4242
return "completed_static_only"
43-
if any(
44-
v.status in {"blocked", "inconclusive_runtime", "unsafe_to_test"} for v in self.verdicts
45-
):
43+
if any(v.status in {"blocked", "inconclusive_runtime"} for v in self.verdicts):
4644
return "completed_with_unverified"
4745
return "completed"
4846

@@ -313,7 +311,6 @@ def _counts(verdicts: list[RuntimeVerdict]) -> dict[str, int]:
313311
"not_reproduced": 0,
314312
"blocked": 0,
315313
"inconclusive_runtime": 0,
316-
"unsafe_to_test": 0,
317314
}
318315
for v in verdicts:
319316
out[v.status] = out.get(v.status, 0) + 1
@@ -325,7 +322,7 @@ def _summary_status(verdicts: list[RuntimeVerdict], *, execute_app: bool) -> str
325322
return "runtime_findings_confirmed"
326323
if not execute_app:
327324
return "completed_static_only"
328-
if any(v.status in {"blocked", "inconclusive_runtime", "unsafe_to_test"} for v in verdicts):
325+
if any(v.status in {"blocked", "inconclusive_runtime"} for v in verdicts):
329326
return "completed_with_unverified"
330327
return "completed"
331328

tests/test_runtime_verifier.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,77 @@ def test_live_verify_runs_profile_agent_command(tmp_path):
360360
assert (results / "runtime" / "F001" / "agent-command.json").exists()
361361

362362

363+
def test_live_verify_runs_external_side_effect_findings_in_staging(tmp_path):
364+
repo = tmp_path / "app"
365+
repo.mkdir()
366+
(repo / "verify_agent.py").write_text(
367+
"""
368+
import json
369+
import os
370+
371+
with open(os.environ["PITHOS_FINDING_JSON"], encoding="utf-8") as f:
372+
finding = json.load(f)
373+
verdict = {
374+
"status": "not_reproduced",
375+
"confidence": "high",
376+
"evidence": [f"exercised staged payment flow for {finding['id']}"],
377+
}
378+
with open(os.environ["PITHOS_VERDICT_JSON"], "w", encoding="utf-8") as f:
379+
json.dump(verdict, f)
380+
""",
381+
encoding="utf-8",
382+
)
383+
profile = repo / ".pithos" / "runtime.yaml"
384+
profile.parent.mkdir()
385+
profile.write_text(
386+
f"""
387+
verification:
388+
execute_app: true
389+
agent_command: "{sys.executable} verify_agent.py"
390+
environment:
391+
sandbox: local
392+
""",
393+
encoding="utf-8",
394+
)
395+
results = tmp_path / "results" / "app" / "run"
396+
results.mkdir(parents=True)
397+
(results / "run-summary.json").write_text(json.dumps({"repo": {"path": str(repo)}}))
398+
(results / "VULN-FINDINGS.json").write_text(
399+
json.dumps(
400+
[
401+
{
402+
"id": "F001",
403+
"title": "Stripe charge endpoint can capture arbitrary staged payments",
404+
"category": "payment provider side effect",
405+
"files": ["src/payments.ts"],
406+
}
407+
]
408+
)
409+
)
410+
(results / "TRIAGE.json").write_text(
411+
json.dumps(
412+
{
413+
"findings": [
414+
{
415+
"id": "F001",
416+
"title": "Stripe charge endpoint can capture arbitrary staged payments",
417+
}
418+
]
419+
}
420+
)
421+
)
422+
423+
result = run_verify_repo(triage_path=results, execute_app=True)
424+
summary = json.loads((results / "runtime" / "runtime-summary.json").read_text())
425+
426+
assert result.status == "completed"
427+
assert result.verdicts[0].plugin == "live_agent"
428+
assert result.verdicts[0].status == "not_reproduced"
429+
assert summary["counts"]["not_reproduced"] == 1
430+
assert len(summary["counts"]) == 5
431+
assert (results / "runtime" / "F001" / "agent-command.json").exists()
432+
433+
363434
def test_live_verify_runs_pi_agent_locally_without_docker(tmp_path, monkeypatch):
364435
repo = tmp_path / "app"
365436
repo.mkdir()

0 commit comments

Comments
 (0)