Skip to content

Commit db6aa80

Browse files
fix(core): add linux bubblewrap sandbox tag (#11767)
## Summary - add a distinct `linux_bubblewrap` sandbox tag when the Linux bubblewrap pipeline feature is enabled - thread the bubblewrap feature flag into sandbox tag generation for: - turn metadata header emission - tool telemetry metric tags and after-tool-use hooks - add focused unit tests for `sandbox_tag` precedence and Linux bubblewrap behavior ## Validation - `just fmt` - `cargo clippy -p codex-core --all-targets` - `cargo test -p codex-core sandbox_tags::tests` - started `cargo test -p codex-core` and stopped it per request Co-authored-by: Codex <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
1 parent ebceb71 commit db6aa80

File tree

4 files changed

+128
-3
lines changed

4 files changed

+128
-3
lines changed

codex-rs/core/src/codex.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,9 @@ impl Session {
945945
cwd.clone(),
946946
session_configuration.sandbox_policy.get(),
947947
session_configuration.windows_sandbox_level,
948+
per_turn_config
949+
.features
950+
.enabled(Feature::UseLinuxSandboxBwrap),
948951
));
949952
TurnContext {
950953
sub_id,
@@ -4072,6 +4075,9 @@ async fn spawn_review_thread(
40724075
parent_turn_context.cwd.clone(),
40734076
&parent_turn_context.sandbox_policy,
40744077
parent_turn_context.windows_sandbox_level,
4078+
parent_turn_context
4079+
.features
4080+
.enabled(Feature::UseLinuxSandboxBwrap),
40754081
));
40764082

