mirror of
https://github.com/microsoft/SkillOpt.git
synced 2026-08-03 07:02:46 +08:00
Guard Cursor file-edit rollouts with sandboxing
This commit is contained in:
@@ -205,7 +205,9 @@ directly through this backend.
|
||||
Cursor Agent first, then select it with `model.target_backend=cursor_exec`.
|
||||
Read-only rollouts use Ask mode; artifact-producing rollouts add Cursor's
|
||||
headless `--force` flag inside the benchmark workspace. SkillOpt enables the
|
||||
Cursor sandbox by default and does not approve MCP servers automatically.
|
||||
Cursor sandbox by default and rejects file-edit rollouts if it is disabled;
|
||||
read-only Ask-mode rollouts may explicitly disable it. SkillOpt does not approve
|
||||
MCP servers automatically.
|
||||
|
||||
### Three OpenAI-compatible paths
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
> **Version note.** This reference tracks `main`. PyPI 0.2.0 does not yet
|
||||
> include the generic research `openai_compatible` backend, Sleep handoff,
|
||||
> Sleep support for non-Azure OpenAI-compatible endpoints, or the Sleep
|
||||
> Sleep support for non-Azure OpenAI-compatible endpoints, the Sleep
|
||||
> `--preferences` flag, or the research `cursor_exec` target harness; use a
|
||||
> source install from `main` for those
|
||||
> features until the next release.
|
||||
> source install from `main` for those features until the next release.
|
||||
|
||||
## Training
|
||||
|
||||
@@ -108,8 +107,10 @@ python scripts/eval_only.py \
|
||||
`cursor_exec` runs the target only; the optimizer remains separately
|
||||
configured. Read-only rollouts use Cursor Ask mode. Rollouts that request file
|
||||
edits use `--force` inside the benchmark workspace, with Cursor sandboxing
|
||||
enabled by default. Override the executable or sandbox through
|
||||
`model.cursor_exec_path` and `model.cursor_exec_sandbox`.
|
||||
enabled. The harness refuses file-edit rollouts when the Cursor sandbox is
|
||||
disabled. Read-only Ask-mode rollouts may explicitly disable it. Override the
|
||||
executable or sandbox through `model.cursor_exec_path` and
|
||||
`model.cursor_exec_sandbox`.
|
||||
|
||||
## SkillOpt-Sleep
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ defaults to `claude` and can be overridden with `CLAUDE_CLI_BIN`.
|
||||
| `model.codex_exec_*` | Codex path, sandbox, profile, SDK mode, reasoning, network/search, and approval policy |
|
||||
| `model.claude_code_exec_*` | Claude path, profile, SDK mode, effort, and thinking-token cap |
|
||||
| `model.cursor_exec_path` | Cursor Agent executable path; default `cursor-agent` |
|
||||
| `model.cursor_exec_sandbox` | Cursor sandbox mode: `enabled` (default) or `disabled` |
|
||||
| `model.cursor_exec_sandbox` | Cursor sandbox mode: `enabled` (default) or `disabled`; file-edit rollouts require `enabled` |
|
||||
|
||||
## Training (`train`)
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ from skillopt.model.backend_config import (
|
||||
get_target_backend,
|
||||
)
|
||||
|
||||
|
||||
ANSWER_SCHEMA: dict[str, Any] = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1212,6 +1211,11 @@ def run_cursor_exec(
|
||||
actual_sandbox = str(sandbox or config["sandbox"])
|
||||
if actual_sandbox not in {"enabled", "disabled"}:
|
||||
raise ValueError("Cursor Agent sandbox must be 'enabled' or 'disabled'")
|
||||
if allow_file_edits and actual_sandbox == "disabled":
|
||||
raise ValueError(
|
||||
"Cursor Agent file-edit rollouts require sandbox='enabled'; "
|
||||
"refusing to combine --force with a disabled sandbox"
|
||||
)
|
||||
|
||||
for attempt in range(retries + 1):
|
||||
attempt_prompt = _exec_prompt(
|
||||
|
||||
@@ -201,6 +201,33 @@ def test_cursor_exec_force_is_limited_to_file_edit_rollouts(
|
||||
assert "You may modify files" in prompt
|
||||
|
||||
|
||||
def test_cursor_exec_rejects_file_edits_with_disabled_sandbox(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
work_dir = _workspace(tmp_path)
|
||||
calls = 0
|
||||
|
||||
def fake_run(_cmd: list[str], **_kwargs: Any) -> SimpleNamespace:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
return SimpleNamespace(returncode=0, stdout=_result(), stderr="")
|
||||
|
||||
backend_config.configure_cursor_exec(sandbox="disabled")
|
||||
monkeypatch.setattr(harness.subprocess, "run", fake_run)
|
||||
|
||||
with pytest.raises(ValueError, match="refusing to combine --force"):
|
||||
harness.run_cursor_exec(
|
||||
work_dir=str(work_dir),
|
||||
prompt="Write solution.py.",
|
||||
model="composer-2.5",
|
||||
timeout=10,
|
||||
allow_file_edits=True,
|
||||
)
|
||||
|
||||
assert calls == 0
|
||||
|
||||
|
||||
def test_cursor_exec_retries_zero_exit_malformed_output(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
|
||||
Reference in New Issue
Block a user