Skip to content

fix(cat): move RigctlPty symlink out of /tmp (GHSA-qxhr-cwrc-pvrm)#3027

Merged
ten9876 merged 2 commits into
mainfrom
auto/fix-2940-rigctl
May 24, 2026
Merged

fix(cat): move RigctlPty symlink out of /tmp (GHSA-qxhr-cwrc-pvrm)#3027
ten9876 merged 2 commits into
mainfrom
auto/fix-2940-rigctl

Conversation

@ten9876

@ten9876 ten9876 commented May 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes GHSA-qxhr-cwrc-pvrm — one half of the #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

  • Clean build, 0 errors
  • 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

  • No new flat-key AppSettings calls
  • All meter UI uses MeterSmoother (N/A)
  • Documentation will be updated in release notes
  • Security-sensitive change references GHSA-qxhr-cwrc-pvrm

Closes #2940 (RigctlPty half — VAB half is in flight separately).

🤖 Generated with Claude Code

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>
@ten9876 ten9876 requested a review from jensenpat as a code owner May 24, 2026 02:14
@ten9876 ten9876 enabled auto-merge (squash) May 24, 2026 02:14

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:52 still has the stale comment // "/tmp/AetherSDR-CAT-A" next to the ptyPath QLabel member. Easy to update while you're there.
  • ::chmod(parentDir, 0700) failure is silently ignored. Defensible as best-effort hardening, but on the ~/.cache/aethersdr fallback with a permissive umask, the dir could end up 0755 without anyone noticing. A qCWarning on chmod != 0 would 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 .tmp file is closed by the parent dir being mode 0700 (only same-uid can interpose, who has stronger powers anyway).
  • <sys/stat.h> include and ::chmod call are both inside the existing #ifndef _WIN32 blocks.
  • defaultSymlinkPath() lives outside the _WIN32 guard but only uses QStandardPaths/QDir/QString — fine to compile on Windows, and start() 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 from MainWindow.
  • Case-insensitive aethersdr detection correctly avoids double-naming on macOS (~/Library/Caches/AetherSDR already 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>
@ten9876 ten9876 requested a review from AetherClaude as a code owner May 24, 2026 02:52
@ten9876

ten9876 commented May 24, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed all three items from your review in ebf71aba:

  1. Help docresources/help/understanding-data-modes.md updated at all 8 sites (lines you flagged: 148-151, 158, 292-293, 433, 482, 552-555). Switched to <runtime>/aethersdr/cat-X placeholder, with the per-platform resolution key explained the first time the path appears. Each setup walkthrough (Winlink Express, fldigi) now says "copy the channel-A path from the CAT Control tile" with a concrete example. The CAT applet UI is the source of truth; the doc points at it.
  2. Stale commentCatControlApplet.h:52 ptyPath now reads // RigctlPty::defaultSymlinkPath(i).
  3. chmod warn — added qCWarning on chmod != 0 with errno, plus explicit <cerrno> include. The comment notes this matters most on the CacheLocation fallback path where umask could leave the dir at 0755.

Local build verified clean. Ready for a re-look.

@ten9876 ten9876 merged commit a41847f into main May 24, 2026
5 checks passed
@ten9876 ten9876 deleted the auto/fix-2940-rigctl branch May 24, 2026 04:01
ten9876 added a commit that referenced this pull request May 24, 2026
## 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>
K5PTB added a commit to K5PTB/AetherSDR that referenced this pull request May 25, 2026
…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>
K5PTB added a commit to K5PTB/AetherSDR that referenced this pull request May 25, 2026
…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>
K5PTB added a commit to K5PTB/AetherSDR that referenced this pull request May 25, 2026
…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>
ten9876 pushed a commit that referenced this pull request May 25, 2026
…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>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…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>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
## 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>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security audit: VirtualAudioBridge (macOS shmem) + RigctlPty (PTY allocation, Linux+macOS)

1 participant