Skip to content

test(hl2): pin the ADC pairing's age gate in the snapshot. Principle VIII. - #6441

Merged
rfoust merged 2 commits into
aethersdr:mainfrom
on8st:hl2/adc-age-gate-test
Oct 13, 2026
Merged

rfoust merged 2 commits into
aethersdr:mainfrom
on8st:hl2/adc-age-gate-test

Conversation

@on8st

@on8st on8st commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #6186

The seam and the test the triage on #6186 describes. One production change: Hl2Backend::healthSnapshot() takes its instant as an argument internally.

Limits

  • The link state is a second clock the test does not set. The overload flag is only present while the link reads Streaming, and that is decided by a QElapsedTimer. Every read asserts that its own snapshot carries the flag, so a link that stopped reading Streaming fails the test and cannot pass as "stale".
  • One direction is not exercised: the injected instant says current while the real clock says stale. That would need a sleep.
  • The chain is fed on the test thread, not on the I/O thread: a real Hl2RxDsp is placed in receiver 0 and given IQ blocks by hand.
  • The paired sentence is retyped in the test. It is built inline in healthSnapshot() and no helper exposes it, so a wording change there fails this test.
  • Receiver 0 only; adcPairing1 and up are not read.
  • Built and run on macOS, Qt 6.12.0. Linux and Windows not built here.

Change

  • Hl2Backend: a private healthSnapshot(std::int64_t nowNs); the public healthSnapshot() calls it with hl2::steadyNowNs(). The slice peak's age is hl2::slicePeakAgeMs(adcPeakObservedAtNs(), nowNs), and that one value feeds both the adcSliceObservedAgoMs row and hl2::slicePeakIsCurrent().
  • Hl2RxDsp::adcPeakObservedAgoMs() is removed: it has no caller left in src/ or tests/, and it was the clock read behind the snapshot's ADC rows.
  • friend struct Hl2AdcAgeTestAccess, beside the existing test-access friends.
  • One comment line in hl2_adc_pairing_test.cpp that said Hl2RxDsp forms the age.

Test

New hl2_adc_age_gate_test, socket-free. A real Hl2RxDsp stamps a peak from a tone 20 dB below full scale; link-up and telemetry with the overload flag clear arrive through MetisClient's signals. The snapshot is read at stamp + kSliceStaleMs ms + 999999 ns and one nanosecond later, with the constant taken from the header.

  • At the first instant the age row reads kSliceStaleMs and the pairing row carries the "agree" sentence.
  • One nanosecond later the age row reads kSliceStaleMs + 1 and the pairing row is not reported.
  • Read again at the first instant: paired again.
  • On every read: the flag row is present, the peak row is present, SliceSamplingGate::applied() is true for the stamp, and the stamp has not moved. So the two reads differ in the age alone.
  • Controls: no stamp before any block; with no link there is no flag and no pairing.

Mutation check. Each mutant is a scratch copy of Hl2Backend.cpp, compiled with the tree's own compile command and linked ahead of the library; none is committed.

mutant failing checks
unmodified copy, same pipeline 0
freshness argument replaced by true (the mutant from the issue) "a peak kSliceStaleMs + 1 old is not paired"
freshness argument replaced by false "a peak exactly kSliceStaleMs old is paired", "and the same peak pairs again"
gate from a second clock read "a peak kSliceStaleMs + 1 old is not paired"
age from the wall clock, as before this PR 4 checks: the age row at all three reads, and the stale pairing

Not run as a mutant: <= changed to < in slicePeakIsCurrent() itself; hl2_adc_pairing_test owns that edge.

Full ctest at 9737ff2f (-j8): 781 of 781 reported passed (ctest counts its skipped tests as passed), no rerun. check_test_registration.py --strict and gen_touchpoint_manifest.py --check pass.

73, ON8ST

🤖 Generated with Claude Code

…VIII.

