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`
|
||||
/// and `/run`, the unit's scratch directory and her sandbox writable, her unit's credentials
|
||||
/// (`/etc/gamecult`, the other `/var/lib` directories, the lease socket under `/run`) hidden, a
|
||||
/// private process table, and the child dead when the unit's process is. The network is shared: the
|
||||
/// web, `huginn` and the API are reachable. The boundary is the operating system's, not a pattern list
|
||||
/// (ruling `eureka-body:ruling:persona-git-personal-sandbox`).
|
||||
/// and `/run`, the unit's scratch directory read-only with only its `claude-config` writable (she
|
||||
/// cannot create, replace or link a file where the organ writes, such as the instructions file),
|
||||
/// her sandbox writable, her unit's credentials (`/etc/gamecult`, the other `/var/lib`
|
||||
/// directories, the lease socket under `/run`) hidden, a private process table, and the child dead
|
||||
/// 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> {
|
||||
let (sandbox, scratch) = (sandbox.to_string_lossy(), scratch.to_string_lossy());
|
||||
let config = scratch.to_string() + "/" + CONFIG_DIR_NAME;
|
||||
[
|
||||
"--ro-bind", "/", "/",
|
||||
"--dev", "/dev",
|
||||
|
|
@ -709,7 +713,8 @@ pub(crate) fn sandbox_args(sandbox: &Path, scratch: &Path) -> Vec<String> {
|
|||
"--tmpfs", "/tmp",
|
||||
"--tmpfs", "/run",
|
||||
"--ro-bind-try", "/run/systemd/resolve", "/run/systemd/resolve",
|
||||
"--bind", &scratch, &scratch,
|
||||
"--ro-bind", &scratch, &scratch,
|
||||
"--bind", &config, &config,
|
||||
"--tmpfs", "/etc/gamecult",
|
||||
"--tmpfs", "/var/lib",
|
||||
"--bind", &sandbox, &sandbox,
|
||||
|
|
|
|||
|
|
@ -1697,8 +1697,8 @@ mod sandbox {
|
|||
fn the_sandbox_arguments_are_pinned() {
|
||||
let args = sandbox_args(Path::new("/var/lib/mind-x/sandbox"), Path::new("/run/mind-x"));
|
||||
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",
|
||||
"/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", "--",
|
||||
"--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",
|
||||
"/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)
|
||||
.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.
|
||||
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"));
|
||||
// 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"));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue