Skip to content

prepare_filesystem() should not chown existing directories in read_write #783

@prekshivyas

Description

@prekshivyas

Problem

prepare_filesystem() in crates/openshell-sandbox/src/lib.rs chowns every path in the policy's read_write list to run_as_user:run_as_group before spawning the child process. This happens unconditionally — even for directories that already exist in the container image with intentional ownership.

This means an image author who sets root:root on a read_write directory (e.g., to protect immutable subdirectories via DAC while allowing writes to others) has that ownership silently overridden at sandbox start.

Example (NemoClaw)

Our Dockerfile sets /sandbox/.nemoclaw to root:root 755 to prevent the agent from renaming the root-owned blueprints/ subdirectory. The policy lists it as read_write because sandbox-owned subdirs (state/, migration/) inside it need to be writable.

At sandbox start, prepare_filesystem() chowns /sandbox/.nemoclaw to sandbox:sandbox, removing the DAC protection. QA confirmed the ownership flip at runtime in NVIDIA/NemoClaw#1607 / nvbug 6059437.

We've worked around this with the sticky bit (chmod 1755) in NVIDIA/NemoClaw#1121, which prevents the sandbox user from renaming or deleting root-owned entries even after the ownership flip. But the root cause is the unconditional chown.

Context

This came up during security hardening review for NemoClaw PR #1121 (read-only sandbox filesystem). The NemoClaw team discussed this with our tech lead and agreed the sticky bit is a sufficient short-term workaround, but that prepare_filesystem() respecting existing ownership would be the cleaner long-term fix — both for us and for any other OpenShell image author who intentionally hardens read_write paths.

Proposed Change

Only chown directories that prepare_filesystem() creates. If a directory already exists in the image, respect the image author's ownership:

for path in &policy.filesystem.read_write {
    if let Ok(meta) = std::fs::symlink_metadata(path) {
        if meta.file_type().is_symlink() {
            return Err(...);  // existing symlink check
        }
        // Directory exists — image author set ownership intentionally, leave it
    } else {
        // Directory doesn't exist — create and chown
        std::fs::create_dir_all(path).into_diagnostic()?;
        chown(path, uid, gid).into_diagnostic()?;
    }
}

This is backwards-compatible — directories that don't exist in the image still get created and chowned. Only pre-existing directories are left alone.

Metadata

Metadata

Assignees

Labels

state:agent-readyApproved for agent implementationstate:pr-openedPR has been opened for this issue

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions