# Code Review

Review scope: current working tree, with emphasis on the snapshot pipeline, its
privileged helper, and CLI/GUI error handling. Findings are ordered by severity.

1. [x] **Critical - Do not execute the user-configured editor through a root shell.** `Settings::getEditor()` returns `guiEditor` from the invoking user's settings without restricting it to an executable ([src/settings.cpp](src/settings.cpp#L536)); `Batchprocessing` concatenates that value into a `bash -c` script ([src/batchprocessing.cpp](src/batchprocessing.cpp#L102)). When the CLI is launched as root and `edit_boot_menu` is enabled, a user who can modify that configuration can run arbitrary commands as root. Represent the editor and its arguments as a `QStringList`, validate/resolve the executable, and invoke it directly with `QProcess` rather than constructing a shell command.
   - **Fixed.** `getEditor()` was replaced by `getEditorCommand()`, which returns a `QStringList`: the configured value (or the `.desktop` `Exec=` fallback) is parsed with `QProcess::splitCommand` (quote-aware, no shell evaluation), `.desktop` field codes are dropped, and the executable must resolve via `QStandardPaths::findExecutable` or the entry is rejected and the fallback chain continues (ending at nano). The shell constructs the old string relied on are now computed in C++: `$(logname)` → `Cmd::loggedInUserName()`, `$DISPLAY`/`$XAUTHORITY` → `qEnvironmentVariable` passed as literal `env` arguments. All three call sites (CLI boot-menu edit, GUI boot-menu edit, GUI excludes edit) now execute the list directly via `Cmd::proc(program, args)` — no `bash -c` anywhere in the path, so neither the configuration, a crafted `Exec=` line, nor the appended file paths can inject shell commands. Residual (accepted): the feature inherently runs the invoking user's chosen editor binary from a root CLI process, but that user already holds root via sudo; injection through content is what's eliminated.

