From 34d1d82de3e50ce19227e5ed477c85e5e310ebd6 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 15 Mar 2026 21:45:11 -0700 Subject: [PATCH] fix(server): harden sandbox TLS secret volume permissions --- crates/openshell-server/src/sandbox/mod.rs | 48 +++++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/openshell-server/src/sandbox/mod.rs b/crates/openshell-server/src/sandbox/mod.rs index 49051e93..1e70cbc9 100644 --- a/crates/openshell-server/src/sandbox/mod.rs +++ b/crates/openshell-server/src/sandbox/mod.rs @@ -969,12 +969,17 @@ fn sandbox_template_to_k8s( ); // Add TLS secret volume. + // Use mode 0400 (owner-read only) so unprivileged processes in the + // sandbox cannot read the client private key material. if !client_tls_secret_name.is_empty() { spec.insert( "volumes".to_string(), serde_json::json!([{ "name": "openshell-client-tls", - "secret": { "secretName": client_tls_secret_name } + "secret": { + "secretName": client_tls_secret_name, + "defaultMode": 256 + } }]), ); } @@ -1047,6 +1052,8 @@ fn inject_pod_template( } // Inject TLS volume at the pod spec level. + // Use mode 0400 (owner-read only) so unprivileged processes in the + // sandbox cannot read the client private key material. if !client_tls_secret_name.is_empty() { let volumes = spec .entry("volumes") @@ -1054,7 +1061,10 @@ fn inject_pod_template( if let Some(volumes_arr) = volumes.as_array_mut() { volumes_arr.push(serde_json::json!({ "name": "openshell-client-tls", - "secret": { "secretName": client_tls_secret_name } + "secret": { + "secretName": client_tls_secret_name, + "defaultMode": 256 + } })); } } @@ -2013,6 +2023,40 @@ mod tests { ); } + #[test] + fn tls_secret_volume_uses_owner_read_only_mode() { + let pod_template = sandbox_template_to_k8s( + &SandboxTemplate::default(), + false, + "openshell/sandbox:latest", + "", + "sandbox-id", + "sandbox-name", + "https://gateway.example.com", + "0.0.0.0:2222", + "secret", + 300, + &std::collections::HashMap::new(), + "openshell-client-tls", + "", + ); + + let volumes = pod_template["spec"]["volumes"] + .as_array() + .expect("volumes should exist"); + let tls_volume = volumes + .iter() + .find(|v| v["name"] == "openshell-client-tls") + .expect("openshell-client-tls volume should exist"); + + assert_eq!(tls_volume["secret"]["secretName"], "openshell-client-tls"); + assert_eq!( + tls_volume["secret"]["defaultMode"], + serde_json::json!(256), + "TLS secret defaultMode should be 0400 (decimal 256)" + ); + } + #[test] fn host_aliases_injected_in_custom_pod_template() { let template = SandboxTemplate {