sandbox: the scratch is read-only to her, only claude-config is writable
Soul s1 scratch-symlink-deputy-write: she could plant a link at the instructions file in the unit's RUNTIME_DIRECTORY and the organ's next write followed it onto any path the unit's user can write, the hidden trace included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
edfbf9ce25
commit
aab25a5a00
2 changed files with 15 additions and 8 deletions
|
|
@ -695,13 +695,17 @@ impl Spawner for ClaudeSpawner {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The bubblewrap arguments up to the program: the whole filesystem read-only, a private `/tmp`
|
/// The bubblewrap arguments up to the program: the whole filesystem read-only, a private `/tmp`
|
||||||
/// and `/run`, the unit's scratch directory and her sandbox writable, her unit's credentials
|
/// and `/run`, the unit's scratch directory read-only with only its `claude-config` writable (she
|
||||||
/// (`/etc/gamecult`, the other `/var/lib` directories, the lease socket under `/run`) hidden, a
|
/// cannot create, replace or link a file where the organ writes, such as the instructions file),
|
||||||
/// private process table, and the child dead when the unit's process is. The network is shared: the
|
/// her sandbox writable, her unit's credentials (`/etc/gamecult`, the other `/var/lib`
|
||||||
/// web, `huginn` and the API are reachable. The boundary is the operating system's, not a pattern list
|
/// directories, the lease socket under `/run`) hidden, a private process table, and the child dead
|
||||||
/// (ruling `eureka-body:ruling:persona-git-personal-sandbox`).
|
/// when the unit's process is. The network is shared: the web, `huginn` and the API are reachable,
|
||||||
|
/// and what she reaches on the host is the uid fence's (gamecult-ops `mind-fence.service`), which
|
||||||
|
/// binds the organ and this sandbox alike. The boundary is the operating system's, not a pattern
|
||||||
|
/// list (ruling `eureka-body:ruling:persona-git-personal-sandbox`).
|
||||||
pub(crate) fn sandbox_args(sandbox: &Path, scratch: &Path) -> Vec<String> {
|
pub(crate) fn sandbox_args(sandbox: &Path, scratch: &Path) -> Vec<String> {
|
||||||
let (sandbox, scratch) = (sandbox.to_string_lossy(), scratch.to_string_lossy());
|
let (sandbox, scratch) = (sandbox.to_string_lossy(), scratch.to_string_lossy());
|
||||||
|
let config = scratch.to_string() + "/" + CONFIG_DIR_NAME;
|
||||||
[
|
[
|
||||||
"--ro-bind", "/", "/",
|
"--ro-bind", "/", "/",
|
||||||
"--dev", "/dev",
|
"--dev", "/dev",
|
||||||
|
|
@ -709,7 +713,8 @@ pub(crate) fn sandbox_args(sandbox: &Path, scratch: &Path) -> Vec<String> {
|
||||||
"--tmpfs", "/tmp",
|
"--tmpfs", "/tmp",
|
||||||
"--tmpfs", "/run",
|
"--tmpfs", "/run",
|
||||||
"--ro-bind-try", "/run/systemd/resolve", "/run/systemd/resolve",
|
"--ro-bind-try", "/run/systemd/resolve", "/run/systemd/resolve",
|
||||||
"--bind", &scratch, &scratch,
|
"--ro-bind", &scratch, &scratch,
|
||||||
|
"--bind", &config, &config,
|
||||||
"--tmpfs", "/etc/gamecult",
|
"--tmpfs", "/etc/gamecult",
|
||||||
"--tmpfs", "/var/lib",
|
"--tmpfs", "/var/lib",
|
||||||
"--bind", &sandbox, &sandbox,
|
"--bind", &sandbox, &sandbox,
|
||||||
|
|
|
||||||
|
|
@ -1697,8 +1697,8 @@ mod sandbox {
|
||||||
fn the_sandbox_arguments_are_pinned() {
|
fn the_sandbox_arguments_are_pinned() {
|
||||||
let args = sandbox_args(Path::new("/var/lib/mind-x/sandbox"), Path::new("/run/mind-x"));
|
let args = sandbox_args(Path::new("/var/lib/mind-x/sandbox"), Path::new("/run/mind-x"));
|
||||||
let expected: Vec<String> = [
|
let expected: Vec<String> = [
|
||||||
"--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc", "--tmpfs", "/tmp", "--tmpfs", "/run", "--ro-bind-try", "/run/systemd/resolve", "/run/systemd/resolve", "--bind", "/run/mind-x", "/run/mind-x", "--tmpfs",
|
"--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc", "--tmpfs", "/tmp", "--tmpfs", "/run", "--ro-bind-try", "/run/systemd/resolve", "/run/systemd/resolve", "--ro-bind", "/run/mind-x", "/run/mind-x", "--bind",
|
||||||
"/etc/gamecult", "--tmpfs", "/var/lib", "--bind", "/var/lib/mind-x/sandbox", "/var/lib/mind-x/sandbox", "--unshare-pid", "--die-with-parent", "--new-session", "--chdir", "/var/lib/mind-x/sandbox", "--",
|
"/run/mind-x/claude-config", "/run/mind-x/claude-config", "--tmpfs", "/etc/gamecult", "--tmpfs", "/var/lib", "--bind", "/var/lib/mind-x/sandbox", "/var/lib/mind-x/sandbox", "--unshare-pid", "--die-with-parent", "--new-session", "--chdir", "/var/lib/mind-x/sandbox", "--",
|
||||||
]
|
]
|
||||||
.map(String::from)
|
.map(String::from)
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
@ -1706,6 +1706,8 @@ mod sandbox {
|
||||||
// The credentials are hidden before the sandbox and the scratch are bound back, and the network is not unshared.
|
// The credentials are hidden before the sandbox and the scratch are bound back, and the network is not unshared.
|
||||||
let at = |needle: &str| args.iter().position(|arg| arg == needle).unwrap();
|
let at = |needle: &str| args.iter().position(|arg| arg == needle).unwrap();
|
||||||
assert!(at("/run") < at("/run/mind-x") && at("/var/lib") < at("/var/lib/mind-x/sandbox"));
|
assert!(at("/run") < at("/run/mind-x") && at("/var/lib") < at("/var/lib/mind-x/sandbox"));
|
||||||
|
// The scratch is read-only to her: no plain bind of it, so she cannot plant a link where the organ writes.
|
||||||
|
assert!(!args.windows(3).any(|w| w[0] == "--bind" && w[1] == "/run/mind-x"), "{args:?}");
|
||||||
assert!(!args.iter().any(|arg| arg == "--unshare-net" || arg == "--unshare-all"));
|
assert!(!args.iter().any(|arg| arg == "--unshare-net" || arg == "--unshare-all"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue