Skip to content

Commit 1483e54

Browse files
committed
Improve skills code review agent safety and samples
1 parent 0e43659 commit 1483e54

11 files changed

Lines changed: 552 additions & 98 deletions

File tree

examples/skills_code_review_agent/agent/filter_policy.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
r"curl\s+[^|]+\|\s*(sh|bash)",
2626
r"wget\s+[^|]+\|\s*(sh|bash)",
2727
r"rm\s+-rf\s+/",
28+
r"rm\s+-rf\s+(\.|\*)",
29+
r"\bgit\s+clean\s+-[A-Za-z]*[fdx][A-Za-z]*",
2830
r"docker\s+run\s+.*--privileged",
31+
r"\bdd\s+.*\bof=",
32+
r"\bmkfs(?:\.[A-Za-z0-9_+-]+)?\b",
2933
r"\bsudo\b",
3034
r"^\s*(sh|bash|zsh|fish|dash|ksh)\b",
3135
r"[;&|`<>]",

examples/skills_code_review_agent/agent/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class SandboxRun:
117117
timed_out: bool = False
118118
output_truncated: bool = False
119119
exception_type: str | None = None
120+
redaction_count: int = 0
120121

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

examples/skills_code_review_agent/agent/pipeline.py

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .models import FilterDecision
1414
from .models import Finding
1515
from .models import ReviewReport
16+
from .models import SandboxRun
1617
from .models import utc_now
1718
from .redaction import redact_text
1819
from .reporting import render_markdown
@@ -98,6 +99,89 @@ def mark_stage(name: str, stage_start: float) -> None:
9899
diff_text=diff.diff_text)
99100
mark_stage("storage_create_task", storage_start)
100101

102+
try:
103+
return await _run_review_after_task_created(
104+
start=start,
105+
stage_durations_ms=stage_durations_ms,
106+
task_id=task_id,
107+
created_at=created_at,
108+
diff=diff,
109+
input_redactions=input_redactions,
110+
skill_audit=skill_audit,
111+
review_store=review_store,
112+
output_dir=output_dir,
113+
sandbox=sandbox,
114+
dry_run=dry_run,
115+
container_image=container_image,
116+
docker_path=docker_path,
117+
docker_base_url=docker_base_url,
118+
cube_template=cube_template,
119+
cube_api_url=cube_api_url,
120+
cube_api_key=cube_api_key,
121+
cube_sandbox_id=cube_sandbox_id,
122+
timeout_sec=timeout_sec,
123+
max_output_bytes=max_output_bytes,
124+
filter_timeout_budget_sec=filter_timeout_budget_sec,
125+
filter_max_output_bytes=filter_max_output_bytes,
126+
network_policy=network_policy,
127+
test_command=test_command,
128+
custom_rule_script=custom_rule_script,
129+
include_network_scanners=include_network_scanners,
130+
sandbox_runner=sandbox_runner,
131+
)
132+
except Exception as exc:
133+
redacted = redact_text(str(exc))
134+
review_store.fail_task(
135+
task_id,
136+
completed_at=utc_now(),
137+
exception_type=exc.__class__.__name__,
138+
message=redacted.text,
139+
)
140+
raise
141+
142+
143+
async def _run_review_after_task_created(
144+
*,
145+
start: float,
146+
stage_durations_ms: dict[str, int],
147+
task_id: str,
148+
created_at: str,
149+
diff: DiffInput,
150+
input_redactions: int,
151+
skill_audit: dict,
152+
review_store: ReviewStore,
153+
output_dir: Path,
154+
sandbox: str,
155+
dry_run: bool,
156+
container_image: str,
157+
docker_path: str | None,
158+
docker_base_url: str | None,
159+
cube_template: str | None,
160+
cube_api_url: str | None,
161+
cube_api_key: str | None,
162+
cube_sandbox_id: str | None,
163+
timeout_sec: float,
164+
max_output_bytes: int,
165+
filter_timeout_budget_sec: float,
166+
filter_max_output_bytes: int,
167+
network_policy: str,
168+
test_command: str | None,
169+
custom_rule_script: str | None,
170+
include_network_scanners: bool,
171+
sandbox_runner: SandboxRunner | None,
172+
) -> ReviewReport:
173+
174+
def mark_stage(name: str, stage_start: float) -> None:
175+
stage_durations_ms[name] = int((time.monotonic() - stage_start) * 1000)
176+
177+
skill_audit["sdk_skill_runtime"] = {
178+
"executed": False,
179+
"reason": (
180+
"SDK skill_load/skill_run smoke is available through --skill-smoke; normal reviews do not execute "
181+
"local workspace runtime before Filter approval."
182+
),
183+
}
184+
101185
filter_start = time.monotonic()
102186
sandbox_requests, request_build_decisions, request_build_redactions = _build_sandbox_requests_for_review(
103187
timeout_sec=timeout_sec,
@@ -143,7 +227,7 @@ def mark_stage(name: str, stage_start: float) -> None:
143227
sandbox_runs = []
144228
sandbox_start = time.monotonic()
145229
for request in allowed_requests:
146-
run = await runner.run(request, diff, skill_dir=SKILL_DIR)
230+
run = await _run_sandbox_request_safely(runner, request, diff)
147231
sandbox_runs.append(run)
148232
mark_stage("sandbox", sandbox_start)
149233
storage_start = time.monotonic()
@@ -164,7 +248,7 @@ def mark_stage(name: str, stage_start: float) -> None:
164248
needs_human_review,
165249
)
166250
deduped_finding_count += merged_deduped_count
167-
sandbox_redactions = sum(redact_text(run.stdout).count + redact_text(run.stderr).count for run in sandbox_runs)
251+
sandbox_redactions = sum(run.redaction_count for run in sandbox_runs)
168252
mark_stage("rules", rules_start)
169253
storage_start = time.monotonic()
170254
review_store.save_findings(task_id, "finding", findings)
@@ -212,6 +296,7 @@ def mark_stage(name: str, stage_start: float) -> None:
212296
"filter_max_output_bytes": filter_max_output_bytes,
213297
"env_whitelist": sorted(ENV_WHITELIST),
214298
"network_policy": network_policy,
299+
"network_enforcement": _network_enforcement_summary(runner.runtime_name),
215300
},
216301
filter_policy=filter_policy.audit(),
217302
input=diff.to_dict(),
@@ -242,6 +327,31 @@ def mark_stage(name: str, stage_start: float) -> None:
242327
return report
243328

