SFTP over `msb ssh serve` creates files as root:root regardless of the authenticated guest user, blocking non-root external SSH clients

Author: albertsikkema-elixCreated Sep 21, 2026Updated Sep 21, 2026

Love MSB so far, i was trying to connect Claude Desktop to it, however that fails. I am not sure if this is the right way to submit a bug (or that it is a bug at all and I missed something obvious), so let me know if you want this differently.

Description

Files created through the SFTP subsystem of msb ssh serve are always owned by root:root with mode 0644, no matter which guest user the SSH session authenticated as. The shell side of the same connection does follow the user.

A non-root user can not handle this from inside the session: the uploaded file cannot be chmoded or written, and images that ship without sudo have no way to fix it. The practical effect is that any external SSH client that uploads-then-executes a helper binary (IDE/editor remote backends, for example) fails against a non-root sandbox, while working fine as root.

One solution would be to run as root inside the container, however given that Claude is running as user that is not an option: this would mean Claude can change its own guardrails in the container.

Related: #741 (bind mounts leaking host uid/gid) — same "guest sees the wrong identity for host-side writes". #741's fix, the uid=<N>,gid=<N> mount option, does not extend to SFTP-created files (see "What does not work" below).

  • msb exec in the same sandbox creates files as the expected guest user.
  • Only the SFTP path is affected.
  • The guest user cannot chmod or unlink the resulting file (though it can unlink it when it owns the parent directory).

Reproduction

Any OCI image with a non-root user. Below uses a node:24-bookworm-slim-based image whose default user is user01 (uid 1000), with no sudo installed.

bash
msb create --name sftptest ghcr.io/example/image:latest
msb ssh authorize --file ~/.ssh/id_ed25519.pub
msb ssh serve --host 127.0.0.1 --port 8022 sftptest &

echo hello > /tmp/probe.txt

# shell side: correct user
ssh -i ~/.ssh/id_ed25519 -p 8022 [email protected] 'id -un'

# sftp side: upload into the user's own home
echo "put /tmp/probe.txt /home/user01/probe.txt" \
  | sftp -i ~/.ssh/id_ed25519 -P 8022 [email protected]

ssh -i ~/.ssh/id_ed25519 -p 8022 [email protected] \
  'stat -c "%A %U:%G %n" /home/user01/probe.txt; chmod +x /home/user01/probe.txt'

Actual Behavior

user01                                            # shell honours the user
-rw-r--r-- root:root /home/user01/probe.txt       # upload does not
chmod: changing permissions of '/home/user01/probe.txt': Operation not permitted

The same put run as [email protected] also produces root:root, but there the session can chmod the result, so the failure only manifests for non-root users.

Expected Behavior

user01
-rw-r--r-- user01:user01 /home/user01/probe.txt
                                                    # chmod succeeds

Files created over SFTP should be owned by the guest user the SSH session authenticated as, consistent with msb exec and with the shell side of the same connection.

What does not work as a workaround

Each of these was tested on msb 0.7.2:

  1. Connecting as the non-root useruser01@ and root@ produce identical root:root files. The writer does not consult the session user.
  2. msb create --user user01 — no effect on SFTP-created files.
  3. Guest-side ~/.ssh/authorized_keys — auth is host-side only (<MSB_HOME>/ssh/authorized_keys), confirmed after a full sandbox restart. Consistent with the docs.
  4. Pre-creating the target as an executable, user-owned file — overwriting an existing file does preserve ownership and mode, but clients that rm-then-put (as the one prompting this report does) land back on the create path.
  5. A read-only parent directory — does not constrain the writer, which is privileged.
  6. --mount-dir ...:uid=1000,gid=1000 over the upload target — the mount itself maps correctly (the directory shows as user01:user01 in the guest), but files SFTP creates inside it are still root:root. So #741's fix does not extend to this path.

Environment

  • msb 0.7.2 (Homebrew, local backend)
  • Host: macOS 26.6.2, Darwin 25.6.0, arm64 (Apple silicon)
  • Guest: Linux 6.12.109 aarch64 (libkrunfw), root disk managed, ext4
  • Client: OpenSSH from macOS; also reproduced by a third-party GUI client (Claude Desktop) that uploads and executes a helper binary over the same connection

Source: superradcompany/microsandbox