Quellcode durchsuchen

Merge remote-tracking branch 'origin/rcc/sandbox' into integration/dori-cleanroom

# Conflicts:
#	rust/crates/commands/src/lib.rs
#	rust/crates/runtime/src/config.rs
#	rust/crates/runtime/src/lib.rs
#	rust/crates/rusty-claude-cli/src/main.rs
YeonGyu-Kim vor 2 Monaten
Ursprung
Commit
1d4c8a8f50

+ 16 - 2
rust/crates/commands/src/lib.rs

@@ -60,6 +60,13 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
         argument_hint: None,
         resume_supported: true,
     },
+    SlashCommandSpec {
+        name: "sandbox",
+        aliases: &[],
+        summary: "Show sandbox isolation status",
+        argument_hint: None,
+        resume_supported: true,
+    },
     SlashCommandSpec {
         name: "compact",
         aliases: &[],
@@ -229,6 +236,7 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
 pub enum SlashCommand {
     Help,
     Status,
+    Sandbox,
     Compact,
     Bughunter {
         scope: Option<String>,
@@ -300,6 +308,7 @@ impl SlashCommand {
         Some(match command {
             "help" => Self::Help,
             "status" => Self::Status,
+            "sandbox" => Self::Sandbox,
             "compact" => Self::Compact,
             "bughunter" => Self::Bughunter {
                 scope: remainder_after_command(trimmed, command),
@@ -1188,6 +1197,7 @@ pub fn handle_slash_command(
         | SlashCommand::Ultraplan { .. }
         | SlashCommand::Teleport { .. }
         | SlashCommand::DebugToolCall
+        | SlashCommand::Sandbox
         | SlashCommand::Model { .. }
         | SlashCommand::Permissions { .. }
         | SlashCommand::Clear { .. }
@@ -1287,6 +1297,7 @@ mod tests {
     fn parses_supported_slash_commands() {
         assert_eq!(SlashCommand::parse("/help"), Some(SlashCommand::Help));
         assert_eq!(SlashCommand::parse(" /status "), Some(SlashCommand::Status));
+        assert_eq!(SlashCommand::parse("/sandbox"), Some(SlashCommand::Sandbox));
         assert_eq!(
             SlashCommand::parse("/bughunter runtime"),
             Some(SlashCommand::Bughunter {
@@ -1416,6 +1427,7 @@ mod tests {
         assert!(help.contains("works with --resume SESSION.json"));
         assert!(help.contains("/help"));
         assert!(help.contains("/status"));
+        assert!(help.contains("/sandbox"));
         assert!(help.contains("/compact"));
         assert!(help.contains("/bughunter [scope]"));
         assert!(help.contains("/commit"));
@@ -1436,14 +1448,15 @@ mod tests {
         assert!(help.contains("/version"));
         assert!(help.contains("/export [file]"));
         assert!(help.contains("/session [list|switch <session-id>]"));
+        assert!(help.contains("/sandbox"));
         assert!(help.contains(
             "/plugin [list|install <path>|enable <name>|disable <name>|uninstall <id>|update <id>]"
         ));
         assert!(help.contains("aliases: /plugins, /marketplace"));
         assert!(help.contains("/agents"));
         assert!(help.contains("/skills"));
-        assert_eq!(slash_command_specs().len(), 25);
-        assert_eq!(resume_supported_slash_commands().len(), 13);
+        assert_eq!(slash_command_specs().len(), 27);
+        assert_eq!(resume_supported_slash_commands().len(), 14);
     }
 
     #[test]
@@ -1490,6 +1503,7 @@ mod tests {
         let session = Session::new();
         assert!(handle_slash_command("/unknown", &session, CompactionConfig::default()).is_none());
         assert!(handle_slash_command("/status", &session, CompactionConfig::default()).is_none());
+        assert!(handle_slash_command("/sandbox", &session, CompactionConfig::default()).is_none());
         assert!(
             handle_slash_command("/bughunter", &session, CompactionConfig::default()).is_none()
         );

+ 6 - 0
rust/crates/runtime/src/lib.rs

@@ -76,6 +76,12 @@ pub use remote::{
     RemoteSessionContext, UpstreamProxyBootstrap, UpstreamProxyState, DEFAULT_REMOTE_BASE_URL,
     DEFAULT_SESSION_TOKEN_PATH, DEFAULT_SYSTEM_CA_BUNDLE, NO_PROXY_HOSTS, UPSTREAM_PROXY_ENV_KEYS,
 };
+pub use sandbox::{
+    build_linux_sandbox_command, detect_container_environment, detect_container_environment_from,
+    resolve_sandbox_status, resolve_sandbox_status_for_request, ContainerEnvironment,
+    FilesystemIsolationMode, LinuxSandboxCommand, SandboxConfig, SandboxDetectionInputs,
+    SandboxRequest, SandboxStatus,
+};
 pub use session::{ContentBlock, ConversationMessage, MessageRole, Session, SessionError};
 pub use usage::{
     format_usd, pricing_for_model, ModelPricing, TokenUsage, UsageCostEstimate, UsageTracker,

+ 97 - 2
rust/crates/rusty-claude-cli/src/main.rs

@@ -34,6 +34,10 @@ use runtime::{
     parse_oauth_callback_request_target, save_oauth_credentials, ApiClient, ApiRequest,
     AssistantEvent, CompactionConfig, ConfigLoader, ConfigSource, ContentBlock,
     ConversationMessage, ConversationRuntime, MessageRole, OAuthAuthorizationRequest, OAuthConfig,
+    parse_oauth_callback_request_target, resolve_sandbox_status, save_oauth_credentials,
+    ApiClient, ApiRequest, AssistantEvent, CompactionConfig, ConfigLoader, ConfigSource,
+    ContentBlock, ConversationMessage, ConversationRuntime, MessageRole,
+    OAuthAuthorizationRequest, OAuthConfig,
     OAuthTokenExchangeRequest, PermissionMode, PermissionPolicy, ProjectContext, RuntimeError,
     Session, TokenUsage, ToolError, ToolExecutor, UsageTracker,
 };
@@ -656,6 +660,7 @@ struct StatusContext {
     memory_file_count: usize,
     project_root: Option<PathBuf>,
     git_branch: Option<String>,
+    sandbox_status: runtime::SandboxStatus,
 }
 
 #[derive(Debug, Clone, Copy)]
@@ -928,6 +933,18 @@ fn run_resume_command(
                 )),
             })
         }
+        SlashCommand::Sandbox => {
+            let cwd = env::current_dir()?;
+            let loader = ConfigLoader::default_for(&cwd);
+            let runtime_config = loader.load()?;
+            Ok(ResumeCommandOutcome {
+                session: session.clone(),
+                message: Some(format_sandbox_report(&resolve_sandbox_status(
+                    runtime_config.sandbox(),
+                    &cwd,
+                ))),
+            })
+        }
         SlashCommand::Cost => {
             let usage = UsageTracker::from_session(session).cumulative_usage();
             Ok(ResumeCommandOutcome {
@@ -1237,6 +1254,10 @@ impl LiveCli {
                 self.run_debug_tool_call()?;
                 false
             }
+            SlashCommand::Sandbox => {
+                Self::print_sandbox_status();
+                false
+            }
             SlashCommand::Compact => {
                 self.compact()?;
                 false
@@ -1319,6 +1340,18 @@ impl LiveCli {
         );
     }
 
+    fn print_sandbox_status() {
+        let cwd = env::current_dir().expect("current dir");
+        let loader = ConfigLoader::default_for(&cwd);
+        let runtime_config = loader
+            .load()
+            .unwrap_or_else(|_| runtime::RuntimeConfig::empty());
+        println!(
+            "{}",
+            format_sandbox_report(&resolve_sandbox_status(runtime_config.sandbox(), &cwd))
+        );
+    }
+
     fn set_model(&mut self, model: Option<String>) -> Result<bool, Box<dyn std::error::Error>> {
         let Some(model) = model else {
             println!(
@@ -1922,6 +1955,7 @@ fn status_context(
     let project_context = ProjectContext::discover_with_git(&cwd, DEFAULT_DATE)?;
     let (project_root, git_branch) =
         parse_git_status_metadata(project_context.git_status.as_deref());
+    let sandbox_status = resolve_sandbox_status(runtime_config.sandbox(), &cwd);
     Ok(StatusContext {
         cwd,
         session_path: session_path.map(Path::to_path_buf),
@@ -1930,6 +1964,7 @@ fn status_context(
         memory_file_count: project_context.instruction_files.len(),
         project_root,
         git_branch,
+        sandbox_status,
     })
 }
 
@@ -1982,6 +2017,7 @@ fn format_status_report(
             context.discovered_config_files,
             context.memory_file_count,
         ),
+        format_sandbox_report(&context.sandbox_status),
     ]
     .join(
         "
@@ -1990,6 +2026,49 @@ fn format_status_report(
     )
 }
 
+fn format_sandbox_report(status: &runtime::SandboxStatus) -> String {
+    format!(
+        "Sandbox
+  Enabled           {}
+  Active            {}
+  Supported         {}
+  In container      {}
+  Requested ns      {}
+  Active ns         {}
+  Requested net     {}
+  Active net        {}
+  Filesystem mode   {}
+  Filesystem active {}
+  Allowed mounts    {}
+  Markers           {}
+  Fallback reason   {}",
+        status.enabled,
+        status.active,
+        status.supported,
+        status.in_container,
+        status.requested.namespace_restrictions,
+        status.namespace_active,
+        status.requested.network_isolation,
+        status.network_active,
+        status.filesystem_mode.as_str(),
+        status.filesystem_active,
+        if status.allowed_mounts.is_empty() {
+            "<none>".to_string()
+        } else {
+            status.allowed_mounts.join(", ")
+        },
+        if status.container_markers.is_empty() {
+            "<none>".to_string()
+        } else {
+            status.container_markers.join(", ")
+        },
+        status
+            .fallback_reason
+            .clone()
+            .unwrap_or_else(|| "<none>".to_string()),
+    )
+}
+
 fn render_config_report(section: Option<&str>) -> Result<String, Box<dyn std::error::Error>> {
     let cwd = env::current_dir()?;
     let loader = ConfigLoader::default_for(&cwd);
@@ -4249,6 +4328,7 @@ mod tests {
         assert!(help.contains("REPL"));
         assert!(help.contains("/help"));
         assert!(help.contains("/status"));
+        assert!(help.contains("/sandbox"));
         assert!(help.contains("/model [model]"));
         assert!(help.contains("/permissions [read-only|workspace-write|danger-full-access]"));
         assert!(help.contains("/clear [--confirm]"));
@@ -4279,8 +4359,8 @@ mod tests {
         assert_eq!(
             names,
             vec![
-                "help", "status", "compact", "clear", "cost", "config", "memory", "init", "diff",
-                "version", "export", "agents", "skills",
+                "help", "status", "sandbox", "compact", "clear", "cost", "config", "memory",
+                "init", "diff", "version", "export", "agents", "skills",
             ]
         );
     }
@@ -4400,6 +4480,7 @@ mod tests {
                 memory_file_count: 4,
                 project_root: Some(PathBuf::from("/tmp")),
                 git_branch: Some("main".to_string()),
+                sandbox_status: runtime::SandboxStatus::default(),
             },
         );
         assert!(status.contains("Status"));
@@ -4996,3 +5077,17 @@ mod tests {
         assert!(!String::from_utf8(out).expect("utf8").contains("step 1"));
     }
 }
+
+#[cfg(test)]
+mod sandbox_report_tests {
+    use super::format_sandbox_report;
+
+    #[test]
+    fn sandbox_report_renders_expected_fields() {
+        let report = format_sandbox_report(&runtime::SandboxStatus::default());
+        assert!(report.contains("Sandbox"));
+        assert!(report.contains("Enabled"));
+        assert!(report.contains("Filesystem mode"));
+        assert!(report.contains("Fallback reason"));
+    }
+}

+ 5 - 1
rust/crates/tools/src/lib.rs

@@ -225,7 +225,11 @@ pub fn mvp_tool_specs() -> Vec<ToolSpec> {
                     "timeout": { "type": "integer", "minimum": 1 },
                     "description": { "type": "string" },
                     "run_in_background": { "type": "boolean" },
-                    "dangerouslyDisableSandbox": { "type": "boolean" }
+                    "dangerouslyDisableSandbox": { "type": "boolean" },
+                    "namespaceRestrictions": { "type": "boolean" },
+                    "isolateNetwork": { "type": "boolean" },
+                    "filesystemMode": { "type": "string", "enum": ["off", "workspace-only", "allow-list"] },
+                    "allowedMounts": { "type": "array", "items": { "type": "string" } }
                 },
                 "required": ["command"],
                 "additionalProperties": false