Skip to content

feat(volumes): add hotmount and blockmount volume types#64

Open
vedranjukic wants to merge 1 commit into
mainfrom
feat/volume-types
Open

feat(volumes): add hotmount and blockmount volume types#64
vedranjukic wants to merge 1 commit into
mainfrom
feat/volume-types

Conversation

@vedranjukic

@vedranjukic vedranjukic commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Description

Syncs the clients monorepo with the API's volume-types feature: regenerates all API clients from the updated OpenAPI spec and adds the hand-written SDK, CLI, and example support for the three volume types (legacy, hotmount, blockmount).

Spec

  • Updates openapi-specs/api.json and regenerates all six API clients (api-client TS, api-client-go, api-client-python, api-client-python-async, api-client-ruby, api-client-java).
  • New models: VolumeType, HotmountRegion, BlockmountConflict, CreateVolumeMountToken, VolumeMountTokenDto; type/region/sizeInGb/shared fields on Volume/CreateVolume/SandboxVolume and blockmountEnabled on Region; new endpoints GET /volumes/hotmount-regions, GET /volumes/blockmount-regions, POST /volumes/{volumeId}/mount-token.

SDKs (TypeScript / Python / Go / Ruby / Java)

  • Volume / VolumeType surface and typed create-with-type/region.
  • Region discovery: volume.listHotmountRegions() / listBlockmountRegions().
  • sandbox.mountVolume(volume, path) — mounts a hotmount volume on the fly: mints a scoped mount token and bootstraps the in-sandbox FUSE agent from the region's init.sh (fails loudly if curl/download is missing).
  • sandbox.pullVolumes(volume?) — explicit blockmount sync over the toolbox channel: commits the sandbox's local writes and applies the volume's latest merged state in place, no restart.

CLI

  • volume create type/region handling and type/region columns in list/info.

Examples

  • New examples/typescript/blockmount (concurrent two-writer merge + explicit pull demo) and examples/typescript/hotmount (mount-on-the-fly + multi-writer propagation).

Depends on the companion API PR in daytonaio/daytona-ai (branch feat/volume-types-refactor-prod), which serves the spec and endpoints these clients target.

Documentation

  • This change requires a documentation update
  • I have made corresponding changes to the documentation

Notes

  • Regenerated clients are byte-for-byte from openapi-specs/api.json — the diff is limited to the volume feature (no version churn or unrelated drift).
  • Verified end-to-end against a live stack using this branch's SDK: the blockmount example (two sandboxes concurrently mount one volume, converge via explicit pulls to the union of files, overlapping file resolved last-change-wins on both sides, conflict surfaced on the manifest) and the hotmount example (mount-on-the-fly in two sandboxes with multi-writer propagation) both pass.
  • Builds verified: TS SDK, all Go modules (api-client-go, sdk-go, cli), Python (compile + editable install), Ruby (syntax), Java SDK (compileJava offline).

Summary by cubic

Adds hotmount and blockmount volume types across API clients, SDKs, and the CLI to enable mount-on-the-fly and explicit sync workflows. Regenerates clients from the updated OpenAPI spec with region discovery and mount token support.

  • New Features
    • OpenAPI/spec and clients: new endpoints /volumes/hotmount-regions, /volumes/blockmount-regions, /volumes/{volumeId}/mount-token; new models (VolumeType, HotmountRegion, BlockmountConflict, CreateVolumeMountToken, VolumeMountTokenDto); added fields on Volume/CreateVolume/SandboxVolume and blockmountEnabled on Region. Regenerated api-client (TS), api-client-go, api-client-python, api-client-python-async, api-client-ruby, api-client-java.
    • SDKs: create volumes with type/region; list hotmount/blockmount regions; in-sandbox mountVolume for hotmount (mints token and bootstraps agent); pullVolumes for blockmount to apply latest state without restart.
    • CLI: volume create supports --type and --region; volume list/info show type, region, size, and shared.
    • Examples: added examples/typescript/blockmount and examples/typescript/hotmount.

Written for commit 0d86632. Summary will update on new commits.

Review in cubic

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution! Before we can merge it, we need you to sign the Daytona Contributor License Agreement. You only need to do this once, and it will cover all of your future contributions to this repository. If you are signing on behalf of an entity (for example, your employer), please also reply with the entity's legal name and your role or title, and confirm that you are authorized to bind it. Please read the CLA and, if you agree, post the following comment on this PR:


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

