Extract claude and codex configs into separate profiles

This commit is contained in:
2026-04-24 08:31:37 +02:00
parent 7c9375cd94
commit a9f5593f03
2 changed files with 78 additions and 14 deletions
+16 -13
View File
@@ -1,5 +1,6 @@
# Globals; [profile.<name>] overrides them when --profile <name> is passed. # Layered settings: CLI > active profile > globals. `--profile` selects the
# CLI flags override both. # profile, otherwise `default-profile` below is used. Vec fields append across
# layers; scalars replace.
whitelist = true whitelist = true
# blacklist = true # blacklist = true
@@ -11,8 +12,6 @@ whitelist = true
# chdir = "~/projects/my-repo" # chdir = "~/projects/my-repo"
ro = [ ro = [
"~/.local/share/claude-code",
"~/.local/share/codex-cli",
"~/dev/agent-config", "~/dev/agent-config",
"/etc/alsa", "/etc/alsa",
"/run/user/1000/pulse", "/run/user/1000/pulse",
@@ -20,7 +19,6 @@ ro = [
# "/host/path:/sandbox/path", # SRC:DST -> mount host SRC at a different target # "/host/path:/sandbox/path", # SRC:DST -> mount host SRC at a different target
] ]
rw = [ rw = [
"~/.config/claude",
"~/.cargo", "~/.cargo",
"~/.rustup", "~/.rustup",
] ]
@@ -33,12 +31,17 @@ env = [
] ]
# unsetenv = ["SOME_LEAKED_VAR"] # unsetenv = ["SOME_LEAKED_VAR"]
entrypoint = ["claude", "--dangerously-skip-permissions"] # entrypoint = ["claude", "--dangerously-skip-permissions"] # binary + baked-in args
# command = ["--model", "opus"] # default trailing args # command = ["--model", "opus"] # default trailing args
# bwrap-args = ["--tmpfs /opt/scratch"] # raw bwrap escape hatch # bwrap-args = ["--tmpfs /opt/scratch"] # raw bwrap escape hatch
# Profiles inherit all globals above and override keys they set. Select one at default-profile = "claude"
# runtime with `--profile <name>`. Vec fields (ro/rw/mask/env/unsetenv) append
# to the globals; scalar fields replace. Profile-less runs use just the globals. [profile.claude]
[profile.blacklist] ro = ["~/.local/share/claude-code"]
blacklist = true rw = ["~/.config/claude"]
entrypoint = ["claude", "--dangerously-skip-permissions"]
[profile.codex]
ro = ["~/.local/share/codex-cli"]
entrypoint = ["codex", "--dangerously-bypass-approvals-and-sandbox"]
+62 -1
View File
@@ -290,6 +290,8 @@ pub struct FileConfig {
pub options: Options, pub options: Options,
#[serde(default)] #[serde(default)]
pub profile: HashMap<String, Options>, pub profile: HashMap<String, Options>,
#[serde(rename = "default-profile", default)]
pub default_profile: Option<String>,
// Collects unrecognized keys; deny_unknown_fields is incompatible with flatten. // Collects unrecognized keys; deny_unknown_fields is incompatible with flatten.
#[serde(flatten)] #[serde(flatten)]
_unknown: HashMap<String, toml::Value>, _unknown: HashMap<String, toml::Value>,
@@ -322,7 +324,7 @@ impl FileConfig {
} }
fn resolve_profile(&self, name: Option<&str>) -> Result<Options, SandboxError> { fn resolve_profile(&self, name: Option<&str>) -> Result<Options, SandboxError> {
match name { match name.or(self.default_profile.as_deref()) {
Some(n) => self Some(n) => self
.profile .profile
.get(n) .get(n)
@@ -879,6 +881,65 @@ mod tests {
assert!(!config.hardened); assert!(!config.hardened);
} }
#[test]
fn build_default_profile_used_when_cli_absent() {
let file_config = FileConfig {
default_profile: Some("auto".into()),
profile: HashMap::from([(
"auto".into(),
Options {
hardened: Some(true),
..Options::default()
},
)]),
..FileConfig::default()
};
let config = build(args_with_command(), Some(file_config)).unwrap();
assert!(config.hardened);
}
#[test]
fn build_cli_profile_overrides_default_profile() {
let file_config = FileConfig {
default_profile: Some("auto".into()),
profile: HashMap::from([
(
"auto".into(),
Options {
hardened: Some(true),
..Options::default()
},
),
(
"other".into(),
Options {
hardened: Some(false),
..Options::default()
},
),
]),
..FileConfig::default()
};
let args = Args {
profile: Some("other".into()),
..args_with_command()
};
let config = build(args, Some(file_config)).unwrap();
assert!(!config.hardened);
}
#[test]
fn build_missing_default_profile_errors() {
let file_config = FileConfig {
default_profile: Some("nope".into()),
..FileConfig::default()
};
assert!(matches!(
build(args_with_command(), Some(file_config)),
Err(SandboxError::ProfileNotFound(_))
));
}
#[test] #[test]
fn build_missing_profile_errors() { fn build_missing_profile_errors() {
let args = Args { let args = Args {