Skip to content

fix(gui): neutralise the release tag in both prompts. Principle VIII. - #6407

Merged
rfoust merged 2 commits into
aethersdr:mainfrom
on8st:fix/release-tag-escaped
Oct 12, 2026
Merged

rfoust merged 2 commits into
aethersdr:mainfrom
on8st:fix/release-tag-escaped

Conversation

@on8st

@on8st on8st commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #6405

Limits

  • Built and run on one Mac (macOS, Qt 6.12.0). Per-PR CI compiles the new
    test but does not run it; it runs on the push to main.
  • No crafted release was fetched. The tag is passed to the two text builders
    in a test; UpdateChecker and the title bar's network path were not run.
  • The dialogs were not looked at on a screen.
  • The test covers the text builders, not the two call sites: nothing fails if
    a call site stops using them.
  • The test builds a stock QMessageBox; the app passes a
    FramelessMessageBox. That class adds a title bar and margins and does not
    touch the label or its text format (read, not tested).
  • VersionNumber::parse is unchanged. It still accepts 99.0.0<b>x as
    99.0.0; the test pins that, so the prompts do not lean on it. Tightening it
    is a behaviour change (it also reads tags such as 0.4.7a) and is left for
    a separate decision.
  • src/core/VersionNumber.h gains #include <QStringList>. It calls
    QString::split and compiled only through a transitive include; the new
    test is the first file to include it without one.

What was wrong

At bbded449, a GitHub release tag_name reached two prompts as markup.

  1. src/gui/TitleBar.cpp:1442-1452, "Outdated Version" (shown before filing
    an issue): the tag, leading v removed, went through .arg(..., latest)
    into <p>…<b>v%2</b>…</p>.
  2. src/core/UpdateChecker.cpp:47-58 emits the tag on updateAvailable;
    src/gui/MainWindow.cpp:1424-1429 showed it with
    box.setText(QString("AetherSDR v%1 is available.").arg(ver)). The box
    never set a text format, so Qt::AutoText rendered a tag that looks like
    markup. This is Help → Check for Updates.

The newer-version test in front of both is not a filter:
VersionNumber::parse (src/core/VersionNumber.h:14-27) reads only the
leading digits of each part.

Change

Both texts are built in a new header, src/gui/ReleaseTagText.h, used by the
two call sites and by the test.

prompt fix why
Outdated Version latestTag.toHtmlEscaped() The body is rich text on purpose (<p>, <b>), so it cannot be plain. Escaping shows the tag as published; latestVer.toString() would show a normalised version that may not match the Releases page.
Update Available box.setTextFormat(Qt::PlainText) Neither the text nor the informative text carries markup, so plain text shows the tag verbatim. Escaping is not enough here: under Qt::AutoText an escaped tag without &lt; (for example 99.0.0&x) is not detected as rich text and would show &amp; literally. The test pins that case.

No change for a well-formed tag such as 26.10.3.

Other places the release feed reaches a widget, and what was done:

place format disposition
MainWindow.cpp, upToDate box AutoText Left. It shows the running application's own version, not the tag.
WhatsNewDialog.cpp, status label (name, tag_name) rich text Left. setStatusText already escapes.
WhatsNewDialog.cpp, release body and the "no notes for " fallback Markdown Left, as scoped in the triage: the browser renders release-authored Markdown by design, and the tag has the same author as the body.
FirmwareStager.cpp → RadioSetupDialog.cpp — Left, as scoped in the triage: a different feed (radio firmware), not the release tag.

UpdateChecker::updateAvailable has one consumer (MainWindow.cpp). There is
no title-bar update indicator, tooltip or status-bar label that shows the tag.

Tests

release_tag_text_test (new, registered in tests/tests.cmake; offscreen, no
network, no sockets), 17 checks, with the tag
99.0.0<a href="http://example.invalid">x</a>:

  • the newer-version test accepts 99.0.0<b>x and the tag above;
  • Outdated Version: no <a in the body, the tag is present escaped
    (&lt;a href=&quot;…), the body's own <p> and <b> are intact, and
    26.10.3 is shown unchanged;
  • Update Available: the box and the label that draws the text are
    Qt::PlainText, the tag is shown verbatim, 26.10.3 is shown unchanged.
    Control: Qt::mightBeRichText() is true for that text, so the format is
    what protects it.