Sync clients with the API volume-types refactor: regenerate all API
clients (TS, Go, Python, Python-async, Ruby, Java) from the updated
openapi-specs/api.json, and add the hand-written SDK/CLI/example support.

Spec additions: VolumeType, HotmountRegion, BlockmountConflict,
CreateVolumeMountToken, VolumeMountTokenDto; region/size/type fields on
Volume/CreateVolume/SandboxVolume/Region; and the /volumes/hotmount-regions,
/volumes/blockmount-regions and /volumes/{volumeId}/mount-token endpoints.

SDKs: Volume/VolumeType surface, Sandbox.mountVolume (hotmount, on-the-fly
token bootstrap) and Sandbox.pullVolumes (blockmount explicit pull) across
all languages; CLI volume create/list/info; new blockmount and hotmount
TypeScript examples.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

34 issues found across 113 files

You’re at about 90% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="openapi-specs/api.json">

<violation number="1" location="openapi-specs/api.json:19805">
P3: Blockmount conflict metadata loses type safety because `winner` and `reason` document finite wire values but model them as unrestricted strings. Generated SDK users can therefore pass or receive values that the schema does not describe, and cannot exhaustively handle the known cases. Defining enums for `winner` (`ours`, `theirs`) and `reason` (`mtime`, `tie`, `modify-over-delete`, `type`) would make the generated models match the API contract.</violation>

<violation number="2" location="openapi-specs/api.json:20025">
P2: Mount-token expiration is exposed as an untyped string in the generated clients because `expiresAt` omits the `date-time` format, even though the value is an ISO timestamp. This differs from the existing timestamp schemas and prevents clients such as TypeScript and Go from generating their normal date/time types. Adding `format: date-time` keeps this response consistent with the rest of the API.</violation>
</file>

<file name="examples/typescript/hotmount/index.ts">

<violation number="1" location="examples/typescript/hotmount/index.ts:44">
P2: When no hotmount region is available, this continues with `region === undefined` and attempts to create the demo volume without the required placement, producing a later and less actionable API failure (or unintended default placement). As in the blockmount example, handling the empty list before selecting a region would give users the correct configuration error.</violation>

<violation number="2" location="examples/typescript/hotmount/index.ts:65">
P2: A failure after either sandbox is created leaves billable sandbox resources running because cleanup is not protected by `finally`. Track the created sandboxes and delete them in a `finally` block, including the partial-creation case.</violation>

<violation number="3" location="examples/typescript/hotmount/index.ts:92">
P2: If the second sandbox never sees the shared file, the example still exits successfully after logging `<not visible yet>`. That masks hotmount synchronization or mounting failures in manual/automated verification; after the retry loop, an undefined `contents` should throw instead of being treated as a successful demo result.</violation>
</file>

<file name="sdk-go/pkg/daytona/sandbox.go">

<violation number="1" location="sdk-go/pkg/daytona/sandbox.go:489">
P2: `MountVolume` accepts relative mount paths even though its contract requires an absolute sandbox path. Validating `mountPath` before building the command would prevent mounts landing in unintended directories and provide a clearer error.</violation>

<violation number="2" location="sdk-go/pkg/daytona/sandbox.go:588">
P1: Mounting can execute unintended shell fragments because the generated `env` assignments interpolate unescaped values into single-quoted shell strings. `mountPath` is caller-provided and is inserted into `SEAWEED_MOUNT_DIR` without escaping, so a `'` in the path can break quoting and change the executed command. Escaping shell values before formatting the command would prevent both injection and quote-related mount failures.</violation>
</file>

<file name="sdk-go/pkg/types/types.go">

<violation number="1" location="sdk-go/pkg/types/types.go:147">
P3: The SDK comment currently marks `Volume.Region` as hotmount-only, but the API returns region for blockmount volumes too. Updating this comment avoids consumers treating valid blockmount region metadata as absent/irrelevant.</violation>
</file>

<file name="sdk-ruby/lib/daytona/sandbox.rb">

<violation number="1" location="sdk-ruby/lib/daytona/sandbox.rb:360">
P3: The new volume APIs are not instrumented, so mount/pull calls won’t emit Sandbox telemetry like other public operations. Adding `:mount_volume` and `:pull_volumes` to `instrument(...)` keeps observability consistent.</violation>

