Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions crates/openshell-core/src/driver_mounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ pub fn validate_container_mount_target(target: &str) -> Result<(), String> {
if !target.starts_with('/') {
return Err("mount target must be an absolute container path".to_string());
}
if target != "/" {
let segments = target.split('/').skip(1).collect::<Vec<_>>();
let has_internal_empty_segment = segments
.iter()
.take(segments.len().saturating_sub(1))
.any(|segment| segment.is_empty());
if has_internal_empty_segment || segments.iter().any(|segment| *segment == ".") {
return Err(
"mount target must be normalized and must not contain empty path segments or '.'"
.to_string(),
);
}
}
let path = Path::new(target);
if path == Path::new("/") {
return Err("mount target must not be the container root".to_string());
Expand Down Expand Up @@ -165,4 +178,17 @@ mod tests {
"mount target must not contain surrounding whitespace"
);
}

#[test]
fn mount_target_rejects_internal_empty_or_dot_segments() {
assert_eq!(
validate_container_mount_target("/sandbox/work//tmp").unwrap_err(),
"mount target must be normalized and must not contain empty path segments or '.'"
);
assert_eq!(
validate_container_mount_target("/sandbox/work/./tmp").unwrap_err(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What bout /sandbox/work/../../tmp?

"mount target must be normalized and must not contain empty path segments or '.'"
);
validate_container_mount_target("/sandbox/work/").unwrap();
}
}
33 changes: 33 additions & 0 deletions crates/openshell-driver-docker/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,39 @@ fn driver_config_allows_explicit_writable_volume_mounts() {
assert_eq!(mounts[0].read_only, Some(false));
}

#[test]
fn driver_config_rejects_duplicate_mount_targets() {
let mut sandbox = test_sandbox();
sandbox
.spec
.as_mut()
.unwrap()
.template
.as_mut()
.unwrap()
.driver_config = Some(json_struct(serde_json::json!({
"mounts": [
{
"type": "volume",
"source": "work-nfs",
"target": "/sandbox/work"
},
{
"type": "tmpfs",
"target": "/sandbox/work"
}
]
})));

let err = build_container_create_body(&sandbox, &runtime_config()).unwrap_err();

assert_eq!(err.code(), tonic::Code::FailedPrecondition);
assert!(
err.message()
.contains("duplicate docker driver_config mount target")
);
}

#[test]
fn driver_config_rejects_bind_mounts_unless_enabled() {
let mut sandbox = test_sandbox();
Expand Down
54 changes: 54 additions & 0 deletions crates/openshell-driver-kubernetes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ nested schema and currently accepts:
- `pod.priority_class_name`
- `containers.agent.resources.requests`
- `containers.agent.resources.limits`
- `containers.agent.volume_mounts[].name`
- `containers.agent.volume_mounts[].mount_path`
- `containers.agent.volume_mounts[].sub_path`
- `containers.agent.volume_mounts[].read_only`
- `volumes[].name`
- `volumes[].persistent_volume_claim.claim_name`
- `volumes[].persistent_volume_claim.read_only`

Nested keys inside the `kubernetes` block use snake_case. The top-level
`driver_config` envelope is keyed by driver names, so `kubernetes` is not part
Expand All @@ -104,3 +111,50 @@ driver's configured `default_runtime_class_name`; the typed public
public `--gpu` flag for the default GPU request, pass a count to `--gpu` for
counted GPU requests, and use `driver_config` only for additional driver-owned
resource details.

Use PVC volumes to mount existing Kubernetes PersistentVolumeClaims into the
agent container. PVC volumes and mounts default to read-only unless
`read_only: false` is set explicitly. Read-write access requires
`read_only: false` on both the PVC volume and each writable mount. The driver
rejects duplicate volume names, invalid DNS-1123 volume labels or PVC claim
subdomain names, mounts that reference unknown volumes, non-normalized or
protected mount paths, and absolute or parent-traversing `sub_path` values.

Any explicit driver-config mount under `/sandbox` disables the driver's
default `/sandbox` workspace PVC injection for that sandbox. Only the explicit
mount paths persist through the external PVC; other `/sandbox` paths come from
the current sandbox image.

```shell
openshell sandbox create \
--driver-config-json '{
"kubernetes": {
"volumes": [{
"name": "user-data",
"persistent_volume_claim": {
"claim_name": "pvc-user-data-123",
"read_only": false
}
}],
"containers": {
"agent": {
"volume_mounts": [
{
"name": "user-data",
"mount_path": "/sandbox/.openshell/workspace",
"sub_path": "workspace",
"read_only": false
},
{
"name": "user-data",
"mount_path": "/sandbox/.openshell/memory",
"sub_path": "memory",
"read_only": false
}
]
}
}
}
}' \
-- claude
```
Loading
Loading