Mutations, each on a scratch copy of the header, test rebuilt:

mutation result
drop .toHtmlEscaped() in outdatedVersionPromptHtml exit 1: [FAIL] outdated: the tag's markup does not reach the body, [FAIL] outdated: the tag is shown escaped
drop box.setTextFormat(Qt::PlainText) in setUpdateAvailableText exit 1: [FAIL] update: the box is plain text (textFormat=2), [FAIL] update: the label that draws the text is plain text (textFormat=2)

From this PR's review (45d3637d): the running version is escaped in the
Outdated Version body too, and one check pins it.

Full ctest at 45d3637d, -j8: 773 tests, 771 passed, 2 failed, 4 skipped
(nr_stereo_independence_{dfnr,bnr}_{24000,48000}_test).

Static checks: tools/check_*.py --strict pass (the two that need a build or
package directory were not run), check_a11y.py on the touched GUI files
reports no findings, gen_touchpoint_manifest.py --check and
tools/docs/gen_reference.py --check are up to date.

73, ON8ST

🤖 Generated with Claude Code

Two prompts showed the GitHub release tag_name as markup. The
newer-version test does not filter it: VersionNumber::parse reads only
the leading digits of each part, so `99.0.0<a href=...>x</a>` parses as
99.0.0 and counts as newer.

- "Outdated Version" (TitleBar, before filing an issue): the body is
  rich text by design (<p>, <b>), so the tag is HTML-escaped.
- "AetherSDR Update Available" (MainWindow, Help > Check for Updates):
  neither line carries markup, so the box is Qt::PlainText and shows
  the tag verbatim. Escaping is not enough there: under Qt::AutoText an
  escaped tag without "&lt;" is not detected as rich text and would
  show its entities literally.

Both texts are built in src/gui/ReleaseTagText.h, used by the two call
sites and by release_tag_text_test. No change for a well-formed tag.

VersionNumber.h gains the <QStringList> include it already needed; the
new test is the first file to include it without a transitive one.
VersionNumber::parse is unchanged.

Fixes aethersdr#6405

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@on8st
on8st requested a review from a team as a code owner October 12, 2026 07:43

@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.

Reviewed at 9044579. I built and ran the new test locally, reproduced both of the mutations the body claims, and probed four attacks the test does not cover. The fix holds up under all of them. Details below so the clean verdict is checkable rather than asserted.

1. Issue fit

Yes, and wider than #6405 asked — correctly so. #6405 scoped only the "Outdated Version" prompt; the triage comment on it (aethersdr-agent, 2026-10-12 06:45) named the second sink (UpdateChecker → MainWindow.cpp "Update Available", reached by the ordinary Help → Check for Updates) and proposed exactly the two mechanisms this PR implements: latestTag.toHtmlEscaped() for the rich-text body, and box.setTextFormat(Qt::PlainText) for the box that carries no intentional markup. The triage also explicitly rejected the issue's alternative (latestVer.toString()) because normalisation would show a version that does not match the Releases page — and this PR's table gives that same reason. Every requirement in the issue and its thread maps to a hunk, and the two dispositions the triage ruled out of scope (WhatsNewDialog markdown body, FirmwareStager) are left alone and disclosed in the body.

2. Scope

Everything in the diff is explained by #6405 plus its triage comment.