<violation number="2" location="sdk-ruby/lib/daytona/sandbox.rb:366">
P2: Forked Sandbox objects will always fail `mount_volume` with “Volume service is not available” because the fork constructor path does not pass `volume_service`. Propagating the parent volume service in `experimental_fork` would keep volume features working after fork.</violation>

<violation number="3" location="sdk-ruby/lib/daytona/sandbox.rb:712">
P1: Mounting can execute unintended shell fragments when the mount path (or token-derived value) contains a single quote, because `build_hotmount_mount_command` inserts values into shell assignments without escaping. Escaping shell values before building `env` assignments would prevent command injection in `sandbox.mount_volume`.</violation>
</file>

<file name="cli/cmd/volume/create.go">

<violation number="1" location="cli/cmd/volume/create.go:35">
P1: Invalid `--type` values are not rejected here: the generated `NewVolumeTypeFromValue` returns the unknown-default sentinel (`"11184809"`) with a nil error for unrecognized strings. As a result, typos are sent to the API rather than producing the CLI's advertised validation error. Validate explicitly against the three user-facing `VolumeType` constants before assigning `createVolume.Type`.</violation>

<violation number="2" location="cli/cmd/volume/create.go:42">
P2: `--region` can currently be supplied without a non-legacy `--type`, producing a legacy request even though `CreateVolume.Region` is explicitly disallowed for legacy volumes. Local validation would return an actionable CLI error before making a request that the API must reject.</violation>

<violation number="3" location="cli/cmd/volume/create.go:62">
P3: The checked-in command reference now tells users to pass the removed `--size` flag and omits both new flags. Regenerating `cli/docs/daytona_volume_create.md` alongside these registrations would keep published CLI usage accurate.</violation>
</file>

<file name="cli/views/volume/list.go">

<violation number="1" location="cli/views/volume/list.go:31">
P3: The volume list still omits the region even though region is now a key placement attribute and this change is intended to add type/region to list output. Only `Type` was added to the headers and row data, while `RenderInfo` does show `volume.Region`. Consider adding a `Region` column (using `-` when the nullable field is absent) so hotmount/blockmount placement is visible without inspecting every volume.</violation>
</file>

<file name="sdk-java/src/main/java/io/daytona/sdk/Sandbox.java">

<violation number="1" location="sdk-java/src/main/java/io/daytona/sdk/Sandbox.java:492">
P1: A mount path containing a single quote is interpreted as shell syntax inside the Sandbox because `appendEnvVar` inserts values verbatim between single quotes. For example, a crafted `mountPath` can terminate `SEAWEED_MOUNT_DIR='...'` and append another command when `process.executeCommand` runs it. Escaping embedded single quotes (for every value, including token DTO fields) before constructing the command would keep these values as data.</violation>
</file>

<file name="sdk-python/src/daytona/common/volume.py">

<violation number="1" location="sdk-python/src/daytona/common/volume.py:40">
P2: Hotmount fails for valid paths containing `'`, and crafted values can inject shell syntax into the sandbox command. Quote each value with `shlex.quote()` instead of manually surrounding it with apostrophes.</violation>
</file>

<file name="sdk-java/src/main/java/io/daytona/sdk/model/Volume.java">

<violation number="1" location="sdk-java/src/main/java/io/daytona/sdk/model/Volume.java:19">
P3: Returned volumes lose the SDK's `VolumeType` type safety because this public field is exposed as `String`, allowing invalid values and forcing callers to use literals. Consider exposing `VolumeType` consistently with `VolumeService.create` and preserving the enum in `toVolume`.</violation>

<violation number="2" location="sdk-java/src/main/java/io/daytona/sdk/model/Volume.java:88">
P3: Blockmount volumes also return a region, so this public getter documentation incorrectly tells users to expect `null`. Describe it as the volume data/deployment region and reserve `null` for legacy volumes.</violation>
</file>

<file name="sdk-python/src/daytona/_async/sandbox.py">

<violation number="1" location="sdk-python/src/daytona/_async/sandbox.py:259">
P2: Relative `mount_path` values are accepted even though this API requires an absolute sandbox path, causing the mount location to depend on the toolbox process working directory. Validate the path before requesting the token or executing the mount command.</violation>

