Skip to content

Commit 19909d3

Browse files
committed
Enforce workspace metadata protections in Seatbelt
1 parent 5b7d6f5 commit 19909d3

2 files changed

Lines changed: 181 additions & 92 deletions

File tree

codex-rs/sandboxing/src/seatbelt.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use codex_network_proxy::has_proxy_url_env_vars;
44
use codex_network_proxy::proxy_url_env_value;
55
use codex_protocol::permissions::FileSystemSandboxPolicy;
66
use codex_protocol::permissions::NetworkSandboxPolicy;
7+
use codex_protocol::permissions::PROTECTED_METADATA_PATH_NAMES;
78
use codex_protocol::protocol::SandboxPolicy;
9+
use codex_protocol::protocol::WritableRoot;
810
use codex_utils_absolute_path::AbsolutePathBuf;
911
use std::collections::BTreeMap;
1012
use std::collections::BTreeSet;
@@ -328,6 +330,7 @@ fn root_absolute_path() -> AbsolutePathBuf {
328330
struct SeatbeltAccessRoot {
329331
root: AbsolutePathBuf,
330332
excluded_subpaths: Vec<AbsolutePathBuf>,
333+
protected_metadata_names: Vec<String>,
331334
}
332335

333336
fn build_seatbelt_access_policy(
@@ -342,9 +345,11 @@ fn build_seatbelt_access_policy(
342345
let root =
343346
normalize_path_for_sandbox(access_root.root.as_path()).unwrap_or(access_root.root);
344347
let root_param = format!("{param_prefix}_{index}");
345-
params.push((root_param.clone(), root.into_path_buf()));
348+
params.push((root_param.clone(), root.clone().into_path_buf()));
346349

347-
if access_root.excluded_subpaths.is_empty() {
350+
if access_root.excluded_subpaths.is_empty()
351+
&& access_root.protected_metadata_names.is_empty()
352+
{
348353
policy_components.push(format!("(subpath (param \"{root_param}\"))"));
349354
continue;
350355
}
@@ -367,6 +372,11 @@ fn build_seatbelt_access_policy(
367372
"(require-not (subpath (param \"{excluded_param}\")))"
368373
));
369374
}
375+
for metadata_name in access_root.protected_metadata_names {
376+
let regex =
377+
seatbelt_protected_metadata_name_regex(&root, &metadata_name).replace('"', "\\\"");
378+
require_parts.push(format!(r#"(require-not (regex #"{regex}"))"#));
379+
}
370380
policy_components.push(format!("(require-all {} )", require_parts.join(" ")));
371381
}
372382

@@ -380,6 +390,38 @@ fn build_seatbelt_access_policy(
380390
}
381391
}
382392

393+
fn seatbelt_protected_metadata_name_regex(root: &AbsolutePathBuf, name: &str) -> String {
394+
let mut root = root.to_string_lossy().to_string();
395+
while root.len() > 1 && root.ends_with('/') {
396+
root.pop();
397+
}
398+
let root = regex_lite::escape(&root);
399+
let name = regex_lite::escape(name);
400+
if root == "/" {
401+
format!(r#"^/{name}(/.*)?$"#)
402+
} else {
403+
format!(r#"^{root}/{name}(/.*)?$"#)
404+
}
405+
}
406+
407+
fn protected_metadata_names_for_writable_root(
408+
file_system_sandbox_policy: &FileSystemSandboxPolicy,
409+
writable_root: &WritableRoot,
410+
cwd: &Path,
411+
) -> Vec<String> {
412+
let mut names = writable_root.protected_metadata_names.clone();
413+
for name in PROTECTED_METADATA_PATH_NAMES {
414+
if names.iter().any(|existing| existing == name) {
415+
continue;
416+
}
417+
let path = writable_root.root.join(*name);
418+
if !file_system_sandbox_policy.can_write_path_with_cwd(path.as_path(), cwd) {
419+
names.push((*name).to_string());
420+
}
421+
}
422+
names
423+
}
424+
383425
fn build_seatbelt_unreadable_glob_policy(
384426
file_system_sandbox_policy: &FileSystemSandboxPolicy,
385427
cwd: &Path,
@@ -586,6 +628,7 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -
586628
vec![SeatbeltAccessRoot {
587629
root: root_absolute_path(),
588630
excluded_subpaths: unreadable_roots.clone(),
631+
protected_metadata_names: Vec::new(),
589632
}],
590633
)
591634
}
@@ -597,6 +640,11 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -
597640
.get_writable_roots_with_cwd(sandbox_policy_cwd)
598641
.into_iter()
599642
.map(|root| SeatbeltAccessRoot {
643+
protected_metadata_names: protected_metadata_names_for_writable_root(
644+
file_system_sandbox_policy,
645+
&root,
646+
sandbox_policy_cwd,
647+
),
600648
root: root.root,
601649
excluded_subpaths: root.read_only_subpaths,
602650
})
@@ -618,6 +666,7 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -
618666
vec![SeatbeltAccessRoot {
619667
root: root_absolute_path(),
620668
excluded_subpaths: unreadable_roots,
669+
protected_metadata_names: Vec::new(),
621670
}],
622671
);
623672
(
@@ -638,6 +687,7 @@ pub fn create_seatbelt_command_args(args: CreateSeatbeltCommandArgsParams<'_>) -
638687
.filter(|path| path.as_path().starts_with(root.as_path()))
639688
.cloned()
640689
.collect(),
690+
protected_metadata_names: Vec::new(),
641691
root,
642692
})
643693
.collect(),

0 commit comments

Comments
 (0)