# Code Review - MX User Manager

This document contains a comprehensive code review of the `mx-user` codebase. The identified issues are listed below with checkboxes to track their resolution.

## Summary of Findings
- **High Severity**: 0 open; 2 fixed (helper privilege escalation, destructive `sed` on binary files)
- **Medium Severity**: 0 open; 4 fixed (rename find path, nested event-loop re-entrancy, incomplete keyPressEvent override, overly broad `chown`)
- **Low Severity**: 0 open; 5 fixed (GUI thread blocking, missing closeEvent override, context menu allocation, connection accumulation, missing application name)
- **Invalid Findings**: 0

## Validation Pass
- Checked against the current source tree on 2026-06-15.
- No listed finding was invalidated by the current implementation.
- Items 10 and 11 are already fixed in the current tree and remain checked for that reason.
- Item 8 remains valid, but the old note understated the risk: closing the window through the window manager bypasses `closeApp()` and may tear down the dialog while a nested process loop is active.

## Fix Log (2026-06-15)
- **Item 1 (helper privilege escalation) — FIXED.** The generic `exec <cmd> <args>` entry point was removed. The helper (`helper.cpp`) now exposes a fixed set of named operations (`add-user`, `del-user`, `set-groups`, `autologin-enable`, `chown-home`, `rsync-copy`, ...). Each builds its command line entirely from internal constants and accepts only validated scalar parameters: usernames/group names (regex), a login shell (must be in `/etc/shells` or an existing executable), display-manager session tokens (enum), and paths constrained to `/home` with no `..` traversal. A caller can no longer inject a path, option, sed script, `find -exec`, or arbitrary command. `cmd.cpp`/`cmd.h` replaced `procAsRoot`/`getOutAsRoot` with `runHelper(actionArgs)`, and every call site in `mainwindow.cpp` was migrated. Verified: `helper exec rm -rf /` is rejected ("Unsupported helper action"), as are out-of-`/home` paths, traversal, and malformed names; valid operations pass validation and reach the underlying tool. (The `auth_admin_keep` policy was left unchanged to preserve the multi-operation UX; the helper is now a genuine boundary regardless of the cached-authorization window.)
- **Item 3 (rename `find` search path) — FIXED incidentally.** The rename home-path rewrite now runs inside the helper's `rename-user` operation against `/home/<new>` (the relocated home), so it no longer targets the non-existent `/home/<old>`.
- **Item 7 (pgrep on every selection change) — FIXED.** The running login manager is detected once at startup (`detectDisplayManager()`, cached in the `displayManager` member) instead of spawning `pgrep` for lightdm/sddm/plasmalogin on every `userComboBox` activation and in `refreshOptions`. `userComboBox_activated` switches on the cached value; `refreshOptions` uses it for the autologin group-box visibility.
- **Item 8 (missing `closeEvent` override) — FIXED.** Added `MainWindow::closeEvent`, which holds the "process not done" confirmation and accepts/ignores the close. `closeApp()` now just calls `close()`, so the Cancel button, Escape, and the window-manager [X] all funnel through `closeEvent` and get the same confirmation — the window can no longer be closed silently while a privileged process is running.
- **Item 6 (overly broad `chown` in `syncDone`) — FIXED.** `syncDone` now re-appends the copied subdirectory (`/Documents`, `/.mozilla`, `/Shared` — mirroring `applyDesktop`) to `toDir` before `chown-home`, so a subset copy chowns only the copied subtree instead of the whole destination home. The `entire` case appends nothing and still chowns `/home/<toUser>` as intended; the `.recently-used`/OpenOffice-lock cleanups run only in the `entire` branch and are unchanged.
- **Item 5 (incomplete `keyPressEvent` override) — FIXED.** `MainWindow::keyPressEvent` now calls `QDialog::keyPressEvent(event)` for any key other than Escape, so the dialog's standard key handling (e.g. Enter activating the default button) is no longer swallowed. (As the finding notes, Tab navigation and shortcuts were never affected — they are handled before `keyPressEvent`.)
- **Item 4 (nested `QEventLoop` re-entrancy) — FIXED.** `Cmd::proc()` now runs its synchronous nested loop as `loop.exec(QEventLoop::ExcludeUserInputEvents)`, so mouse/keyboard input is deferred while a command runs — the user can no longer change selections, re-trigger Apply, or Escape/close the dialog mid-operation. Process-I/O, timer, and paint events still process, so rsync progress and repaints continue; queued input is delivered after the command finishes. One central change covers every privileged call.
- **Item 2 (destructive `sed -i` on binary files) — FIXED.** The home-path rewrite (helper `opRewriteHomePaths`, used by both `rename-user` and the Copy-tab `rewrite-home-paths`) now gates `sed` behind a `grep -I` test within the same `find`: `find <dir> -type f -exec grep -IqF -e "home/<old>" {} ; -exec sed -i 's|home/<old>|home/<new>|g' {} +`. `grep -I` reports binary files as non-matching, so find's AND-chain runs `sed` only on text files that actually contain the old path — binaries (SQLite DBs, caches, browser profiles, images) are skipped instead of corrupted. Verified in /tmp: a text file is rewritten while a binary file containing the literal `home/<old>` bytes is left byte-for-byte intact.