<violation number="2" location="sdk-python/src/daytona/_async/sandbox.py:260">
P1: A crafted `mount_path` can execute arbitrary shell commands inside the sandbox because `build_hotmount_mount_command` interpolates it into a single-quoted shell assignment without escaping. Quote every environment value with `shlex.quote` in the command builder before passing this user-controlled value to `_process.exec`.</violation>
</file>

<file name="sdk-python/src/daytona/_sync/sandbox.py">

<violation number="1" location="sdk-python/src/daytona/_sync/sandbox.py:276">
P1: A crafted `mount_path` can inject arbitrary shell commands into the Sandbox because `build_hotmount_mount_command` embeds it in a single-quoted assignment without shell escaping. Shell-quote every interpolated environment value (for example with `shlex.quote`) before passing the command to `Process.exec`.</violation>
</file>

<file name="sdk-python/src/daytona/_sync/volume.py">

<violation number="1" location="sdk-python/src/daytona/_sync/volume.py:51">
P3: `get()` now gives users the opposite blockmount contract: `region` is optional and selects the region-local store; it does not pin attachment to that region. Aligning this text with `create()` avoids callers adding unnecessary region-selection logic.</violation>

<violation number="2" location="sdk-python/src/daytona/_sync/volume.py:108">
P3: The return-value documentation advertises `HotmountRegion.id`, which does not exist and would raise `AttributeError` if consumers follow it. The field is named `region`, as the example below already shows.</violation>
</file>

<file name="sdk-python/src/daytona/_async/volume.py">

<violation number="1" location="sdk-python/src/daytona/_async/volume.py:51">
P3: `get(..., create=True, region=...)` now documents blockmount `region` as required and as an attach pin, contradicting both `create()` and the API contract where it is optional and only controls data placement. Aligning this text avoids steering SDK users toward incorrect creation and attachment assumptions.</violation>

<violation number="2" location="sdk-python/src/daytona/_async/volume.py:106">
P3: The new async volume-type parameters and three API wrappers have no unit coverage, so argument-order, forwarding, or await regressions can pass the SDK test suite. Tests that assert the exact generated-client calls and returned values would protect this new public surface.</violation>
</file>

<file name="examples/typescript/blockmount/index.ts">

<violation number="1" location="examples/typescript/blockmount/index.ts:34">
P2: A transient refresh, authentication, or server failure is treated as proof that teardown finished, so the example may try deleting a volume while its sandbox is still active. The catch should return only for `DaytonaNotFoundError` and rethrow every other error.</violation>

<violation number="2" location="examples/typescript/blockmount/index.ts:74">
P2: A partial sandbox-creation failure leaves the successful sandbox (and volume) running because `Promise.all` rejects before either reference is assigned. Tracking each result (for example with `Promise.allSettled`) and cleaning created resources in `finally` would prevent leaked billable resources.</violation>

<violation number="3" location="examples/typescript/blockmount/index.ts:88">
P2: The demo's expected B-wins result is flaky when the two runners' clocks differ by more than this delay, because merge order is based on runner-local mtimes rather than client request order. Setting an explicit ordered mtime inside the sandboxes, or avoiding an asserted winner, would make the example deterministic.</violation>
</file>

<file name="sdk-typescript/src/Sandbox.ts">

<violation number="1" location="sdk-typescript/src/Sandbox.ts:64">
P0: A mount path containing a single quote can break out of the environment assignment and execute arbitrary shell commands, potentially as root via `sudo`. Shell-escape every assignment value before building the command (including API-provided token fields).</violation>

<violation number="2" location="sdk-typescript/src/Sandbox.ts:183">
P2: Hotmount mounting is unavailable on every Sandbox returned by `_experimental_fork()` because that constructor call does not propagate `this.volumeService`. Pass the service into the forked `Sandbox` just as `Daytona.create/get/list` do.</violation>

<violation number="3" location="sdk-typescript/src/Sandbox.ts:277">
P3: Relative or empty `mountPath` values are accepted despite the absolute-path contract, so the volume can be mounted under the process working directory instead of the caller's intended location. Validate that `mountPath` starts with `/` and raise `DaytonaValidationError` otherwise.</violation>
</file>

<file name="sdk-go/pkg/daytona/volume.go">

<violation number="1" location="sdk-go/pkg/daytona/volume.go:118">
P3: The new Create parameter docs understate region support and can mislead users selecting blockmount placement. Wording this as a general region option would match current behavior and type docs.</violation>