244329

330+
async def _run_sandbox_request_safely(
331+
runner: SandboxRunner,
332+
request: SandboxRequest,
333+
diff: DiffInput,
334+
) -> SandboxRun:
335+
start = time.monotonic()
336+
try:
337+
return await runner.run(request, diff, skill_dir=SKILL_DIR)
338+
except Exception as exc: # pylint: disable=broad-except
339+
redacted = redact_text(str(exc))
340+
return SandboxRun(
341+
name=request.name,
342+
runtime=runner.runtime_name,
343+
command=request.command,
344+
status="failed",
345+
exit_code=None,
346+
duration_ms=int((time.monotonic() - start) * 1000),
347+
stdout="",
348+
stderr=redacted.text[:request.max_output_bytes],
349+
exception_type=exc.__class__.__name__,
350+
output_truncated=len(redacted.text) > request.max_output_bytes,
351+
redaction_count=redacted.count,
352+
)
353+
354+
245355
def query_task(db_path: Path, task_id: str) -> dict:
246356
return SQLiteReviewStore(db_path).get_task_bundle(task_id)
247357

@@ -456,6 +566,24 @@ def _build_conclusion(findings, warnings, needs_human_review, sandbox_runs, filt
456566
return "No blocking issues detected by the offline review pipeline."
457567

458568

569+
def _network_enforcement_summary(runtime_name: str) -> str:
570+
if runtime_name == "container":
571+
return (
572+
"container host_config sets network_mode=none; network-backed scanners require a separately approved "
573+
"runtime image/policy."
574+
)
575+
if runtime_name == "cube":
576+
return (
577+
"Filter gates requested network domains; per-run egress enforcement must be provided by the Cube/E2B "
578+
"workspace policy."
579+
)
580+
if runtime_name == "fake":
581+
return "fake runtime simulates scanner behavior without network access."
582+
if runtime_name == "local":
583+
return "local runtime is development fallback only and does not enforce network isolation."
584+
return "custom runtime must enforce network isolation according to the active Filter policy."
585+
586+
459587
def _dedupe_finding_buckets(
460588
findings: list[Finding],
461589
warnings: list[Finding],
@@ -506,14 +634,16 @@ def _findings_from_scanner_runs(sandbox_runs,
506634
needs_human_review: list[Finding] = []
507635
redactions = 0
508636
for run in sandbox_runs:
509-
if run.name != "scanner_probe" or not run.stdout.strip().startswith("{"):
637+
if not run.stdout.strip().startswith("{"):
510638
continue
511639
try:
512640
import json
513641

514642
payload = json.loads(run.stdout)
515643
except Exception:
516644
continue
645+
if "scanner_runs" not in payload:
646+
continue
517647
for scanner_run in payload.get("scanner_runs", []):
518648
for item in scanner_run.get("findings", []):
519649
evidence = str(item.get("evidence", ""))

examples/skills_code_review_agent/agent/runtime_factory.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ def create_container_sandbox_runner(*,
1919
from trpc_agent_sdk.code_executors.container import ContainerConfig
2020
from trpc_agent_sdk.code_executors.container import create_container_workspace_runtime
2121

22-
config = ContainerConfig(image=image, docker_path=docker_path or "", base_url=base_url or "")
22+
config = ContainerConfig(
23+
image=image,
24+
docker_path=docker_path or "",
25+
base_url=base_url or "",
26+
host_config={"network_mode": "none"},
27+
)
2328
runtime = create_container_workspace_runtime(container_config=config)
2429
return build_workspace_sandbox_runner(runtime, "container")
2530

0 commit comments

Comments
 (0)