40774083
let review_turn_context = TurnContext {

codex-rs/core/src/sandbox_tags.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use codex_protocol::config_types::WindowsSandboxLevel;
66
pub(crate) fn sandbox_tag(
77
policy: &SandboxPolicy,
88
windows_sandbox_level: WindowsSandboxLevel,
9+
use_linux_sandbox_bwrap: bool,
910
) -> &'static str {
1011
if matches!(policy, SandboxPolicy::DangerFullAccess) {
1112
return "none";
@@ -17,8 +18,61 @@ pub(crate) fn sandbox_tag(
1718
{
1819
return "windows_elevated";
1920
}
21+
if cfg!(target_os = "linux") && use_linux_sandbox_bwrap {
22+
return "linux_bubblewrap";
23+
}
2024

2125
get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled)
2226
.map(SandboxType::as_metric_tag)
2327
.unwrap_or("none")
2428
}
29+
30+
#[cfg(test)]
31+
mod tests {
32+
use super::sandbox_tag;
33+
use crate::exec::SandboxType;
34+
use crate::protocol::SandboxPolicy;
35+
use crate::safety::get_platform_sandbox;
36+
use codex_protocol::config_types::WindowsSandboxLevel;
37+
use codex_protocol::protocol::NetworkAccess;
38+
use pretty_assertions::assert_eq;
39+
40+
#[test]
41+
fn danger_full_access_is_untagged_even_when_bubblewrap_is_enabled() {
42+
let actual = sandbox_tag(
43+
&SandboxPolicy::DangerFullAccess,
44+
WindowsSandboxLevel::Disabled,
45+
true,
46+
);
47+
assert_eq!(actual, "none");
48+
}
49+
50+
#[test]
51+
fn external_sandbox_keeps_external_tag_when_bubblewrap_is_enabled() {
52+
let actual = sandbox_tag(
53+
&SandboxPolicy::ExternalSandbox {
54+
network_access: NetworkAccess::Enabled,
55+
},
56+
WindowsSandboxLevel::Disabled,
57+
true,
58+
);
59+
assert_eq!(actual, "external");
60+
}
61+
62+
#[test]
63+
fn bubblewrap_feature_sets_distinct_linux_tag() {
64+
let actual = sandbox_tag(
65+
&SandboxPolicy::new_read_only_policy(),
66+
WindowsSandboxLevel::Disabled,
67+
true,
68+
);
69+
let expected = if cfg!(target_os = "linux") {
70+
"linux_bubblewrap"
71+
} else {
72+
get_platform_sandbox(false)
73+
.map(SandboxType::as_metric_tag)
74+
.unwrap_or("none")
75+
};
76+
assert_eq!(actual, expected);
77+
}
78+
}

codex-rs/core/src/tools/registry.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::time::Duration;
44
use std::time::Instant;
55

66
use crate::client_common::tools::ToolSpec;
7+
use crate::features::Feature;
78
use crate::function_tool::FunctionCallError;
89
use crate::protocol::SandboxPolicy;
910
use crate::sandbox_tags::sandbox_tag;
@@ -88,6 +89,10 @@ impl ToolRegistry {
8889
sandbox_tag(
8990
&invocation.turn.sandbox_policy,
9091
invocation.turn.windows_sandbox_level,
92+
invocation
93+
.turn
94+
.features
95+
.enabled(Feature::UseLinuxSandboxBwrap),
9196
),
9297
),
9398
(
@@ -356,8 +361,12 @@ async fn dispatch_after_tool_use_hook(dispatch: AfterToolUseHookDispatch<'_>) {
356361
success: dispatch.success,
357362
duration_ms: u64::try_from(dispatch.duration.as_millis()).unwrap_or(u64::MAX),
358363
mutating: dispatch.mutating,
359-
sandbox: sandbox_tag(&turn.sandbox_policy, turn.windows_sandbox_level)
360-
.to_string(),
364+
sandbox: sandbox_tag(
365+
&turn.sandbox_policy,
366+
turn.windows_sandbox_level,
367+
turn.features.enabled(Feature::UseLinuxSandboxBwrap),
368+
)
369+
.to_string(),
361370
sandbox_policy: sandbox_policy_tag(&turn.sandbox_policy).to_string(),
362371
output_preview: dispatch.output_preview.clone(),
363372
},

codex-rs/core/src/turn_metadata.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,17 @@ impl TurnMetadataState {
132132
cwd: PathBuf,
133133
sandbox_policy: &SandboxPolicy,
134134
windows_sandbox_level: WindowsSandboxLevel,
135+
use_linux_sandbox_bwrap: bool,
135136
) -> Self {
136137
let repo_root = get_git_repo_root(&cwd).map(|root| root.to_string_lossy().into_owned());
137-
let sandbox = Some(sandbox_tag(sandbox_policy, windows_sandbox_level).to_string());
138+
let sandbox = Some(
139+
sandbox_tag(
140+
sandbox_policy,
141+
windows_sandbox_level,
142+
use_linux_sandbox_bwrap,
143+
)
144+
.to_string(),
145+
);
138146
let base_metadata = build_turn_metadata_bag(Some(turn_id), sandbox, None, None);
139147
let base_header = base_metadata
140148
.to_header_value()
@@ -290,4 +298,52 @@ mod tests {
290298
Some(false)
291299
);
292300
}
301+
302+
#[test]
303+
fn turn_metadata_state_respects_linux_bubblewrap_toggle() {
304+
let temp_dir = TempDir::new().expect("temp dir");
305+
let cwd = temp_dir.path().to_path_buf();
306+
let sandbox_policy = SandboxPolicy::new_read_only_policy();
307+
308+
let without_bubblewrap = TurnMetadataState::new(
309+
"turn-a".to_string(),
310+
cwd.clone(),
311+
&sandbox_policy,
312+
WindowsSandboxLevel::Disabled,
313+
false,
314+
);
315+
let with_bubblewrap = TurnMetadataState::new(
316+
"turn-b".to_string(),
317+
cwd,
318+
&sandbox_policy,
319+
WindowsSandboxLevel::Disabled,
320+
true,
321+
);
322+
323+
let without_bubblewrap_header = without_bubblewrap
324+
.current_header_value()
325+
.expect("without_bubblewrap_header");
326+
let with_bubblewrap_header = with_bubblewrap
327+
.current_header_value()
328+
.expect("with_bubblewrap_header");
329+
330+
let without_bubblewrap_json: Value =
331+
serde_json::from_str(&without_bubblewrap_header).expect("without_bubblewrap_json");
332+
let with_bubblewrap_json: Value =
333+
serde_json::from_str(&with_bubblewrap_header).expect("with_bubblewrap_json");
334+
335+
let without_bubblewrap_sandbox = without_bubblewrap_json
336+
.get("sandbox")
337+
.and_then(Value::as_str);
338+
let with_bubblewrap_sandbox = with_bubblewrap_json.get("sandbox").and_then(Value::as_str);
339+
340+
let expected_with_bubblewrap =
341+
sandbox_tag(&sandbox_policy, WindowsSandboxLevel::Disabled, true);
342+
assert_eq!(with_bubblewrap_sandbox, Some(expected_with_bubblewrap));
343+
344+
if cfg!(target_os = "linux") {
345+
assert_eq!(with_bubblewrap_sandbox, Some("linux_bubblewrap"));
346+
assert_ne!(with_bubblewrap_sandbox, without_bubblewrap_sandbox);
347+
}
348+
}
293349
}

0 commit comments

Comments
 (0)