# Code Review

**Scope:** entire codebase (`src/` — all `.cpp`/`.h` files, ~8,200 lines)
**Date:** 2026-07-27
**Verdict:** Solid overall — the security-sensitive paths (elevation broker, Cmd argv-based process execution, the historical work.cpp:691 file-open bug) are well hardened. All five valid findings have been fixed; the remaining `--directory` finding was determined to be invalid because it describes intentional CLI behavior.

## Action items

1. - [x] **Incomplete sanitization lets shell metacharacters into root-written lsb-release template** — `src/helper.cpp:215-269`
   `sanitizeTemplateValue()` strips only `"`, `\n`, `\r` before interpolating GUI-supplied free text (Codename/Version/Project Name from `mainwindow.cpp:646-649`, passed via `Work::writeLsbRelease()` at `work.cpp:1566-1576`) into `KEY="value"` lines written by the root-privileged helper to the `lsb-release` live-files template. It does not strip `$`, backticks, or `\`. `lsb-release`-style files are conventionally shell-sourced (`. /etc/lsb-release`), so a value like `MX $(touch /root/pwned)` typed into the Codename field survives into the ISO's `/etc/lsb-release` as live command-substitution syntax. Strip/escape `$`, `` ` ``, and `\` in `sanitizeTemplateValue()`, or write the file in a format that isn't shell-syntax at all.

2. - [x] **`--exclude` CLI option silently no-ops under non-English locales** — `src/settings.cpp:1063-1081` and `:1447-1462`
   `excludeItem()`'s static lookup map is keyed by `QObject::tr("Desktop")`, `QObject::tr("Documents")`, etc., which resolve to the *installed translation* (translators are set up in `main.cpp` before `Settings` is constructed). Its only caller, `processExclArgs()`, validates and passes the *untranslated* literal CLI values ("Desktop", "Documents", ...) straight into `excludeItem()`. Under a locale with an installed translation (verified `translations/mx-snapshot_fr.ts` maps `"Desktop"` → `"Bureau"`), `iso-snapshot-cli --exclude Desktop` under `LANG=fr_FR.UTF-8` finds no matching map key, so the exclusion function never runs — no warning, no error, but the folder ships in the snapshot uncondtionally excluded item is silently NOT excluded. Key the map by the untranslated literal (matching `valid_options`), not by `tr()`.

3. - [x] **`-m`/`--month` can never accept its documented suffix argument** — `src/main.cpp:130-134`
   The option is declared as `{{"m", "month"}, description, ""}` — an empty `valueName`, which tells `QCommandLineParser` the option takes no value at all (becomes a plain flag), even though the help text documents `-m <suffix>` (e.g. "'1' for 'July.1'"). Running `iso-snapshot-cli -m 1` leaves `argParser.value("month")` empty (the `1` becomes an unconsumed positional argument) and Qt logs `option not expecting values: "month"`; `settings.cpp:1479-1480`'s suffix-append logic never fires. Give the option a non-empty `valueName` (e.g. `"suffix"`) so it actually accepts a value.

4. - [x] **Boot-menu editor launch failure is silently swallowed in CLI batch mode** — `src/batchprocessing.cpp:100-110`
   `Cmd().proc(editorProgram, ...)` returns a `bool` success/failure that is discarded. `getEditorCommand()` always wraps the launch as `x-terminal-emulator -e <editor> ...`. In a headless CLI automation context (cron, systemd timer, SSH without `DISPLAY`, minimal build host without `x-terminal-emulator`) — exactly where `edit_boot_menu` might be left enabled from a copied config — the launch fails, the log claims "The program will pause the build and open the boot menu," it doesn't actually pause, and the build silently proceeds with the unedited boot menu. Check the return value and log/abort on failure.

5. - [x] **`resetCustomExcludes()` can permanently delete the user's exclude file on a failed reset** — `src/excludesutils.cpp:82-118`
   The existing `configuredPath` file is backed up and then removed *before* the replacement copy from `sourcePath` is attempted (line 104 removes, line 111 copies). If the final `QFile::copy(sourcePath, configuredPath)` fails (e.g. `sourcePath` becomes unreadable between the caller's existence check and this copy, or the target filesystem fills up), the function returns `false` but the user's original file is already gone — only a timestamped backup remains, nothing restores it. Copy the new file to a temp path first and only remove/replace the original after the copy succeeds (or restore from the backup on failure).

6. - [x] **INVALID — `--directory` CLI argument double-appends `/snapshot` inconsistently with the config-file path** — `src/settings.cpp:1364-1373` vs `:1228-1230`
   The CLI option intentionally accepts a parent directory and places output in its `snapshot/` child; this behavior predates the current implementation and matches the project's convention that snapshot output lives in a directory named `snapshot`. Therefore, `--directory /mnt/backup/snapshot` designates `/mnt/backup/snapshot` as the parent and `/mnt/backup/snapshot/snapshot` as the output directory. The config loader's suffix guard lets `snapshot_dir` represent either the conventional final path (including the shipped `/home/snapshot` default) or a parent path; it does not establish that `--directory` has the same input semantics. Changing the CLI path handling would be a behavior/UX change, not a bug fix justified by this finding.