healthSnapshot() gains a private overload taking the instant; the ADC rows
take their age from it and read no clock, so the age row and the pairing
gate come from one value. Hl2RxDsp::adcPeakObservedAgoMs() had no other
caller and is removed. hl2_adc_age_gate_test reads the snapshot one
nanosecond either side of the kSliceStaleMs boundary (aethersdr#6186).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@on8st
on8st requested a review from a team as a code owner October 13, 2026 20:23
@rfoust rfoust self-assigned this Oct 13, 2026

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

1. Issue fit

Yes. This implements the seam the triage on #6186 prescribed, point for point, and it closes the gap that issue names: nothing under tests/ read the adcPairing%1 or adcSliceObservedAgoMs%1 rows, so the freshness argument at the call site was product-only code.

Mapping the triage's requirements to the diff:

Triage requirement Where
Clock on the snapshot, not an Hl2RxDsp fake clock Hl2Backend.h:304, Hl2Backend.cpp:6023-6028
Age from slicePeakAgeMs(adcPeakObservedAtNs(), nowNs), one value feeding both row and gate Hl2Backend.cpp:6108-6110, consumed at :6123
adcPeakObservedAgoMs() deleted Hl2RxDsp.h (−9); grep -rn adcPeakObservedAgoMs . over the whole head returns nothing, docs included
friend struct Hl2AdcAgeTestAccess beside the existing *TestAccess friends Hl2Backend.h:300
Hold SliceSamplingGate open while varying only the age tests/hl2_adc_age_gate_test.cpp:139 asserts samplingGateOpen() on every read
Reads at stamp + 150 ms + 999 999 ns and stamp + 151 ms :198-200, with firstStale - lastCurrent == 1 asserted
Land after #6164 so the helpers exist once head is one commit on 47aa4098

The fifth row is the one worth calling out. The triage warned that a test asserting only "stale → Unknown" survives the true mutant if the sampling gate happens to be shut, because both gates return Unknown. This test asserts the gate open, the stamp unmoved, the flag row present and the peak row present on every read, so the two instants really do differ in the age alone. That is the difference between pinning the gate and pinning a coincidence.

The mutation table in the body is consistent with what I read: the age row and the pairing row are separate put() calls fed from one ago, so the true mutant can only be caught by the stale pairing check — thin, but correct, and the false mutant is caught from the other side.

2. Scope

Everything in the diff is explained by the issue and its triage.

File What it changes Claimed? Verdict
src/core/backends/hl2/Hl2Backend.cpp public healthSnapshot() forwards to a new healthSnapshot(nowNs); age via slicePeakAgeMs yes In scope
src/core/backends/hl2/Hl2Backend.h private overload + one friend + comments yes In scope
src/core/backends/hl2/Hl2RxDsp.h deletes adcPeakObservedAgoMs(), rewords the stamp comment yes In scope — see the governance note under Nits
tests/hl2_adc_age_gate_test.cpp the new test (+234) yes In scope
tests/hl2_adc_pairing_test.cpp one comment, Hl2RxDsp → Hl2Backend forms the age yes In scope, and now true
tests/tests.cmake registers the target, joins AETHER_SETTINGS_CONSUMERS yes In scope

No new public surface: the overload is private, absent from IRadioBackend, and its only production caller is the public no-arg version. No row, key or label is added or renamed, so AutomationServer::doHealth()'s output is unchanged and no consumer can pick an instant. No settings key, no capability field, no protocol verb. No CHANGELOG.md entry — correct.

Socket tests: none added or modified. MetisClient's constructor creates one QTimer and no socket, and m_telemetryService is never constructed by Hl2Backend's constructor, so the "socket-free" claim holds for real and not just by intent.

Security audit (required by the harness; run by the security-audit subagent against the head checkout): all 64 CodeGuard findings refuted, no genuine finding in the additions. Credited as security-audit (CodeGuard).

  • The ~60 CS-VLN-CMDI-BACKTICK hits on the C++ files are the scanner matching Markdown-style backticks inside // comments. A grep for a backtick on any line not starting //, * or /* returns zero matches across all four C++ files, and there is no system/popen/QProcess/exec* anywhere in the audited tree. The flagged lines are comment prose that exists verbatim on main; the ~5-line shift is the new forwarder.
  • .github/workflows/static-checks.yml does not intersect this diff at all — git diff --name-only 47aa4098d..9737ff2f5 lists six files and that is not one of them. All 14 of its findings are whole-repo scan noise on untouched code, the codeguard-file-list-can-be-main-tip shape. (Its line 496 FILES=$(cat …) word-split weakness is real but pre-existing, and already replaced by the bash-array form on main in #6439.)
  • CS-VLN-CMDI-ENVVAR at Hl2Backend.cpp:321,330 is qEnvironmentVariableIsSet() — a presence test that reads no value, pre-existing.
  • Independently of CodeGuard: slicePeakAgeMs()'s (nowNs - peakAtNs) / 1'000'000 cannot wrap in practice (both operands are ns counts from one steady_clock), and the negative clamp runs before the static_cast<qulonglong> at :6113, so no negative-to-unsigned wrap is reachable.

3. Blockers

None.

4. Nits (all non-blocking)

  • tests/hl2_adc_age_gate_test.cpp:138 — the second clock is a 2.5 s budget, not just "a clock the test does not set." The flag row only appears while telemetryLinkState() == Streaming, and that is m_rxAdvanceClock.elapsed() < kStreamStallDeclareMs (Hl2TelemetryCadence.h:64, 2500 ms), restarted by the linkUp handler at :189. The three snapshot reads after it are cheap, and the WDSP feed happens before linkUp, so the real margin is large. But a >2.5 s stall between linkUp and the last read — a sanitizer lane under load is the plausible one — fails on "the snapshot carries the overload flag, clear: the link reads Streaming", which reads as a link bug rather than as a slow machine. Your Limits section is right that it fails rather than passing as "stale"; a word in that check's text naming the 2.5 s window would save the next person the hunt.
  • src/core/backends/hl2/Hl2Backend.cpp:6222 — healthSnapshot(nowNs) is not clock-free as a whole. One later row still reads the wall clock:
            m_adcWindowClock.isValid() ? QVariant(qint64(m_adcWindowClock.elapsed()))
    Your header comment is accurate as written — it scopes the claim to "the converter rows" — but the triage asked for a function that "reads the clock zero times", and a future reader injecting nowNs to pin some other row will find this one still on QElapsedTimer. Worth one clause in the Hl2Backend.h:301-303 comment saying the converter rows alone are on nowNs.
  • src/core/backends/hl2/Hl2RxDsp.h:20 — the Hl2AdcPairing.h include is now referenced only from a comment. With adcPeakObservedAgoMs() gone, no declaration in the header uses a symbol from it (:366 is prose). Hl2RxDsp.cpp:3, Hl2Backend.cpp:16 and Hl2Backend.h:15 all include it directly, so dropping it would not break those three — I did not walk every other includer of Hl2RxDsp.h, so check before you pull it. Pure tidiness either way.
  • Governance, for the maintainer rather than the author. #6186 still carries maintainer-review, and its triage closed by asking for two rulings that have not been given: which test file this belongs in, and whether deleting adcPeakObservedAgoMs() is in scope or a separate cleanup. The PR answers both in its body (new file; deletion in scope, zero callers left) and the triage did say a new file was fair and that the accessor "can go", so this reads as reasonable rather than as jumping a gate. Flagging it only so the ruling lands on the record.

The retyped "agree — …" sentence is disclosed in your Limits and I checked it against Hl2Backend.cpp:6138-6141 character for character, .arg(headroom) included. I would not ask for a helper: the expected string is built from the measured peak, not a hardcoded dB figure, which is what keeps the test off platform-specific WDSP gain.

5. What I tried to break

Findings here are reasoned from source in the head checkout at /tmp/aetherclaude/pr-6441; I have no build and ran nothing but the Python static checks.

  • Deadlock in the test harness. Qt::BlockingQueuedConnection on m_metis (:168, :176) is a self-deadlock if the target shares the caller's thread. It does not: the constructor moves m_metis to m_ioThread and startStreamThread() calls thread->start() (AppActivity.h:25), so the event loop is live. The backend itself stays on the test thread, which is why sendPostedEvents(&backend, QEvent::MetaCall) is the right follow-up for the queued linkUp/telemetryUpdated handlers.
  • Empty receiver vector. attachChain() returns nullptr if m_rx is empty, which would fail the test immediately. It is not: m_rx.assign(1, Receiver{}) runs in the constructor, and m_rx[0].dsp is nullptr there, so the injection overwrites nothing and leaks nothing.
  • Lifetime of the injected chain. The destructor delete r.dsp outright after both threads are joined (:1749-1751), and m_ioDsps is empty because the test never connects, so there is no second owner and no cross-thread alias. The test's Hl2RxDsp stays on the test thread — processIqBlock and healthSnapshot both run there, and m_adcPeakAtNs is an atomic read either way.
  • A rebuild stealing the chain at the link edge. This was my best shot at a use-after-free: if linkUp queued a receiver rebuild, the injected dsp could be deleted mid-test. pushInitialState(), replayRestoredSession() and emitAllSliceState() contain no rebuild, beginDspSetup, deleteLater or buildReceivers call — chains are built in connectRadio(), which this test never calls. Your "the link edge kept receiver 0's chain" assertion at :192 guards it anyway.
  • The two instants. stamp + 150*1e6 + 999999 truncates to 150 → current (<= in slicePeakIsCurrent); stamp + 151*1e6 → 151 → stale. The arithmetic checks out and the one-nanosecond separation is asserted rather than assumed.
  • The 16-block feed cap. kMaxBlocksForOneSample = 16 at :113 looked like a magic number until I found the same constant and value in hl2_adc_sampling_seam_test.cpp:76, documented there as "~15 blocks at this rate and block size" — the same 48 kHz / 1024 configuration this test uses. Justified, and the loop early-exits once stamped.
  • A silently hot tone. kToneAmplitude = 0.1f relies on WDSP's processing gain not pushing the slice inside kSliceHotHeadroomDb, which would change the verdict to SliceOnly and the sentence with it. The test asserts sliceHeadroomDb(*peak) > kSliceHotHeadroomDb explicitly at :184, so that fails loudly instead of quietly changing what is being tested.
  • Does the test prove anything against unfixed code? Not in the usual sense — it cannot compile on main (no overload, no friend), so "fails without the fix" is vacuous for a PR whose subject is the seam. The question that matters is whether it kills a broken gate, and it does in both directions: the pairing row is asserted present at exactly kSliceStaleMs and absent one nanosecond later, so flipping the freshness argument either way goes red.
  • CI, both halves. build, check-macos and check-windows are green on 9737ff2f, so the new target compiles on all three platforms — your Limits section undersells this. But ci.yml's ctest calls are -R-filtered and name no hl2_adc test, so the first Linux and Windows execution is the post-merge full suite. check_wdsp_test_links.py --strict and check_test_registration.py --strict both pass locally on the head, and the target's linkage (aethercore Qt6::Core Qt6::Network) matches its Hl2RxDsp-instantiating siblings.
  • The overload itself. A private healthSnapshot(std::int64_t) alongside a public virtual healthSnapshot() does not hide the override — both are declared in the same class, so the zero-argument call resolves to the public one and access is checked after resolution. No ambiguity for existing callers.

6. Recommendation

Approve with nits. The production change is a clock parameter, one friend and a dead accessor deletion; the test is the careful one the triage asked for, and it guards the specific trap (SliceSamplingGate held open) that would otherwise have let the mutant live. The three nits are comment-and-wording work, and the only thing I would not merge without is the maintainer ruling #6186 is still labelled for — which is a signature, not a change. Thanks for the Limits section: every limit I set out to find was already in it, which is rarer than it should be.


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

@aethersdr-agent

Copy link
Copy Markdown
Contributor

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

  • [CRITICAL] CS-VLN-CMDI-SUBSHELL — Vulnerability: CS-VLN-CMDI-SUBSHELL in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:496
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:14
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:36
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:81
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:172
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:309
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:332
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:340
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:355
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:399
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:414
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:469
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:531
  • [MEDIUM] CS-VLN-CMDI-ENVVAR — Vulnerability: CS-VLN-CMDI-ENVVAR in .github/workflows/static-checks.yml /tmp/aetherclaude/pr-6441/.github/workflows/static-checks.yml:534
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:178
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:257
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:593
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:699
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:1949
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:2039
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:2295
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:2455
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:2567
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:3159
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:4082
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:4181
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:4499
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:4921
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:5488
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:5537
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:5916
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:6035
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:6082
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:6114
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:6288
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:6335
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:6526
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:6598
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:6639
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/hl2/Hl2Backend.cpp /tmp/aetherclaude/pr-6441/src/core/backends/hl2/Hl2Backend.cpp:6864

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: $20.8728 · model: claude-opus-5

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

Addresses #6186: the socket-free test reaches healthSnapshot's ADC freshness gate while holding the stamp, sampling gate, peak, and overload flag steady. No blockers remain. The RAII setup nit is fixed in 0455180.

Scope audit:

Files Change Verdict
Hl2Backend.cpp/.h Private snapshot clock seam and test access In scope
Hl2RxDsp.h Remove unused age accessor In scope
hl2_adc_age_gate_test.cpp Boundary coverage and RAII setup In scope
hl2_adc_pairing_test.cpp, tests.cmake Comment, registration, settings isolation In scope

All changes are disclosed and explained by the issue. No public protocol surface, defaults, or UI behavior changed.

Verification: reviewed merge 082ff3d against main 47aa409 and original head 9737ff2. Built focused targets with the prescribed ARM64 toolchain, RADE enabled; both hl2_adc_pairing_test and hl2_adc_age_gate_test pass. Rebuilt and reran both after the RAII fix; both pass, with ARM64 executable verified. Registration and diff checks pass. The four engine-boundary checks have identical passing diagnostics on base and merge.

Adversarial checks: always-current fails the stale assertion; always-stale fails both fresh assertions; restoring the old wall-clock calculation fails the injected-age and stale-pairing assertions. Restoring production code passes again and matches the reviewed source hash. Examined no-stamp/no-link controls, fixed-stamp fresh/stale/fresh reads, unused accessor callers, ownership, and teardown.

Limits: no full suite rerun or GUI/live-radio session; demo mode does not reach this HL2-specific path. Verification was socket-free backend coverage with no TX. No invocable automated review pass was available; a second manual lifecycle/failure-mode pass was completed. New-head CI is queued and must pass before merge.

@rfoust
rfoust enabled auto-merge (squash) October 13, 2026 21:53
@aethersdr-agent

Copy link
Copy Markdown
Contributor

Antares Detector — candidate vulnerable file(s)

  • src/core/backends/hl2/Hl2AdcPairing.h
  • src/core/backends/hl2/Hl2Backend.cpp

The pairing age logic uses (nowNs - peakAtNs) / 1000000 to compute age in ms, which can overflow std::int64_t and produce a negative age. The slicePeakAgeMs function returns ago < 0 ? 0 : ago, so a negative computed age is treated as 0 and deemed 'current', incorrectly pairing a dead peak with a live overload flag. This is a memory-safety/logic flaw in the ADC pairing age validation.

Localized by Cisco Foundation AI Antares-1B running locally in the AetherClaude sandbox, seeded by the Cartographer security map. Advisory only — please verify before acting.


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

@rfoust
rfoust merged commit 7d82707 into aethersdr:main Oct 13, 2026
5 checks passed
@rfoust rfoust removed their assignment Oct 13, 2026
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.

HL2: no test covers the ADC age gate's call site in healthSnapshot()

2 participants