---

### Issues List

1. - [x] **[HIGH] — FIXED (see Fix Log)** **Security Vulnerability: Arbitrary Root Command Execution via Privileged Helper**
   - **Location:** [helper.cpp:L57-81](file:///home/adrian/mx-user/helper.cpp#L57-L81) (`allowedCommands`) and [cmd.cpp:L64-82](file:///home/adrian/mx-user/cmd.cpp#L64-L82) (`helperProc`).
   - **Description:** The helper executable runs as root via `pkexec` and is designed to act as a gatekeeper restricting operations to a predefined list of commands (such as `rm`, `find`, `sed`, `chown`). However, the helper does not sanitize or validate command-line arguments, forwarding them unchecked. Additionally, the Polkit policy (`scripts/org.mxlinux.pkexec.mx-user-helper.policy`) uses `auth_admin_keep`, which caches root authorization for the session (~5 minutes). During this window, any other process in the user session can call the helper without a password prompt. An attacker or malicious script could exploit this to execute arbitrary commands as root (e.g., calling `/usr/lib/mx-user/helper exec rm -rf /` or using `/usr/bin/find`'s `-exec` option), bypassing all safety checks and resulting in a local privilege escalation.
   - **Recommended Fix:** The helper should validate both the commands *and* their arguments (e.g., ensuring paths are restricted to `/home/<user>`), or the helper should implement the operations directly in C++ using secure system APIs rather than spawning external shells/binaries with user-supplied arguments. Consider using `auth_admin` instead of `auth_admin_keep` to avoid cached authorization windows.

2. - [x] **[HIGH] — FIXED (see Fix Log)** **Destructive `sed -i` Operations on Binary Files**
   - **Location:** [mainwindow.cpp:L732-734](file:///home/adrian/mx-user/mainwindow.cpp#L732-L734) (`applyRename`) and [mainwindow.cpp:L787-791](file:///home/adrian/mx-user/mainwindow.cpp#L787-L791) (`syncDone`).
   - **Description:** The application uses `find -type f -exec sed -i 's|...|...|g' {} +` to rewrite home directory references inside the user's home directory. Doing this blindly on all files (`-type f`) runs `sed` on binary files (such as SQLite databases, caches, browser profiles, and index files), causing data corruption because `sed` treats them as text and rewrites their contents.
   - **Recommended Fix:** Avoid running `sed -i` on arbitrary files in the home directory. Instead, filter search results to text/config files only, or target specific configuration directories and known file formats (e.g., desktop files, shell profiles) rather than running it recursively on every file.

3. - [x] **[MEDIUM] — FIXED (see Fix Log)** **Renaming Bug: `find` Search Path is Incorrect**
   - **Location:** [mainwindow.cpp:L732-734](file:///home/adrian/mx-user/mainwindow.cpp#L732-L734) (`applyRename`).
   - **Description:** When a user is renamed, `usermod --login new_name --move-home --home /home/new_name old_name` is executed first to rename the user and move the home directory. Right after, the application attempts to update path references inside the user's files using a `find` command. However, it sets the search directory to `/home/old_name` (which no longer exists because it was moved by `usermod`). The `find` command fails immediately with a "No such file or directory" error, meaning references to the old home directory inside the user's configuration files are never updated.
   - **Recommended Fix:** Update the `find` command search path to `/home/new_name` instead of `/home/old_name`.

4. - [x] **[MEDIUM] — FIXED (see Fix Log)** **UI Re-entrancy Risks via Nested `QEventLoop`**
   - **Location:** [cmd.cpp:L103-111](file:///home/adrian/mx-user/cmd.cpp#L103-L111) (`proc`).
   - **Description:** The `Cmd::proc()` function starts a nested `QEventLoop` synchronously while a process is running to block the current function execution. However, nested event loops allow the main Qt GUI event loop to continue processing user events. While `cmd.cpp` has a guard (`if (state() != QProcess::NotRunning) return false;`) that prevents a second process from starting concurrently, the user can still interact with other GUI elements (e.g. changing selections, closing windows, opening other dialogs), which can lead to UI inconsistencies or silent failures.
   - **Recommended Fix:** Disable the main window controls or show a modal progress/busy dialog while operations are running.

5. - [x] **[MEDIUM] — FIXED (see Fix Log)** **Incomplete `keyPressEvent` Override Swallows Dialog Submission Keys**
   - **Location:** [mainwindow.cpp:L816-821](file:///home/adrian/mx-user/mainwindow.cpp#L816-L821) (`keyPressEvent`).
   - **Description:** `MainWindow` overrides `keyPressEvent` to handle the Escape key. However, if the key pressed is *not* Escape, the function exits without invoking the base class implementation `QDialog::keyPressEvent(event)`. This bypasses standard dialog-level key handling (most notably, pressing Enter/Return to submit forms or activate default buttons). Note that widget-level events (like Tab navigation) and shortcuts/mnemonics are handled before `keyPressEvent` is reached, so they are unaffected.
   - **Recommended Fix:** Call the base class implementation `QDialog::keyPressEvent(event)` for all unhandled keys:
     ```cpp
     void MainWindow::keyPressEvent(QKeyEvent *event)
     {
         if (event->key() == Qt::Key_Escape) {
             closeApp();
         } else {
             QDialog::keyPressEvent(event);
         }
     }
     ```

6. - [x] **[MEDIUM] — FIXED (see Fix Log)** **Overly Broad `chown` Operation in `syncDone`**
   - **Location:** [mainwindow.cpp:L777](file:///home/adrian/mx-user/mainwindow.cpp#L777) (`syncDone`).
   - **Description:** When copying or synchronizing a subset of a desktop (such as only "Documents" or ".mozilla" folders), the application executes a recursive `chown -R` on the *entire* home directory of the destination user (`/home/toUser`) instead of restricting it to the copied subdirectory. This can overwrite file ownership for other files in the user's home directory that might intentionally belong to other groups/users and will be extremely slow for large directories.
   - **Recommended Fix:** Only perform the ownership change on the specific directories/files that were copied.

7. - [x] **[LOW] — FIXED (see Fix Log)** **UI Thread Blocking: Spawning Processes on Selection Change**
   - **Location:** [mainwindow.cpp:L903-920](file:///home/adrian/mx-user/mainwindow.cpp#L903-L920) (`userComboBox_activated`).
   - **Description:** Every time the selected user in `userComboBox` changes, the application executes multiple `pgrep` commands (`lightdm`, `sddm`, `plasmalogin`) sequentially using `QProcess::execute` on the main GUI thread. While `pgrep` is fast enough that the lag is minimal (single-digit milliseconds), spawning child processes synchronously on the GUI thread is a poor design practice that should be cleaned up.
   - **Recommended Fix:** Detect the display manager once during application initialization or refresh, and store the result in a member variable rather than spawning external processes on every combo box change.

8. - [x] **[LOW] — FIXED (see Fix Log)** **Missing `closeEvent` Override**
   - **Location:** [mainwindow.h](file:///home/adrian/mx-user/mainwindow.h) and [mainwindow.cpp](file:///home/adrian/mx-user/mainwindow.cpp).
   - **Description:** While `closeApp()` is called when clicking the "Cancel" button or pressing Escape, it is *not* called when the user closes the dialog using the window manager's `[X]` close button on the title bar. This allows the user to close the window without confirmation. If a privileged process is active, this path bypasses the `shell->state()` check in `closeApp()` and can destroy the dialog while the nested process loop is active.
   - **Recommended Fix:** Override `closeEvent(QCloseEvent *event)` in `MainWindow` and call `closeApp()`.

9. - [x] **[LOW]** **Memory Leak: Bounded Context Menu Allocation in `PassEdit`**
   - **Location:** [passedit.cpp:L137-147](file:///home/adrian/mx-user/passedit.cpp#L137-L147) (`masterContextMenu`).
   - **Description:** The `QMenu *menu` created via `master->createStandardContextMenu()` is allocated on the heap. Because it is parented to the line edit (`master`), it is not a permanent memory leak and will be cleaned up when the dialog is destroyed. However, if the user right-clicks the password field multiple times, it creates and accumulates multiple `QMenu` objects in memory during the dialog's lifetime.
   - **Recommended Fix:** Set the menu attribute to auto-delete on close since `popup()` is non-blocking:
     ```cpp
     menu->setAttribute(Qt::WA_DeleteOnClose);
     ```

10. - [x] **[LOW]** **Accumulating Signal Connections in `MainWindow::applyDesktop`**
   - **Location:** [mainwindow.cpp:L372](file:///home/adrian/mx-user/mainwindow.cpp#L372) (`applyDesktop`).
   - **Review Status:** Already fixed in the current tree. `applyDesktop()` now stores the connection handle and disconnects it after the synchronous `rsync` call returns.
   - **Description:** Every time the user clicks "Apply" on the Copy tab, a new lambda connection is added to the shell's `outputAvailable` signal. These connections are never disconnected, meaning they accumulate over time and result in multiple lambdas executing redundant work.
   - **Recommended Fix:** Disconnect the signal or store the connection object and disconnect it when the process finishes, or set up the connection once during initialization.

11. - [x] **[LOW]** **Lack of Explicit Application Name Setting**
   - **Location:** [main.cpp:L48](file:///home/adrian/mx-user/main.cpp#L48).
   - **Review Status:** Already fixed in the current tree. `main.cpp` now calls `QApplication::setApplicationName("mx-user");` before the application icon, settings path, translations, and helper path are resolved.
   - **Description:** While `QApplication::setOrganizationName("MX-Linux")` is called, `QApplication::setApplicationName` is never called. Qt falls back to the binary name, but explicitly setting it is best practice to avoid path resolution differences.
   - **Recommended Fix:** Add `QApplication::setApplicationName("mx-user");` in `main.cpp`.
