ruvnet / ruvnet/RuView

fix(provision): WiFi password persisted world-readable (0644) in per-port state file

Open
#1,754 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug security
Dominant language
Rust
Stars
94.5k
Forks
12.5k
Avg merge
21h 27m
Merged PRs (30d)
43

Description

Summary

provision.py persists the WiFi password in cleartext to a per-port state file created with
default permissions, so it lands world-readable (0644) under the default umask on macOS and
mainstream Linux.

  • The password is deliberately kept in the merged state dict (provision.py:103) and
    round-tripping it is covered by tests (tests/test_provision_state.py:37), so persistence
    is intentional — the issue is only how it is protected on disk.
  • save_state() (provision.py:147-157) calls os.makedirs(state_dir, exist_ok=True) and
    open(tmp, "w") with no mode argument. Both take whatever the umask allows.
  • Observed on a real host: state directory 0755, files 0644.

Why now

Found while provisioning several nodes for hardware testing. Any local process or user on a
shared machine can read the WiFi credentials of every network the operator has provisioned.

Reproduction

$ python3 provision.py --port <PORT> --ssid <SSID> --password <PW> --target-ip <IP>
$ ls -l ~/.config/wifi-densepose/esp32-provision-state/
-rw-r--r--  ...  _dev_cu.usbmodemXXXX.json
$ python3 -c "import json;print('password' in json.load(open('<that file>')))"
True

Done when

The state directory is 0700 and state files are 0600, on both a fresh install and an
existing directory created by an earlier version.

Work items

  • os.makedirs(state_dir, mode=0o700, exist_ok=True) and os.chmod the directory on the
    existing-directory path, since makedirs does not alter an existing one.
  • Create the temp file restrictively (e.g. os.open(..., 0o600) or os.chmod before
    os.replace) so the secret is never briefly world-readable.
  • Tighten permissions on directories/files left behind by earlier versions.
  • Consider whether the password needs to be persisted at all, or could be prompted per
    invocation — that would remove the exposure rather than narrow it.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in provision.py, especially save_state() at lines 147-157, and review the state round-trip coverage in tests/test_provision_state.py. Verify directory and state-file permissions for both fresh and pre-existing paths, including the temporary file before replacement. Done means directories are 0700 and files are 0600 without briefly exposing the password.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.