Skip to content

Commit b9828e0

Browse files
committed
fix(dashboard): allow shell to bypass lease check like screenshot
Dashboard is an administrative interface; both shell and screenshot should be usable on any sandbox regardless of current lease holder. - run_shell: pass require_lease=False to get_observer_booter_by_id - Update test to verify admin shell access bypasses session ownership
1 parent 21f7c59 commit b9828e0

2 files changed

Lines changed: 19 additions & 5 deletions

File tree

astrbot/dashboard/routes/sandbox.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,10 @@ async def run_shell(self, sandbox_id: str):
160160
command = str(data.get("command") or "").strip()
161161
if not command:
162162
return jsonify(Response().error("command is required").__dict__)
163+
# Dashboard shell access is an administrative operation; it does
164+
# not need a lease so admins can operate any sandbox at any time.
163165
booter = await computer_client.sandbox_manager.get_observer_booter_by_id(
164-
sandbox_id, self._session_id()
166+
sandbox_id, self._session_id(), require_lease=False
165167
)
166168
shell = getattr(booter, "shell", None)
167169
if shell is None:

tests/test_dashboard.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -520,15 +520,25 @@ async def available():
520520

521521

522522
@pytest.mark.asyncio
523-
async def test_sandbox_dashboard_shell_respects_session_ownership(
523+
async def test_sandbox_dashboard_shell_bypasses_lease_for_admin_access(
524524
app: Quart,
525525
authenticated_header: dict,
526526
monkeypatch: pytest.MonkeyPatch,
527527
):
528+
"""Dashboard shell is an administrative operation and must bypass lease."""
528529
from astrbot.core.computer import computer_client
529530
from astrbot.core.computer.sandbox_manager import SandboxManager
530531
from astrbot.core.computer.sandbox_registry import SandboxRegistry
531532

533+
class FakeShell:
534+
async def exec(self, command, cwd=None, env=None, timeout=300, shell=True):
535+
return {
536+
"command": command,
537+
"stdout": "ok\n",
538+
"stderr": "",
539+
"exit_code": 0,
540+
}
541+
532542
async def available():
533543
return True
534544

@@ -551,7 +561,9 @@ async def available():
551561
lease_expires_at=9999999999,
552562
connect_info={"name": "Sandbox 1"},
553563
)
554-
manager.session_booter["sandbox-1"] = SimpleNamespace(available=available)
564+
manager.session_booter["sandbox-1"] = SimpleNamespace(
565+
available=available, shell=FakeShell()
566+
)
555567

556568
test_client = app.test_client()
557569
response = await test_client.post(
@@ -562,8 +574,8 @@ async def available():
562574
data = await response.get_json()
563575

564576
assert response.status_code == 200
565-
assert data["status"] == "error"
566-
assert "controlled by another session" in data["message"]
577+
assert data["status"] == "ok"
578+
assert data["data"]["result"]["stdout"] == "ok\n"
567579

568580

569581
@pytest.mark.asyncio

0 commit comments

Comments
 (0)