2. [x] **High - Replace the predictable shared `/tmp/snapshot-checksum-temp` directory.** The preempt checksum path reuses a fixed, world-writable temporary directory and recursively deletes it ([src/work.cpp](src/work.cpp#L991), [src/work.cpp](src/work.cpp#L997)). A concurrent run can corrupt another run's checksum; a local user can pre-create or alter this directory and its entries, causing root-run snapshots to copy to attacker-controlled paths or remove attacker-controlled content. Use a per-run `QTemporaryDir` (or `mkdtemp`) with mode `0700`, keep it owned by the process, and remove the directory only that instance created.
   - **Fixed.** `Work::makeChecksum()` now creates a per-run `QTemporaryDir("/tmp/snapshot-checksum-XXXXXX")` (mkdtemp: mode 0700, owned by the process, unpredictable name) and falls back to checksumming in place if creation fails. The directory is removed only by the `QTemporaryDir` destructor of the instance that created it. `ChecksumUtils::preemptCommand()`'s contract changed accordingly: the script requires the directory to already exist and never creates or deletes it (the `mkdir`, `rm -rf`, and `.keep` logic are gone), so it can't be lured into an attacker-prepared path — enforced by tests, including one asserting a missing temp dir is a hard failure rather than an invitation to `mkdir`. The stale-file pre-clean step was dropped too; it only existed because the shared directory could hold leftovers from a previous interrupted run.

3. [x] **High - Fail the snapshot when `isohybrid` fails.** `createIso()` ignores the result of `isohybrid --uefi` ([src/work.cpp](src/work.cpp#L910)). The method then sets `done = true` and reports completion ([src/work.cpp](src/work.cpp#L928)), so a requested hybrid ISO can be reported as successful even though it was never made bootable in the requested mode. Check the return value, emit an error, and return `false` before marking the run complete.
   - **Fixed.** `createIso()` now checks the `isohybrid --uefi` result, emits a critical error, and returns `false` before `done = true` (with the usual `cleanupStarted` guard for user-cancelled runs).

4. [x] **High - Propagate checksum failures instead of reporting success.** `makeChecksum()` is `void` and discards the return status from the checksum shell command ([src/work.cpp](src/work.h#L96), [src/work.cpp](src/work.cpp#L1023)). Both optional final checksums are consequently ignored ([src/work.cpp](src/work.cpp#L920)), yet the ISO is reported as successfully completed. Return `bool`, verify both the command result and the expected checksum file, then abort the workflow when a requested checksum cannot be produced.
   - **Fixed.** `makeChecksum()` is now `[[nodiscard]] bool`: it returns the command status AND-ed with a check that the expected checksum file exists and is non-empty (`ChecksumUtils::verifyChecksumFile`). All six call sites (vmlinuz, initrd.gz, airootfs.sfs, linuxfs, and the two final ISO checksums) abort with a critical error on failure — via `cleanUp()` in `copyNewIso()`, via `return false` before `done = true` in `createIso()`. The checksum scripts moved to a pure `ChecksumUtils` unit (src/checksumutils.{h,cpp}); the preempt script also gained `set -e` so a failed `cp`/checksum inside it is no longer masked by the trailing temp-dir removal succeeding.

5. [x] **Medium - Stop initrd creation on module-copy failures.** `copyModules()` ignores the status of both `copy-initrd-modules` and the elevated `copy-initrd-programs` call ([src/work.cpp](src/work.cpp#L482)). `copyNewIso()` continues to repack and checksum the initrd ([src/work.cpp](src/work.cpp#L700)), allowing an ISO with missing kernel modules/programs to be marked complete. Make `copyModules()` return `bool` and exit through `cleanUp()` when either command fails.
   - **Fixed.** `copyModules()` is now `[[nodiscard]] bool`, failing when either `copy-initrd-modules` or `copy-initrd-programs` fails (the cosmetic `chown` fixup stays best-effort). `copyNewIso()` emits a critical error and exits through `cleanUp()` on failure, before the initrd is repacked.

6. [x] **Medium - Check errors while preparing the ISO template and package list.** Several required commands have their results discarded: template extraction and initrd copy ([src/work.cpp](src/work.cpp#L638), [src/work.cpp](src/work.cpp#L656)), and package-list generation ([src/work.cpp](src/work.cpp#L1244)). A failed extraction/copy can become a late, less actionable ISO failure; a failed package list can yield a superficially successful ISO missing expected metadata. Convert the relevant methods to return success values and verify output files/directories before continuing.
   - **Fixed.** Template extraction (both the Debian and Arch `tar xf` calls), the template-initrd copy, and the kernel copy now abort through `cleanUp()` with a critical error on failure. `savePackageList()` (both branches) runs its pipeline with `set -o pipefail` (so a `dpkg`/`pacman` failure is no longer masked by `awk` succeeding on empty input), verifies the output file is non-empty, and aborts through `cleanUp()` otherwise — both frontends already check `isCleaningUp()` right after the call.

7. [x] **Low - Reject unknown or unusable filesystems in `isOnSupportedPartition()`.** The helper treats every filesystem type outside a short denylist as supported ([src/filesystemutils.cpp](src/filesystemutils.cpp#L29)). Invalid/unready `QStorageInfo` values and unlisted remote or pseudo filesystems can therefore be selected for temporary snapshot output, only to fail later. Require `isValid()`, `isReady()`, and `!isReadOnly()` before accepting a location; consider an allowlist for filesystems tested with the snapshot workflow.
   - **Fixed.** `isOnSupportedPartition()` now rejects storage that is not valid, not ready, or read-only, and the denylist was flipped to an allowlist of local POSIX filesystems (`ext2/3/4`, `btrfs`, `xfs`, `f2fs`, `jfs`, `tmpfs`, `zfs`, `bcachefs`, `nilfs2`, `reiserfs`, plus `overlay`/`aufs` for live sessions). Unknown types, every `fuse.*` spelling, `nfs`/`nfs4` (which the old denylist missed entirely), and VM shared folders (`vmhgfs`/`vboxsf`/`virtiofs`) all fall out structurally instead of by enumeration. The classifier is exposed as a pure `isSupportedFilesystemType()` and covered by a new `test-filesystemutils` target (accepted/rejected type tables + nonexistent-path rejection). The cost asymmetry justifies the strict allowlist: a wrong rejection just moves the work dir to `/tmp`/`/home`, while a wrong acceptance surfaced as an hours-late mksquashfs/xorriso failure.
   - **Follow-up (review feedback):** `Settings::checkTempDir()` validated `/home` but then expanded it to `/home/<user>` without revalidating, so a user home on a separate NFS/FUSE/ecryptfs mount could still become the work dir. The expansion now happens *before* validation and free-space comparison (for both the configured value and the fallback candidate), so every check acts on the directory actually used.

8. [x] **Low - Add workflow-level regression tests for required artifact failures.** Existing tests cover command framing and SquashFS argument construction, but not the failure paths above ([tests/test_cmd.cpp](tests/test_cmd.cpp#L1), [tests/test_squashfsutils.cpp](tests/test_squashfsutils.cpp#L1)). Add tests with failing command stubs for `isohybrid`, checksum creation, module copying, and template extraction, asserting that the workflow does not mark the run successful.
   - **Partially addressed** alongside the fixes for items 3-6. `tests/test_checksumutils.cpp` executes the extracted checksum scripts through bash exactly as `Work::makeChecksum` does — including with a failing `md5sum` stub on `PATH` and a missing source file — and asserts the failure is visible to the caller (non-zero exit, no valid checksum file, empty redirect leftovers rejected). The isohybrid/module-copy/template-extraction checks added in items 3, 5, and 6 live inside `Work`, which cannot currently be instantiated in a test (`Settings` is concrete, probes the host system in its constructor, and is not injectable); true workflow-level "run not marked successful" assertions for those paths would need a `Settings`/`Cmd` seam first.
