fix(cat): move RigctlPty symlink out of /tmp (GHSA-qxhr-cwrc-pvrm)#3027
Conversation
Fixes GHSA-qxhr-cwrc-pvrm / #2940. Phase 1 of the #2940 audit found that RigctlPty's convenience symlink at `/tmp/AetherSDR-CAT-{A,B,C,D}` is shared across all local users (no per-user namespace) and uses a non-atomic `unlink + symlink` sequence that has a TOCTOU window on non-sticky-bit /tmp filesystems. Sticky-bit /tmp + PTY-side mode 0620 mitigate most of the risk, but the symlink still causes cross-user conflicts (one operator's discovery symlink blocks another's) and the /tmp placement isn't the right hygiene for a per-user resource. ## What changed New static `RigctlPty::defaultSymlinkPath(int sliceIndex)` returns the canonical per-user symlink location: Linux: $XDG_RUNTIME_DIR/aethersdr/cat-{A,B,C,D} (systemd's /run/user/${UID}, mode 0700, tmpfs, ephemeral) Linux fallback: $HOME/.cache/aethersdr/cat-{A,B,C,D} (when XDG_RUNTIME_DIR isn't set — containers, minimal init) macOS: $HOME/Library/Caches/AetherSDR/cat-{A,B,C,D} (per-user, mode 0700 by default) Resolution uses QStandardPaths::RuntimeLocation first, falls back through CacheLocation to the home-relative path. The aethersdr/ subdir is created with `mkpath` and tightened to mode 0700 via chmod so a hostile group member can't readlink() the PTY path even on shared systems with a shared primary group. ## Atomic replacement The `unlink()` + `symlink()` pair is replaced with a `symlink(slaveName, tmpPath)` + `rename(tmpPath, finalPath)` pattern. `rename()` is atomic across the same filesystem; the temp path is in the same per-user directory so the rename never crosses filesystems. No TOCTOU window where a hostile actor could insert a symlink between unlink and create. ## UI sync `MainWindow` no longer hardcodes `/tmp/AetherSDR-CAT-${letter}` for `setSymlinkPath()` — it calls `RigctlPty::defaultSymlinkPath(i)`. `CatControlApplet` does the same for the displayed path label and for the reset-on-stopped() handler so the UI always reflects where the symlink will actually appear. ## Migration impact CAT-capable software (WSJT-X, fldigi, etc.) that was configured to read `/tmp/AetherSDR-CAT-A` etc. will need to be re-pointed at the new path. The path is displayed in the CAT applet so the operator can see where to point their software. Release notes will call this out. ## Test plan - [x] Clean build, 0 warnings - [x] Existing test suite passes (18/18) - [ ] Manual: start AetherSDR, open CAT applet, confirm displayed path is under $XDG_RUNTIME_DIR/aethersdr or equivalent - [ ] Manual: verify the symlink actually exists at that path and points to the PTY slave (/dev/pts/N or /dev/ttysXXX) - [ ] Manual: re-point WSJT-X (or any CAT software) at the new path, confirm CAT commands flow - [ ] Manual: stop the PTY, confirm symlink is removed; restart, confirm atomic replacement works (no stale .tmp file) Closes #2940 (RigctlPty half). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks @ten9876 — the security fix is solid and the design choices are well-justified. A few notes:
One real gap: in-app help still points at /tmp
resources/help/understanding-data-modes.md has ~8 references to /tmp/AetherSDR-CAT-* (lines 148-151, 158, 292-293, 433, 482, 552-555). That file ships with the binary and is the doc users consult when setting up WSJT-X/fldigi. After this PR merges, the applet UI shows the new per-user path but the help doc still walks them through /tmp/AetherSDR-CAT-A, which won't exist.
Suggest updating the help table/examples in this PR (or a fast-follow) — probably to a placeholder like <runtime>/aethersdr/cat-A with a note that the actual path is visible in the CAT applet row. The release-notes mention you've already planned for is good, but the help file is the more durable surface.
Smaller items
src/gui/CatControlApplet.h:52still has the stale comment// "/tmp/AetherSDR-CAT-A"next to theptyPathQLabel member. Easy to update while you're there.::chmod(parentDir, 0700)failure is silently ignored. Defensible as best-effort hardening, but on the~/.cache/aethersdrfallback with a permissive umask, the dir could end up 0755 without anyone noticing. AqCWarningonchmod != 0would be cheap diagnostic value with no downside.
Verified
- Atomic-replace pattern is correct:
unlink(tmp) + symlink(slave, tmp) + rename(tmp, final)removes the TOCTOU window on the final path. The tiny window on the.tmpfile is closed by the parent dir being mode 0700 (only same-uid can interpose, who has stronger powers anyway). <sys/stat.h>include and::chmodcall are both inside the existing#ifndef _WIN32blocks.defaultSymlinkPath()lives outside the_WIN32guard but only usesQStandardPaths/QDir/QString— fine to compile on Windows, andstart()is a no-op there anyway.- Header default-member-initializer
m_symlinkPath{defaultSymlinkPath(0)}correctly delegates to the static function; no drift between the default and the override path used fromMainWindow. - Case-insensitive
aethersdrdetection correctly avoids double-naming on macOS (~/Library/Caches/AetherSDRalready carries the segment). - No
QSettings/ flat-key concerns — Principle V N/A as stated.
Holding off on approval for the docs gap, but the core fix LGTM.
🤖 aethersdr-agent · cost: $4.6474 · model: claude-opus-4-7
- Update resources/help/understanding-data-modes.md (all 8 sites) to use a <runtime>/aethersdr/cat-X placeholder with a per-platform key the first time the path appears. The CAT Control tile shows the resolved path, which is the most reliable thing to copy. The doc ships with the binary, so it's a more durable surface than the release notes for users following the WSJT-X / fldigi / Winlink setup walkthroughs. - Drop stale "/tmp/AetherSDR-CAT-A" comment next to the ptyPath QLabel in CatControlApplet.h — now points at RigctlPty::defaultSymlinkPath(). - Log a qCWarning if chmod(0700) on the parent dir fails. Cheap diagnostic value; the CacheLocation fallback path is where the dir could land at umask-default (0755) if we don't notice. Adds <cerrno> to be explicit about the errno include even though unistd.h pulls it in transitively on POSIX. Build verified locally; no behavioural change beyond the warn log. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed all three items from your review in
Local build verified clean. Ready for a re-look. |
## Summary Release prep for v26.5.3 — Aetherial Audio TX completion + security hardening + 100-commit reliability sweep. - **CHANGELOG.md** — new v26.5.3 section: 138 commits across 14 contributors since v26.5.2.1 - **README.md** — version line bump 26.5.2.1 → 26.5.3 - **CMakeLists.txt** — already at 26.5.3 (set in #3024) ## Highlights - **Aetherial Audio TX completion** — PAPR all-pass cascade + split-band DESS, full per-stage CHAIN UI - **Security advisories** — GHSA-wfx7-w6p8-4jr2 (WAN cert-pin Phase 2, #3026) + GHSA-qxhr-cwrc-pvrm (CAT PTY symlink, #3027) - **MQTT settings dialog refactor** (#3051, @s53zo) — JSON-array topics + button-event lists, nested-JSON consolidation - **Audio reliability sweep** — WASAPI silent-open watchdog, mono-only USB PnP mic recovery, MQTT subscribe diff - **Native Hamlib NET rigctl** — eliminates external rigctld dependency - **Stream Deck integration** — Elgato SDK plugin (Win/Mac/Linux-via-OpenDeck) ## Contributors thanked @jensenpat (16), @aethersdr-agent (22 bot), @NF0T (12), @rfoust (9), @Ozy311 (5), @M7HNF-Ian (5), @chibondking (5), @M8WLO (4), @s53zo (2), @pepefrog1234 (2), @K5PTB (2), @chrisb1964 (1) ## Test plan - [ ] CI green (build + check-paths + check-windows) - [ ] CHANGELOG renders correctly on GitHub - [ ] README version banner shows 26.5.3 - [ ] After merge: tag v26.5.3 once CI green on main 73, Jeremy KK7GWY & Claude (AI dev partner) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ethersdr#2962) Implements a native SmartCAT-compatible TCP CAT server, enabling band, mode, and frequency sync with N1MM, fldigi, WSJT-X, MacLoggerDX, and other logging/digital-mode software via the industry-standard CAT protocol. ## Why CAT instead of rigctld N1MM does not speak the Hamlib NET rigctl protocol; it requires a CAT port (TS-2000 or FlexCAT dialect). This made a full CAT implementation necessary for N1MM compatibility. Not1MM (a separate Linux-only product) uses rigctld — that is covered by aethersdr#2975. ## Protocol coverage - Full Kenwood TS-2000 command set per the manual appendix - Full FlexCAT command set per SmartCAT User Guide v4.1.5, except commands not applicable to a Flex radio (see PR body for gap list) - Two TCP ports; each port has a companion PTY that accepts the same commands — operators with existing SmartCAT/SmartDAX configurations can keep them unchanged ## Integration testing Tested against real hardware (FlexRadio 6500, firmware 4.2.18): | App | Connection | Result | |--------------|-----------|--------| | fldigi | TCP + PTY | ✓ | | WSJT-X | TCP + PTY | ✓ | | MacLoggerDX | TCP | ✓ | | N1MM+ | TCP | ✓ (CAT sync; CW keying not possible — see below) | Primary platform: macOS. Also tested on Raspberry Pi 5 / Raspberry Pi OS Trixie. ### N1MM CW keying — known limitation N1MM keys CW by toggling RTS/DTR on a real serial COM port. Virtual serial ports (PTYs) are not exposed as COM ports in Windows, so RTS/DTR keying cannot reach AetherSDR. No workaround exists without a WinKeyer-compatible keyer emulator; documented here so operators don't waste time chasing it. ## Test suites - `CAT_TS-2000_test`: 15 sections, 103 checks (92 always-on + 11 PTT/CW gated) - `CAT_Flex_test`: 16 sections, 129 checks (105 always-on + 24 PTT/CW gated) PTT and CW keyer tests are **skipped by default** to avoid unintended transmissions. Enable with `--ptt` and `--cw` when a radio is connected and the operator is ready to transmit. Firmware TX race noted during CW testing: the radio briefly reports TX=0 via CAT while the PA is still hot after CW completes. Safety watches added to the test suite; documented as known hardware behaviour. ## CAT applet redesign Configuration moved to a pop-out window (port number, dialect, VFO A/B slice assignment per port) rather than the right panel. Inspired by SmartSDR for macOS but more intuitive. The pattern — space-consuming config in a pop-out, status summary in the applet — is worth adopting for other applets. UI details: dynamic PTY-path column sizing; "Flex" dialect label (was "FlexCAT"); VFO column widths; column stretch. ## Security - Applied the PTY symlink fix (same pattern as GHSA-qxhr-cwrc-pvrm, aethersdr#3027) to CatPort: per-user symlink directories, atomic rename instead of unlink+symlink. ## Code quality - UAF audit: SmartCatProtocol and the new RigctlProtocol additions are both clean — no `[this]` captures in queued lambdas; all dispatch is synchronous on the main thread. - ThemeManager: `CatControlApplet` was included in the Phase 2 mass migration (aethersdr#3102). Because this branch replaces the entire applet, that migration did not carry forward. We applied `ThemeManager::applyStyleSheet()` throughout — both the docked panel and the pop-out window — so all widgets auto-restyle on `themeChanged`, consistent with the rest of the codebase. One intentional hardcode remains: the `:checked` background `#006040` (CAT active state); there is no `color.background.success` token analogous to `color.background.tx`. Adding one would complete the conversion and is worth proposing as a follow-up. Closes aethersdr#2962 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ethersdr#2962) Implements a native SmartCAT-compatible TCP CAT server, enabling band, mode, and frequency sync with N1MM, fldigi, WSJT-X, MacLoggerDX, and other logging/digital-mode software via the industry-standard CAT protocol. ## Why CAT instead of rigctld N1MM does not speak the Hamlib NET rigctl protocol; it requires a CAT port (TS-2000 or FlexCAT dialect). This made a full CAT implementation necessary for N1MM compatibility. Not1MM (a separate Linux-only product) uses rigctld — that is covered by aethersdr#2975. ## Protocol coverage - Full Kenwood TS-2000 command set per the manual appendix - Full FlexCAT command set per SmartCAT User Guide v4.1.5, with the exception of ZZPA/ZZPE (panadapter data streaming — out of scope) - Two TCP ports by default: 4532 (rigctld) and 5001 (FlexCAT); up to 6 more ports can be configured. Each TCP port has a companion PTY that accepts the same commands and responds identically. PTY paths are shown in the pop-out applet and can be copied to the clipboard with a right-click. ## Integration testing Tested against real hardware (FlexRadio 6500, firmware 4.2.18): | App | Connection | Result | |--------------|-----------|--------| | fldigi | TCP + PTY | ✓ | | WSJT-X | TCP + PTY | ✓ | | MacLoggerDX | PTY (TS-2000 only) | ✓ | | N1MM+ | TCP | ✓ (CAT sync; CW keying not possible — see below) | Primary platform: macOS. Also tested on Raspberry Pi 5 / Raspberry Pi OS Trixie. ### N1MM CW keying — known limitation N1MM keys CW by toggling RTS/DTR on a real serial COM port. Virtual serial ports (PTYs) are not exposed as COM ports in Windows, so RTS/DTR keying cannot reach AetherSDR. No workaround exists without a WinKeyer-compatible keyer emulator; documented so operators don't waste time chasing it. ## Test suites - `CAT_TS-2000_test`: 15 sections, 103 checks (92 always-on + 11 PTT/CW gated) - `CAT_Flex_test`: 16 sections, 129 checks (105 always-on + 24 PTT/CW gated) PTT and CW keyer tests are **skipped by default** to avoid unintended transmissions. Enable with `--ptt` and `--cw` when a radio is connected and the operator is ready to transmit. Firmware TX race noted during CW testing: the radio briefly reports TX=0 via CAT while the PA is still hot after CW completes. Safety watches added to the test suite; documented as known hardware behaviour. ## CAT applet redesign Inspired by SmartSDR for macOS. The space-consuming configuration belongs in a pop-out, not the applet panel. PTY paths for running ports are shown in the table and can be copied to the clipboard with a right-click — handy when configuring external apps. ## Security - Applied the PTY symlink fix (same pattern as GHSA-qxhr-cwrc-pvrm, aethersdr#3027) to CatPort: per-user symlink directories, atomic rename instead of unlink+symlink. ## Code quality - UAF audit: SmartCatProtocol and new RigctlProtocol additions both clean — no [this] captures in queued lambdas; all dispatch synchronous on main thread - ThemeManager: CatControlApplet was included in the Phase 2 mass migration (aethersdr#3102). Because this branch replaces the entire applet, that migration did not carry forward. Applied ThemeManager::applyStyleSheet() throughout — both the docked panel and the pop-out window — so all widgets auto-restyle on themeChanged, consistent with the rest of the codebase. One intentional hardcode remains: the :checked background #006040 (CAT active state); there is no color.background.success token analogous to color.background.tx. Adding one would complete the conversion and is worth proposing as a follow-up. Closes aethersdr#2962 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ation The GHSA security fix (aethersdr#3027) moved PTY symlinks from /tmp/AetherSDR-CAT-* to per-user directories (QStandardPaths::CacheLocation/cat-{A,B,C}). All three integration test suites hardcoded the old /tmp paths as their --pty default, causing section 14/15/16 to skip every run. Replace each hardcoded string with a runtime-computed defaultPtyPath() that mirrors CatPort::defaultSymlinkPath() exactly. The --pty override still works for non-standard setups. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…3131) ## CAT Protocol Coverage - Full Kenwood TS-2000 command set, referencing the manual appendix, except commands not applicable to a FlexRadio - Full FlexCAT command set per SmartCAT User Guide v4.1.5, with the exception of ZZPA/ZZPE (panadapter data streaming — out of scope for a CAT server) - Two TCP ports by default: 4532 (rigctld) and 5001 (FlexCAT). Up to 6 more ports can be configured/enabled; each port has a companion PTY that accepts the same commands and responds identically. PTY paths are shown in the pop-out applet and can be copied to the clipboard with a right-click — handy when configuring external apps. ## Why CAT in addition to rigctld Some external apps do not support rigctld, but expect to send TS-2000 CAT commands, with extensions by FlexRadio. AetherSDR now speaks both protocols, giving operators the flexibility to use whichever their software requires — setups that currently use SmartCAT can work directly with AetherSDR without change. TCI is preferred, but this supports both legacy apps and legacy hams. ## CAT applet redesign Inspired by SmartSDR for macOS. The space-consuming configuration only appears when popped out, avoiding clutter of the right panel. ## Test suites - **`CAT_TS-2000_test`**: 15 sections, 103 checks (92 always-on + 11 PTT/CW gated) - **`CAT_Flex_test`**: 16 sections, 129 checks (105 always-on + 24 PTT/CW gated) PTT and CW keyer tests are **skipped by default** to avoid unintended transmissions. Enable with `--ptt` and `--cw` when a radio is connected and the operator is ready to transmit. A firmware TX race was noted during CW testing: the radio briefly reports TX=0 via CAT while the PA is still hot after CW completes. Safety watches are added to the test suite; documented as known hardware behaviour. ## Integration testing Tested against real hardware (FlexRadio 6500, firmware 4.2.18). Primary platform: macOS. Also tested on Raspberry Pi 5 / Raspberry Pi OS Trixie. | App | Connection | Result | |---|---|---| | fldigi | TCP + PTY | ✓ | | WSJT-X | TCP + PTY | ✓ | | MacLoggerDX | PTY (TS-2000 only) | ✓ | | N1MM+ | TCP | ✓ (CAT sync; CW keying not possible — see below) | ### N1MM CW keying — known limitation N1MM keys CW by toggling RTS/DTR on a real serial COM port. Virtual serial ports (PTYs) are not exposed as COM ports in Windows, so RTS/DTR keying cannot reach AetherSDR. No workaround exists without a WinKeyer-compatible keyer emulator. Documented here so operators don't waste time chasing it. ## Security Applied the PTY symlink fix (same pattern as GHSA-qxhr-cwrc-pvrm, #3027) to `CatPort`: per-user symlink directories, atomic rename instead of unlink+symlink. ## Code quality **UAF audit:** `SmartCatProtocol` and the new `RigctlProtocol` additions are both clean — no `[this]` captures in queued lambdas; all dispatch is synchronous on the main thread. **ThemeManager:** `CatControlApplet` was included in the Phase 2 mass migration (#3102). Because this branch replaces the entire applet, that migration did not carry forward. We applied `ThemeManager::applyStyleSheet()` throughout — both the docked panel and the pop-out — so all widgets auto-restyle on `themeChanged`, consistent with the rest of the codebase. One intentional hardcode remains: the `:checked` background `#006040` (CAT active state). There is no `color.background.success` token analogous to `color.background.tx`; adding one would complete the conversion and is worth proposing as a follow-up. Closes #2962 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ethersdr#3027) ## Summary Fixes [GHSA-qxhr-cwrc-pvrm](GHSA-qxhr-cwrc-pvrm) — one half of the aethersdr#2940 security audit. Moves the rigctld convenience symlink out of the shared `/tmp` directory into a per-user runtime/cache directory, and replaces the non-atomic `unlink + symlink` sequence with an atomic `symlink + rename` pattern. ## Why - **Cross-user conflicts**: Two users running AetherSDR on the same machine fight over `/tmp/AetherSDR-CAT-{A,B,C,D}`. Second user gets `EEXIST` on `symlink()`, their CAT software can't auto-discover the PTY. - **TOCTOU on non-sticky `/tmp`**: Rare hardening configs / sandboxed setups don't apply the sticky bit. The `unlink → symlink` gap allows an attacker to insert a symlink between the two calls. (Sticky bit + PTY mode 0620 mitigate on standard systems; this fix removes the dependency.) - **Hygiene**: A per-user resource should not live in shared `/tmp`. Right home is `$XDG_RUNTIME_DIR` (Linux) or `~/Library/Caches/` (macOS). ## What changed New static `RigctlPty::defaultSymlinkPath(int sliceIndex)`: | Platform | Path | |---|---| | Linux (systemd) | `$XDG_RUNTIME_DIR/aethersdr/cat-{A,B,C,D}` | | Linux fallback | `$HOME/.cache/aethersdr/cat-{A,B,C,D}` | | macOS | `$HOME/Library/Caches/AetherSDR/cat-{A,B,C,D}` | Parent dir created via `mkpath` and tightened to mode 0700 via `chmod` so a hostile group member can't `readlink()` the PTY path even on shared systems with shared primary groups. `RigctlPty::start()`: replaces `unlink + symlink` with `symlink(slaveName, tmpPath)` + `rename(tmpPath, finalPath)`. `rename()` is atomic; the temp file is in the same per-user directory so the rename stays within one filesystem. UI surfaces updated: `MainWindow` and `CatControlApplet` both call `RigctlPty::defaultSymlinkPath(i)` instead of hardcoding `/tmp/AetherSDR-CAT-${letter}`. The displayed path always matches the actual symlink location. ## Migration impact **User-visible change**: anyone with WSJT-X / fldigi / etc. configured to point at `/tmp/AetherSDR-CAT-A` will need to update their CAT software config to the new per-user path. The displayed path in the CAT applet shows the new location. Worth a release-notes mention. Defaulting to the canonical per-user location going forward — no automatic compatibility symlink at `/tmp` (would re-introduce the hygiene issue). ## Stats - 4 files, +98 / −17 - No new dependencies - No flat-key AppSettings additions (Principle V — N/A, this isn't settings code) ## Test plan - [x] Clean build, 0 errors - [x] Existing test suite passes (18/18) - [ ] Manual: start AetherSDR, open CAT applet, confirm displayed path is under `$XDG_RUNTIME_DIR/aethersdr/` or equivalent - [ ] Manual: verify the symlink actually exists at that path and points to the PTY slave - [ ] Manual: re-point CAT software at the new path, confirm commands flow - [ ] Manual: stop / restart the PTY, confirm atomic replacement works (no stale `.tmp` file) - [ ] Multi-user: two simultaneous AetherSDR instances on the same machine no longer conflict over the symlink path ## Checklist - [x] No new flat-key `AppSettings` calls - [x] All meter UI uses `MeterSmoother` (N/A) - [x] Documentation will be updated in release notes - [x] Security-sensitive change references GHSA-qxhr-cwrc-pvrm Closes aethersdr#2940 (RigctlPty half — VAB half is in flight separately). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary Release prep for v26.5.3 — Aetherial Audio TX completion + security hardening + 100-commit reliability sweep. - **CHANGELOG.md** — new v26.5.3 section: 138 commits across 14 contributors since v26.5.2.1 - **README.md** — version line bump 26.5.2.1 → 26.5.3 - **CMakeLists.txt** — already at 26.5.3 (set in aethersdr#3024) ## Highlights - **Aetherial Audio TX completion** — PAPR all-pass cascade + split-band DESS, full per-stage CHAIN UI - **Security advisories** — GHSA-wfx7-w6p8-4jr2 (WAN cert-pin Phase 2, aethersdr#3026) + GHSA-qxhr-cwrc-pvrm (CAT PTY symlink, aethersdr#3027) - **MQTT settings dialog refactor** (aethersdr#3051, @s53zo) — JSON-array topics + button-event lists, nested-JSON consolidation - **Audio reliability sweep** — WASAPI silent-open watchdog, mono-only USB PnP mic recovery, MQTT subscribe diff - **Native Hamlib NET rigctl** — eliminates external rigctld dependency - **Stream Deck integration** — Elgato SDK plugin (Win/Mac/Linux-via-OpenDeck) ## Contributors thanked @jensenpat (16), @aethersdr-agent (22 bot), @NF0T (12), @rfoust (9), @Ozy311 (5), @M7HNF-Ian (5), @chibondking (5), @M8WLO (4), @s53zo (2), @pepefrog1234 (2), @K5PTB (2), @chrisb1964 (1) ## Test plan - [ ] CI green (build + check-paths + check-windows) - [ ] CHANGELOG renders correctly on GitHub - [ ] README version banner shows 26.5.3 - [ ] After merge: tag v26.5.3 once CI green on main 73, Jeremy KK7GWY & Claude (AI dev partner) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ethersdr#3131) ## CAT Protocol Coverage - Full Kenwood TS-2000 command set, referencing the manual appendix, except commands not applicable to a FlexRadio - Full FlexCAT command set per SmartCAT User Guide v4.1.5, with the exception of ZZPA/ZZPE (panadapter data streaming — out of scope for a CAT server) - Two TCP ports by default: 4532 (rigctld) and 5001 (FlexCAT). Up to 6 more ports can be configured/enabled; each port has a companion PTY that accepts the same commands and responds identically. PTY paths are shown in the pop-out applet and can be copied to the clipboard with a right-click — handy when configuring external apps. ## Why CAT in addition to rigctld Some external apps do not support rigctld, but expect to send TS-2000 CAT commands, with extensions by FlexRadio. AetherSDR now speaks both protocols, giving operators the flexibility to use whichever their software requires — setups that currently use SmartCAT can work directly with AetherSDR without change. TCI is preferred, but this supports both legacy apps and legacy hams. ## CAT applet redesign Inspired by SmartSDR for macOS. The space-consuming configuration only appears when popped out, avoiding clutter of the right panel. ## Test suites - **`CAT_TS-2000_test`**: 15 sections, 103 checks (92 always-on + 11 PTT/CW gated) - **`CAT_Flex_test`**: 16 sections, 129 checks (105 always-on + 24 PTT/CW gated) PTT and CW keyer tests are **skipped by default** to avoid unintended transmissions. Enable with `--ptt` and `--cw` when a radio is connected and the operator is ready to transmit. A firmware TX race was noted during CW testing: the radio briefly reports TX=0 via CAT while the PA is still hot after CW completes. Safety watches are added to the test suite; documented as known hardware behaviour. ## Integration testing Tested against real hardware (FlexRadio 6500, firmware 4.2.18). Primary platform: macOS. Also tested on Raspberry Pi 5 / Raspberry Pi OS Trixie. | App | Connection | Result | |---|---|---| | fldigi | TCP + PTY | ✓ | | WSJT-X | TCP + PTY | ✓ | | MacLoggerDX | PTY (TS-2000 only) | ✓ | | N1MM+ | TCP | ✓ (CAT sync; CW keying not possible — see below) | ### N1MM CW keying — known limitation N1MM keys CW by toggling RTS/DTR on a real serial COM port. Virtual serial ports (PTYs) are not exposed as COM ports in Windows, so RTS/DTR keying cannot reach AetherSDR. No workaround exists without a WinKeyer-compatible keyer emulator. Documented here so operators don't waste time chasing it. ## Security Applied the PTY symlink fix (same pattern as GHSA-qxhr-cwrc-pvrm, aethersdr#3027) to `CatPort`: per-user symlink directories, atomic rename instead of unlink+symlink. ## Code quality **UAF audit:** `SmartCatProtocol` and the new `RigctlProtocol` additions are both clean — no `[this]` captures in queued lambdas; all dispatch is synchronous on the main thread. **ThemeManager:** `CatControlApplet` was included in the Phase 2 mass migration (aethersdr#3102). Because this branch replaces the entire applet, that migration did not carry forward. We applied `ThemeManager::applyStyleSheet()` throughout — both the docked panel and the pop-out — so all widgets auto-restyle on `themeChanged`, consistent with the rest of the codebase. One intentional hardcode remains: the `:checked` background `#006040` (CAT active state). There is no `color.background.success` token analogous to `color.background.tx`; adding one would complete the conversion and is worth proposing as a follow-up. Closes aethersdr#2962 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Fixes GHSA-qxhr-cwrc-pvrm — one half of the #2940 security audit.
Moves the rigctld convenience symlink out of the shared
/tmpdirectory into a per-user runtime/cache directory, and replaces the non-atomicunlink + symlinksequence with an atomicsymlink + renamepattern.Why
/tmp/AetherSDR-CAT-{A,B,C,D}. Second user getsEEXISTonsymlink(), their CAT software can't auto-discover the PTY./tmp: Rare hardening configs / sandboxed setups don't apply the sticky bit. Theunlink → symlinkgap allows an attacker to insert a symlink between the two calls. (Sticky bit + PTY mode 0620 mitigate on standard systems; this fix removes the dependency.)/tmp. Right home is$XDG_RUNTIME_DIR(Linux) or~/Library/Caches/(macOS).What changed
New static
RigctlPty::defaultSymlinkPath(int sliceIndex):$XDG_RUNTIME_DIR/aethersdr/cat-{A,B,C,D}$HOME/.cache/aethersdr/cat-{A,B,C,D}$HOME/Library/Caches/AetherSDR/cat-{A,B,C,D}Parent dir created via
mkpathand tightened to mode 0700 viachmodso a hostile group member can'treadlink()the PTY path even on shared systems with shared primary groups.RigctlPty::start(): replacesunlink + symlinkwithsymlink(slaveName, tmpPath)+rename(tmpPath, finalPath).rename()is atomic; the temp file is in the same per-user directory so the rename stays within one filesystem.UI surfaces updated:
MainWindowandCatControlAppletboth callRigctlPty::defaultSymlinkPath(i)instead of hardcoding/tmp/AetherSDR-CAT-${letter}. The displayed path always matches the actual symlink location.Migration impact
User-visible change: anyone with WSJT-X / fldigi / etc. configured to point at
/tmp/AetherSDR-CAT-Awill need to update their CAT software config to the new per-user path. The displayed path in the CAT applet shows the new location.Worth a release-notes mention. Defaulting to the canonical per-user location going forward — no automatic compatibility symlink at
/tmp(would re-introduce the hygiene issue).Stats
Test plan
$XDG_RUNTIME_DIR/aethersdr/or equivalent.tmpfile)Checklist
AppSettingscallsMeterSmoother(N/A)Closes #2940 (RigctlPty half — VAB half is in flight separately).
🤖 Generated with Claude Code