File What it changes Claimed? Verdict
src/gui/ReleaseTagText.h New header; the two prompt-text builders Yes In scope. Referenced by both call sites and the test — not a dead addition.
src/gui/TitleBar.cpp:1449 Sink 1 now calls outdatedVersionPromptHtml Yes (#6405 body) In scope. Text identical for a well-formed tag.
src/gui/MainWindow.cpp:1430 Sink 2 now calls setUpdateAvailableText Yes (triage comment) In scope. Named in the thread, and the PR title says "both prompts".
src/core/VersionNumber.h:4 #include <QStringList> Yes Required, not churn. I compiled the header standalone with that line deleted: error: implicit instantiation of undefined template 'QList<QString>' at s.split('.'). It only ever compiled transitively; the new test is the first TU to include it alone.
tests/release_tag_text_test.cpp New socket-free test Yes In scope.
tests/tests.cmake:7155-7164 Registers the target Yes In scope, and correctly not added to ci.yml — tools/check_ci_test_gate.py freezes that list and I confirmed it still reports ok: per-PR gate matches the frozen list (7 frozen / 762 registered).

No new public surface, no protocol verb, no settings key, no changed default, no CHANGELOG.md entry, no removed guard. No commit on the branch is unrelated to the issue. Reading the - lines: the only deletions are the two inlined format strings being replaced by calls to the helpers that produce the same text.

Socket-test disclosure: none. tests/release_tag_text_test.cpp contains no QTcpServer/QTcpSocket/QUdpSocket/QLocalServer/QWebSocketServer, no bind/listen/connectToHost, no peer process and no Fake* device class. The tag is passed in as a literal; the network paths (UpdateChecker, TitleBar::showFeatureRequestDialog) are never entered. Nothing is removed either.

3. Blockers

None.

4. Nits (all non-blocking)

  • src/gui/ReleaseTagText.h:20 — current is not escaped while latestTag is:
            .arg(current, latestTag.toHtmlEscaped());
    Harmless today: every caller passes QCoreApplication::applicationVersion(), set from the compile-time AETHERSDR_VERSION (src/main.cpp:119, :422). I confirmed the asymmetry is live — passing <b>pwn</b> as current renders as markup — so it is one .toHtmlEscaped() away from being defence-in-depth rather than a latent trap for the next caller:
            .arg(current.toHtmlEscaped(), latestTag.toHtmlEscaped());
  • tests/release_tag_text_test.cpp:71 — the test builds a stock QMessageBox; production passes a FramelessMessageBox (src/gui/MainWindow.cpp:1427). I read src/gui/FramelessMessageBox.{h,cpp} end to end: it only overlays a title bar and re-applies contents margins in showEvent, and never touches the label or textFormat, so the widget-class difference is benign here. Worth a line in the body's Limits list alongside the author's own "the test covers the text builders, not the two call sites".
  • The same Limits bullet is otherwise accurate and appreciated: nothing fails if a call site stops using the helpers. A source-text assertion is the in-tree idiom for pinning that (#6402 does it for MainWindow, which links into no test) — optional, and arguably not worth it for a two-line diff.

5. CodeGuard dispositions

All 31 findings refuted; none intersects a diff hunk. The hunks are MainWindow.cpp 115-118 / 1426-1433, TitleBar.cpp 8-11 / 1446-1452, VersionNumber.h 1-4 — every flagged line is outside them, consistent with the scanner having walked the whole file at main's tip.

  • CS-PII-IP — VersionNumber.h:10 is the comment // Strips non-numeric suffixes (e.g., "0.4.7a" → 0.4.7.0). A version string read as a dotted quad. Untouched text, shifted one line by the new include. MainWindow.cpp:6917 is the WSJT-X multicast default 224.0.0.1.
  • CS-VLN-CMDI-BACKTICK (16 hits) — lexical matches on Markdown backticks inside // comments, e.g. MainWindow.cpp:538 // Null when \id` is empty or not in `devices`.` No shell anywhere near them.
  • CS-VLN-CMDI-AMP — the C++ reference/logical-AND token, e.g. TitleBar.cpp:728 void TitleBar::setActiveRadio(const QString& id).
  • CS-INJ-role_override — MainWindow.cpp:11753 is the comment // CNN override: if carrierScore > 0.70, …; the word "override". CS-INJ-homoglyph at line 0 is em-dashes and arrows in comments.
  • CS-VLN-PATH-PROC — QProcess::startDetached with the argument-list overload (no shell) relaunching applicationFilePath() with arguments().mid(1). Untouched region.

security-audit (CodeGuard) reached the same dispositions independently and also confirmed the positive security property: the escaped tag lands in element content rather than an attribute, so <>&" coverage suffices, and no anchor can form. That last point matters because QMessageBox's main label has openExternalLinks enabled — I measured openExternalLinks=1 — which is what made a crafted tag able to put a clickable arbitrary URL in a dialog titled "Outdated Version" before this change.

6. What I tried to break

  • Built and ran the test (clang++ -std=c++20, Homebrew Qt 6.11.2, QT_QPA_PLATFORM=offscreen): 16/16 [ OK ], ALL PASS, exit 0. The author's run was Qt 6.12.0; the behaviour is the same at 6.11.2.
  • Attacked the test itself — reproduced both mutations on a scratch copy of the header. Dropping .toHtmlEscaped() → exit 1 with [FAIL] outdated: the tag's markup does not reach the body and [FAIL] outdated: the tag is shown escaped, the body printing the live <a href="http://example.invalid">. Dropping box.setTextFormat(Qt::PlainText) → exit 1 with [FAIL] update: the box is plain text (textFormat=2) and the same for the label. Four failures, matching the body's table line for line. This test would not pass against the unfixed code.
  • %-placeholder re-expansion — a tag of 9.9.9%1 could in principle be re-substituted into the format string. It is not: the two-argument QString::arg(a, b) substitutes in a single pass, and the output carries a literal %1. The multi-arg form is load-bearing here; .arg(current).arg(tag) would not be.
  • Attribute-context escaping — toHtmlEscaped() leaves ' alone, so I tried a tag of 9.9.9' onx='y. The tag sits in element content inside <b>, so the quote cannot close an attribute. No injection.
  • Production call ordering — the real site calls setIcon, then setUpdateAvailableText, then two addButtons, and FramelessMessageBox::showEvent warns that setText/addButton rebuild the grid layout. I replicated that exact sequence: the label stays Qt::PlainText after both addButton calls and after show(). The format survives the rebuild.
  • Informative label — I measured it as Qt::PlainText too (fmt=0) after setUpdateAvailableText, so the format propagates. Moot either way: setInformativeText carries only the local applicationVersion().
  • Siblings left behind — all four other callers of VersionNumber::parse checked. UpdateChecker.cpp:59 emits current (not the tag) on upToDate, so MainWindow.cpp:1445's AutoText box never sees network text; WhatsNewDialog.cpp:482 escapes its status label and :408 its error text; RadioSetupDialog's firmware version comes through a digits-and-dots regex. "View Latest Release" opens the constant UpdateChecker::kReleasesPageUrl, not a tag-derived URL. No unfixed sink remains for the release tag.
  • Repo static checks — check_test_registration.py --strict, gen_touchpoint_manifest.py --check and check_ci_test_gate.py --strict all pass on the head checkout. CI is green on all five checks at 9044579.
  • Could not check: the dialogs on a screen (headless), and the real network paths with a crafted release. Everything above is reasoned from the code plus a locally built test binary — not from observing the shipping app.

One thing outside this PR, surfaced by the audit so it is not lost: src/gui/WhatsNewDialog.cpp:277 hands QDesktopServices::openUrl a URL from the release-body markdown, and githubUrlForMaybeRelativeLink (:46-51) rewrites only relative URLs, so an absolute file:/smb: scheme would reach the OS handler on a user click. Pre-existing, maintainer-authored source, click-gated, and not part of this change — worth its own issue rather than a note here.

7. Recommendation

Approve with nits. The fix matches the triage's proposal on both sinks, the mechanism choice per prompt is justified in the code and pinned by a test that genuinely dies when either half is removed, the one extra line (<QStringList>) is provably required, and there is no behaviour change for a well-formed tag. The two nits are a one-word escape on current and a Limits-list line about the test's widget class; neither is worth a round trip on its own.

One thing for the maintainer rather than the author: #6405 carries maintainer-review, and this PR implements the agent triage's recommendation verbatim — including its rejection of the issue author's own latestVer.toString() alternative. Merging is implicitly ratifying that triage call. I agree with it (showing the tag as published is truthful; the normalised form could disagree with the Releases page the operator is about to open), but it is your call, not mine.

Thanks for picking up the second sink and for the mutation table — it made this reviewable in minutes rather than hours.


🤖 aethersdr-agent · cost: $13.6098 · model: claude-opus-5

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Cisco CodeGuard — static analysis of this PR (36 finding(s))

  • [LOW] CS-PII-IP — PII detected: ip_address in src/core/VersionNumber.h /tmp/aetherclaude/pr-6407/src/core/VersionNumber.h:10
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:9701
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:9813
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:9935
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:9936
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:9937
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:6225
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:6304
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:540
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:3610
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:538
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:1231
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:1754
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:1846
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:1923
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:1965
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:2000
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:2295
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:3552
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:3643
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:6167
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:8037
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:8111
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:8196
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:8377
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:8580
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:9802
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:6917
  • [CRITICAL] CS-INJ-role_override — Prompt injection: role override attempt in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:11753
  • [CRITICAL] CS-INJ-role_override — Prompt injection: role override attempt (detected after NFKC normalization) in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:11753
  • [HIGH] CS-INJ-homoglyph — Mixed Unicode scripts detected (possible homoglyph attack) in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6407/src/gui/MainWindow.cpp:0
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/TitleBar.cpp /tmp/aetherclaude/pr-6407/src/gui/TitleBar.cpp:728
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/TitleBar.cpp /tmp/aetherclaude/pr-6407/src/gui/TitleBar.cpp:217
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/TitleBar.cpp /tmp/aetherclaude/pr-6407/src/gui/TitleBar.cpp:650
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/TitleBar.cpp /tmp/aetherclaude/pr-6407/src/gui/TitleBar.cpp:863
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/TitleBar.cpp /tmp/aetherclaude/pr-6407/src/gui/TitleBar.cpp:1256

Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them.


🤖 aethersdr-agent · cost: $13.7772 · model: claude-opus-5

… VIII.

Review of aethersdr#6407: outdatedVersionPromptHtml() escaped the tag and not
`current`. Every caller passes the compile-time application version, so
nothing rendered wrongly; escaping both leaves no trap for a later caller.
One check pins it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@on8st

on8st commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review, and for building and breaking the test yourself. Fixed in 45d3637d, a new commit on top (no rebase).

  • current is escaped too in outdatedVersionPromptHtml(). As you say, nothing rendered wrongly today; this removes the trap for a later caller. One check pins it; 17 in all.
  • The Limits list now says the test builds a stock QMessageBox while the app passes a FramelessMessageBox, and that the difference is read, not tested.

Not added: a source-text pin on the two call sites. I agree it is the in-tree idiom, and that it is arguably not worth it for a two-line diff; say if you want it.

For the maintainer, as you put it: this takes the triage's choice of escaping the tag over showing latestVer.toString(). I agree with the reason given, that the normalised form could differ from the tag on the Releases page.

The WhatsNewDialog.cpp link-scheme point is separate; I am not folding it in here.

Full ctest at 45d3637d on macOS: 771 of 773. The two that fail are known on this Mac: #6008, and vkamp_connection_test with one of its two known checks (#5254).

73, ON8ST

@rfoust rfoust self-assigned this Oct 12, 2026

@rfoust rfoust left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

#6407 fixes #6405 at the reported Outdated Version prompt and at the second release-tag sink in Update Available. The rich-text prompt escapes the tag; the plain-text prompt forces Qt::PlainText. Both preserve the published tag for display. I found no blockers.

Changed files Scope Verdict
ReleaseTagText.h, TitleBar.cpp, MainWindow.cpp Two release prompts In scope
VersionNumber.h Direct include needed by the new test In scope
release_tag_text_test.cpp, tests.cmake Socket-free regression coverage In scope

Verification: reviewed head 45d3637d79009cf82b37f3d9517cb4943942885b merged cleanly with current main 20122a8bc7e801532c89e12633654c14e25eb88b. Built the merged result on macOS ARM64; release_tag_text_test passed. Confirmed the build system and executable are ARM64 and RNNoise has no x86 sources. All five PR checks are green. In scratch, independently removing the tag escape, the plain-text setting, and the current-version escape made the regression test fail; restoring them made it pass. I also checked the sibling release-tag uses and the normal release display. An isolated TX-disabled demo run reached the ordinary update prompt; a crafted network release was not injected into the full app. The test exercises the text builders with a stock QMessageBox, while production uses FramelessMessageBox; source review found no text-format override there.

The scope and the test boundary are stated accurately in the PR. Approve.

@rfoust
rfoust merged commit cc06ebe into aethersdr:main Oct 12, 2026
5 checks passed
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.

The Outdated Version prompt shows the release tag as unescaped HTML

2 participants