security: multi_user_add leaves user homes and secrets.toml world-readable #107

Closed
opened 2026-04-21 15:25:28 +00:00 by sameh-farouk · 0 comments
Member

Update 2026-04-21: Rebased against development after an upstream refactor that moved per-user mycelium state from /etc/hero-users/<user>.env (root-owned) to ~/hero/cfg/hero_cfg.toml (user-owned, explicitly chmod 644). The core concern — default perms leaving secrets world-readable — remains unchanged. Body updated to reflect current code.

Summary

Newly-provisioned user homes have default umask permissions (755/644) on sensitive files. In particular:

  • ~/hero/code/secrets/secrets.toml — world-readable (644) with forge tokens, LLM API keys, DB passwords, SMTP creds
  • ~/hero/cfg/hero_cfg.toml — explicitly chmod 644 in bridge_save_state (less sensitive: mycelium bridge name + prefix, but still exposes topology)
  • Home dir itself — never locked down, so any other user can cd /home/<someone> and browse

Sampled on a live host: 13 users' secrets.toml readable cross-user via:

find /home -maxdepth 5 -name secrets.toml -perm -o=r

Location

tools/modules/installers/multiuser.numulti_user_add chowns the home to the user but never chmods it down. The only paths explicitly tightened are SSH-related (~/.ssh/*). Home and ~/hero/code/secrets/* inherit 755/644 from the btrfs template snapshot.

bridge_save_state (line 127) explicitly sets ~/hero/cfg/hero_cfg.toml to 644.

Proposed fix

In multi_user_add, after the snapshot + chown step, add:

^sudo chmod 700 $homedir
if (do { ^sudo test -d $"($homedir)/hero/code/secrets" } | complete | get exit_code) == 0 {
    ^sudo chmod 700 $"($homedir)/hero/code/secrets"
    for f in ["secrets.toml" "secrets.sh"] {
        let p = $"($homedir)/hero/code/secrets/($f)"
        if (do { ^sudo test -f $p } | complete | get exit_code) == 0 {
            ^sudo chmod 600 $p
        }
    }
}

Also in bridge_save_state: change the explicit chmod 644 on hero_cfg.toml to chmod 600. Only the user's own tools and processes read it; no reason for it to be world-readable.

Verification

After patching, on a freshly-provisioned user:

  • ls -ld ~drwx------
  • ls -l ~/hero/code/secrets/secrets.toml-rw-------
  • ls -l ~/hero/cfg/hero_cfg.toml-rw-------
  • Other users get Permission denied on /home/<user>/hero/code/secrets/.

Impact

High, systemic. Every user ever provisioned on a multi-user host has had their secrets cross-user-readable by default. Recommended follow-up: notify affected users, ask them to rotate anything sensitive in secrets.toml and run chmod 700 ~ themselves.

> **Update 2026-04-21:** Rebased against `development` after an upstream refactor that moved per-user mycelium state from `/etc/hero-users/<user>.env` (root-owned) to `~/hero/cfg/hero_cfg.toml` (user-owned, explicitly `chmod 644`). The core concern — default perms leaving secrets world-readable — remains unchanged. Body updated to reflect current code. ### Summary Newly-provisioned user homes have default umask permissions (`755`/`644`) on sensitive files. In particular: - `~/hero/code/secrets/secrets.toml` — world-readable (644) with forge tokens, LLM API keys, DB passwords, SMTP creds - `~/hero/cfg/hero_cfg.toml` — explicitly `chmod 644` in `bridge_save_state` (less sensitive: mycelium bridge name + prefix, but still exposes topology) - Home dir itself — never locked down, so any other user can `cd /home/<someone>` and browse Sampled on a live host: 13 users' `secrets.toml` readable cross-user via: ``` find /home -maxdepth 5 -name secrets.toml -perm -o=r ``` ### Location `tools/modules/installers/multiuser.nu` — `multi_user_add` chowns the home to the user but never `chmod`s it down. The only paths explicitly tightened are SSH-related (`~/.ssh/*`). Home and `~/hero/code/secrets/*` inherit `755`/`644` from the btrfs template snapshot. `bridge_save_state` (line 127) explicitly sets `~/hero/cfg/hero_cfg.toml` to `644`. ### Proposed fix In `multi_user_add`, after the snapshot + chown step, add: ```nu ^sudo chmod 700 $homedir if (do { ^sudo test -d $"($homedir)/hero/code/secrets" } | complete | get exit_code) == 0 { ^sudo chmod 700 $"($homedir)/hero/code/secrets" for f in ["secrets.toml" "secrets.sh"] { let p = $"($homedir)/hero/code/secrets/($f)" if (do { ^sudo test -f $p } | complete | get exit_code) == 0 { ^sudo chmod 600 $p } } } ``` Also in `bridge_save_state`: change the explicit `chmod 644` on `hero_cfg.toml` to `chmod 600`. Only the user's own tools and processes read it; no reason for it to be world-readable. ### Verification After patching, on a freshly-provisioned user: - `ls -ld ~` → `drwx------` - `ls -l ~/hero/code/secrets/secrets.toml` → `-rw-------` - `ls -l ~/hero/cfg/hero_cfg.toml` → `-rw-------` - Other users get `Permission denied` on `/home/<user>/hero/code/secrets/`. ### Impact **High, systemic.** Every user ever provisioned on a multi-user host has had their secrets cross-user-readable by default. Recommended follow-up: notify affected users, ask them to rotate anything sensitive in `secrets.toml` and run `chmod 700 ~` themselves.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_skills#107
No description provided.