User Story
As an operator running sandboxes across multiple workspaces on the VM driver, I want a stop, start, or delete request that carries a sandbox id to either act on that exact sandbox or fail, so that a retried delete never destroys an unrelated sandbox that happens to share its name.
Problem Statement
stop_sandbox, start_sandbox, and delete_sandbox on the VM driver prefer the sandbox_id, but when that id is not present in the registry they fall back to matching on sandbox_name instead of reporting the sandbox as absent.
crates/openshell-driver-vm/src/driver.rs:1392 (stop_sandbox; start_sandbox:1466 and delete_sandbox:1531 are the same shape):
let record_id = {
let registry = self.registry.lock().await;
if registry.contains_key(sandbox_id) {
Some(sandbox_id.to_string())
} else {
registry
.iter()
.find(|(_, record)| record.snapshot.name == sandbox_name)
.map(|(id, _)| id.clone())
}
};
The fallback fires whenever the id is missing — including when the caller supplied a perfectly valid one. Sandbox names are unique per workspace, not globally (crates/openshell-server/src/persistence/tests.rs:684, sqlite_name_unique_scoped_by_workspace), and the VM registry permits same-named entries: create_sandbox rejects duplicates by id only (driver.rs:843), never by name. SandboxRecord.snapshot.workspace is populated (driver.rs:6405) but is not consulted when matching, and HashMap::iter().find(...) selects arbitrarily when several names match.
The gRPC handlers (driver.rs:3923, :3933, :3943) forward sandbox_id and sandbox_name straight through with no id-match post-check. get_sandbox is not affected: it gates the fallback on sandbox_id.is_empty() (driver.rs:1614) and its handler verifies the resolved id afterward (driver.rs:3903).
The most ordinary trigger is a repeated delete. A successful delete_sandbox removes the registry entry, so a retried or duplicate DeleteSandbox carrying the same id and name finds no id, falls through to the name branch, and can delete a same-named sandbox in another workspace. Delete is intended to be idempotent — it returns deleted: false when nothing matches — and that is precisely the path that misfires. Gateway restart is not a reliable trigger: restore_persisted_sandboxes (driver.rs:1645) rehydrates the registry from disk on startup.
Impact / Why This Matters
A retried delete can destroy a live sandbox belonging to a different workspace. The caller receives deleted: true and no error, so the loss is silent, and because the fallback resolves through hash iteration order the outcome is not reproducible. Start and stop carry the same exposure with less severe consequences.
There is no caller-side workaround: the request already carries the correct id and the driver discards it once the id is absent. Avoiding the bug requires never reusing a sandbox name across workspaces on a VM-driver gateway, which contradicts the workspace-scoped naming the rest of the product guarantees.
Related: #3234 and #3240 address the same class of defect in the Docker driver; #3253 covers the MXC driver.
Acceptance Criteria
Reproduction Steps
Code inspection; not reproduced against a running VM-driver gateway.
- Create workspaces
alpha and beta on a gateway using the VM compute driver.
- Create a sandbox named
demo in alpha, and another named demo in beta. Both are accepted — names are unique per workspace, and the VM registry rejects duplicates by id only.
- Delete the
demo in beta. It succeeds and its registry entry is removed.
- Repeat the same
DeleteSandbox request (retry, duplicate delivery, or a second client call). Its id is no longer in the registry, so the driver falls back to the name demo and may delete the sandbox in alpha, returning deleted: true.
Environment
- OpenShell:
main at a0814443
- OS: Linux (libkrun-backed VM driver)
- Runtime, deployment, or integration: VM compute driver, gateway with more than one workspace
User Story
As an operator running sandboxes across multiple workspaces on the VM driver, I want a stop, start, or delete request that carries a sandbox id to either act on that exact sandbox or fail, so that a retried delete never destroys an unrelated sandbox that happens to share its name.
Problem Statement
stop_sandbox,start_sandbox, anddelete_sandboxon the VM driver prefer thesandbox_id, but when that id is not present in the registry they fall back to matching onsandbox_nameinstead of reporting the sandbox as absent.crates/openshell-driver-vm/src/driver.rs:1392(stop_sandbox;start_sandbox:1466anddelete_sandbox:1531are the same shape):The fallback fires whenever the id is missing — including when the caller supplied a perfectly valid one. Sandbox names are unique per workspace, not globally (
crates/openshell-server/src/persistence/tests.rs:684,sqlite_name_unique_scoped_by_workspace), and the VM registry permits same-named entries:create_sandboxrejects duplicates by id only (driver.rs:843), never by name.SandboxRecord.snapshot.workspaceis populated (driver.rs:6405) but is not consulted when matching, andHashMap::iter().find(...)selects arbitrarily when several names match.The gRPC handlers (
driver.rs:3923,:3933,:3943) forwardsandbox_idandsandbox_namestraight through with no id-match post-check.get_sandboxis not affected: it gates the fallback onsandbox_id.is_empty()(driver.rs:1614) and its handler verifies the resolved id afterward (driver.rs:3903).The most ordinary trigger is a repeated delete. A successful
delete_sandboxremoves the registry entry, so a retried or duplicateDeleteSandboxcarrying the same id and name finds no id, falls through to the name branch, and can delete a same-named sandbox in another workspace. Delete is intended to be idempotent — it returnsdeleted: falsewhen nothing matches — and that is precisely the path that misfires. Gateway restart is not a reliable trigger:restore_persisted_sandboxes(driver.rs:1645) rehydrates the registry from disk on startup.Impact / Why This Matters
A retried delete can destroy a live sandbox belonging to a different workspace. The caller receives
deleted: trueand no error, so the loss is silent, and because the fallback resolves through hash iteration order the outcome is not reproducible. Start and stop carry the same exposure with less severe consequences.There is no caller-side workaround: the request already carries the correct id and the driver discards it once the id is absent. Avoiding the bug requires never reusing a sandbox name across workspaces on a VM-driver gateway, which contradicts the workspace-scoped naming the rest of the product guarantees.
Related: #3234 and #3240 address the same class of defect in the Docker driver; #3253 covers the MXC driver.
Acceptance Criteria
sandbox_id,stop_sandbox,start_sandbox, anddelete_sandboxresolve on that id alone and do not fall back to the name.Reproduction Steps
Code inspection; not reproduced against a running VM-driver gateway.
alphaandbetaon a gateway using the VM compute driver.demoinalpha, and another nameddemoinbeta. Both are accepted — names are unique per workspace, and the VM registry rejects duplicates by id only.demoinbeta. It succeeds and its registry entry is removed.DeleteSandboxrequest (retry, duplicate delivery, or a second client call). Its id is no longer in the registry, so the driver falls back to the namedemoand may delete the sandbox inalpha, returningdeleted: true.Environment
mainata0814443