<violation number="2" location="sdk-go/pkg/daytona/volume.go:175">
P2: GetMountToken can panic on invalid input because `volume.ID` is dereferenced unconditionally. Adding a nil guard keeps this path in normal error handling instead of crashing the caller.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

}
const envAssignments = Object.entries(envVars)
.filter(([, value]) => value !== undefined && value !== '')
.map(([key, value]) => `${key}='${value}'`)

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: A mount path containing a single quote can break out of the environment assignment and execute arbitrary shell commands, potentially as root via sudo. Shell-escape every assignment value before building the command (including API-provided token fields).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-typescript/src/Sandbox.ts, line 64:

<comment>A mount path containing a single quote can break out of the environment assignment and execute arbitrary shell commands, potentially as root via `sudo`. Shell-escape every assignment value before building the command (including API-provided token fields).</comment>

<file context>
@@ -41,6 +41,43 @@ import { ComputerUse } from './ComputerUse'
+  }
+  const envAssignments = Object.entries(envVars)
+    .filter(([, value]) => value !== undefined && value !== '')
+    .map(([key, value]) => `${key}='${value}'`)
+    .join(' ')
+
</file context>
Suggested change
.map(([key, value]) => `${key}='${value}'`)
.map(([key, value]) => `${key}='${String(value).replace(/'/g, "'\\''")}'`)
Fix with cubic

raise DaytonaError("Volume service is not available for this Sandbox instance.")

mount_token = await self._volume_service.get_mount_token(volume)
command = build_hotmount_mount_command(mount_token, mount_path)

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: A crafted mount_path can execute arbitrary shell commands inside the sandbox because build_hotmount_mount_command interpolates it into a single-quoted shell assignment without escaping. Quote every environment value with shlex.quote in the command builder before passing this user-controlled value to _process.exec.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-python/src/daytona/_async/sandbox.py, line 260:

<comment>A crafted `mount_path` can execute arbitrary shell commands inside the sandbox because `build_hotmount_mount_command` interpolates it into a single-quoted shell assignment without escaping. Quote every environment value with `shlex.quote` in the command builder before passing this user-controlled value to `_process.exec`.</comment>

<file context>
@@ -217,6 +226,85 @@ async def get_work_dir(self) -> str:
+            raise DaytonaError("Volume service is not available for this Sandbox instance.")
+
+        mount_token = await self._volume_service.get_mount_token(volume)
+        command = build_hotmount_mount_command(mount_token, mount_path)
+        response = await self._process.exec(command)
+        if response.exit_code != 0:
</file context>
Fix with cubic

raise DaytonaError("Volume service is not available for this Sandbox instance.")

mount_token = self._volume_service.get_mount_token(volume)
command = build_hotmount_mount_command(mount_token, mount_path)

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: A crafted mount_path can inject arbitrary shell commands into the Sandbox because build_hotmount_mount_command embeds it in a single-quoted assignment without shell escaping. Shell-quote every interpolated environment value (for example with shlex.quote) before passing the command to Process.exec.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-python/src/daytona/_sync/sandbox.py, line 276:

<comment>A crafted `mount_path` can inject arbitrary shell commands into the Sandbox because `build_hotmount_mount_command` embeds it in a single-quoted assignment without shell escaping. Shell-quote every interpolated environment value (for example with `shlex.quote`) before passing the command to `Process.exec`.</comment>

<file context>
@@ -233,6 +242,85 @@ def get_work_dir(self) -> str:
+            raise DaytonaError("Volume service is not available for this Sandbox instance.")
+
+        mount_token = self._volume_service.get_mount_token(volume)
+        command = build_hotmount_mount_command(mount_token, mount_path)
+        response = self._process.exec(command)
+        if response.exit_code != 0:
</file context>
Fix with cubic

Comment thread cli/cmd/volume/create.go
Comment on lines +35 to +39
volumeType, err := apiclient.NewVolumeTypeFromValue(typeFlag)
if err != nil {
return fmt.Errorf("invalid volume type %q: must be one of legacy, hotmount, blockmount", typeFlag)
}
createVolume.Type = volumeType

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Invalid --type values are not rejected here: the generated NewVolumeTypeFromValue returns the unknown-default sentinel ("11184809") with a nil error for unrecognized strings. As a result, typos are sent to the API rather than producing the CLI's advertised validation error. Validate explicitly against the three user-facing VolumeType constants before assigning createVolume.Type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cli/cmd/volume/create.go, line 35:

<comment>Invalid `--type` values are not rejected here: the generated `NewVolumeTypeFromValue` returns the unknown-default sentinel (`"11184809"`) with a nil error for unrecognized strings. As a result, typos are sent to the API rather than producing the CLI's advertised validation error. Validate explicitly against the three user-facing `VolumeType` constants before assigning `createVolume.Type`.</comment>

<file context>
@@ -27,9 +27,23 @@ var CreateCmd = &cobra.Command{
+		}
+
+		if typeFlag != "" {
+			volumeType, err := apiclient.NewVolumeTypeFromValue(typeFlag)
+			if err != nil {
+				return fmt.Errorf("invalid volume type %q: must be one of legacy, hotmount, blockmount", typeFlag)
</file context>
Suggested change
volumeType, err := apiclient.NewVolumeTypeFromValue(typeFlag)
if err != nil {
return fmt.Errorf("invalid volume type %q: must be one of legacy, hotmount, blockmount", typeFlag)
}
createVolume.Type = volumeType
volumeType := apiclient.VolumeType(typeFlag)
switch volumeType {
case apiclient.VOLUMETYPE_LEGACY, apiclient.VOLUMETYPE_HOTMOUNT, apiclient.VOLUMETYPE_BLOCKMOUNT:
createVolume.Type = &volumeType
default:
return fmt.Errorf("invalid volume type %q: must be one of legacy, hotmount, blockmount", typeFlag)
}
Fix with cubic

'SEAWEED_VERSION' => mount_token.version
}
env_assignments = env_vars.reject { |_, value| value.nil? || value.empty? }
.map { |key, value| "#{key}='#{value}'" }

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Mounting can execute unintended shell fragments when the mount path (or token-derived value) contains a single quote, because build_hotmount_mount_command inserts values into shell assignments without escaping. Escaping shell values before building env assignments would prevent command injection in sandbox.mount_volume.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-ruby/lib/daytona/sandbox.rb, line 712:

<comment>Mounting can execute unintended shell fragments when the mount path (or token-derived value) contains a single quote, because `build_hotmount_mount_command` inserts values into shell assignments without escaping. Escaping shell values before building `env` assignments would prevent command injection in `sandbox.mount_volume`.</comment>

<file context>
@@ -624,6 +692,40 @@ def pause(timeout: DEFAULT_TIMEOUT)
+        'SEAWEED_VERSION' => mount_token.version
+      }
+      env_assignments = env_vars.reject { |_, value| value.nil? || value.empty? }
+                                .map { |key, value| "#{key}='#{value}'" }
+                                .join(' ')
+      # The bootstrap (and init.sh itself) requires curl. Fail loudly if it is missing or the
</file context>
Suggested change
.map { |key, value| "#{key}='#{value}'" }
.map { |key, value| "#{key}='#{value.to_s.gsub("'", %q('"'"'))}'" }
Fix with cubic

* await sandbox.mountVolume(volume, "/mnt/shared");
*/
@WithInstrumentation()
public async mountVolume(volume: Volume, mountPath: string): Promise<void> {

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Relative or empty mountPath values are accepted despite the absolute-path contract, so the volume can be mounted under the process working directory instead of the caller's intended location. Validate that mountPath starts with / and raise DaytonaValidationError otherwise.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-typescript/src/Sandbox.ts, line 277:

<comment>Relative or empty `mountPath` values are accepted despite the absolute-path contract, so the volume can be mounted under the process working directory instead of the caller's intended location. Validate that `mountPath` starts with `/` and raise `DaytonaValidationError` otherwise.</comment>

<file context>
@@ -215,6 +253,86 @@ export class Sandbox {
+   * await sandbox.mountVolume(volume, "/mnt/shared");
+   */
+  @WithInstrumentation()
+  public async mountVolume(volume: Volume, mountPath: string): Promise<void> {
+    if (volume.type !== VolumeType.HOTMOUNT) {
+      throw new DaytonaValidationError(
</file context>
Suggested change
public async mountVolume(volume: Volume, mountPath: string): Promise<void> {
public async mountVolume(volume: Volume, mountPath: string): Promise<void> {
if (!mountPath.startsWith('/')) {
throw new DaytonaValidationError('Hotmount mount path must be absolute.')
}
Fix with cubic

//
// Parameters:
// - name: Unique name for the volume
// - opts: Optional volume type and hotmount region.

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new Create parameter docs understate region support and can mislead users selecting blockmount placement. Wording this as a general region option would match current behavior and type docs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-go/pkg/daytona/volume.go, line 118:

<comment>The new Create parameter docs understate region support and can mislead users selecting blockmount placement. Wording this as a general region option would match current behavior and type docs.</comment>

<file context>
@@ -115,6 +115,7 @@ func (v *VolumeService) Get(ctx context.Context, name string) (*types.Volume, er
 //
 // Parameters:
 //   - name: Unique name for the volume
+//   - opts: Optional volume type and hotmount region.
 //
 // Example:
</file context>
Suggested change
// - opts: Optional volume type and hotmount region.
// - opts: Optional volume type and region.
Fix with cubic

Comment thread sdk-go/pkg/types/types.go
OrganizationID string `json:"organizationId"`
Type string `json:"type"`
SizeInGb *float32 `json:"sizeInGb,omitempty"`
// Region is the hotmount region the volume lives in. Set only for hotmount volumes.

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The SDK comment currently marks Volume.Region as hotmount-only, but the API returns region for blockmount volumes too. Updating this comment avoids consumers treating valid blockmount region metadata as absent/irrelevant.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-go/pkg/types/types.go, line 147:

<comment>The SDK comment currently marks `Volume.Region` as hotmount-only, but the API returns region for blockmount volumes too. Updating this comment avoids consumers treating valid blockmount region metadata as absent/irrelevant.</comment>

<file context>
@@ -139,14 +139,84 @@ type PaginatedSnapshots struct {
+	OrganizationID string   `json:"organizationId"`
+	Type           string   `json:"type"`
+	SizeInGb       *float32 `json:"sizeInGb,omitempty"`
+	// Region is the hotmount region the volume lives in. Set only for hotmount volumes.
+	Region *string `json:"region,omitempty"`
+	// Shared is the hotmount sharing mode. Set only for hotmount volumes.
</file context>
Fix with cubic

# @example
# volume = daytona.volume.get('shared-fs')
# sandbox.mount_volume(volume, '/mnt/shared')
def mount_volume(volume, mount_path)

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new volume APIs are not instrumented, so mount/pull calls won’t emit Sandbox telemetry like other public operations. Adding :mount_volume and :pull_volumes to instrument(...) keeps observability consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-ruby/lib/daytona/sandbox.rb, line 360:

<comment>The new volume APIs are not instrumented, so mount/pull calls won’t emit Sandbox telemetry like other public operations. Adding `:mount_volume` and `:pull_volumes` to `instrument(...)` keeps observability consistent.</comment>

<file context>
@@ -338,6 +340,72 @@ def update_env(env: nil, unset: nil)
+    # @example
+    #   volume = daytona.volume.get('shared-fs')
+    #   sandbox.mount_volume(volume, '/mnt/shared')
+    def mount_volume(volume, mount_path)
+      unless volume.type == DaytonaApiClient::VolumeType::HOTMOUNT
+        raise Sdk::Error,
</file context>
Fix with cubic

Comment thread cli/views/volume/list.go
SortVolumes(&volumeList)

headers := []string{"Volume", "State", "Size", "Created"}
headers := []string{"Volume", "Type", "State", "Size", "Created"}

@cubic-dev-ai cubic-dev-ai Bot Jul 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The volume list still omits the region even though region is now a key placement attribute and this change is intended to add type/region to list output. Only Type was added to the headers and row data, while RenderInfo does show volume.Region. Consider adding a Region column (using - when the nullable field is absent) so hotmount/blockmount placement is visible without inspecting every volume.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cli/views/volume/list.go, line 31:

<comment>The volume list still omits the region even though region is now a key placement attribute and this change is intended to add type/region to list output. Only `Type` was added to the headers and row data, while `RenderInfo` does show `volume.Region`. Consider adding a `Region` column (using `-` when the nullable field is absent) so hotmount/blockmount placement is visible without inspecting every volume.</comment>

<file context>
@@ -27,7 +28,7 @@ func ListVolumes(volumeList []apiclient.VolumeDto, activeOrganizationName *strin
 	SortVolumes(&volumeList)
 
-	headers := []string{"Volume", "State", "Size", "Created"}
+	headers := []string{"Volume", "Type", "State", "Size", "Created"}
 
 	data := [][]string{}
</file context>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant