Extract claude and codex configs into separate profiles
This commit is contained in:
+14
-11
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user