Repository navigation
log: session diagnostics with a redaction guard (closes #438) - #442
Merged
Merged
Conversation
`grep -rE 'qCDebug|qCInfo|qCWarning' app/src/` returned zero hits, and the redirection client had a logging category it never wrote to. #433 arrived as a screenshot of a generic Qt socket string with no way to tell which protocol phase had failed; this is the gap between that and the promise README.md already makes to reporters. Adds four qumesh.app.* categories and fills in the redirection transcript: every state transition, the 0x11 StartSessionReply status (BUSY/UNSUPPORTED previously reached only lastError), the 0x14 auth caps and challenge, the 0x40 -> 0x41 KVM round-trip, TLS trust outcomes, and a phase-tagged line at the single fail() choke point. Warnings and once-per-session facts stay on; the phase-by-phase transcript sits behind QT_LOGGING_RULES='qumesh.*=true'. Two things the issue text didn't know: Q_LOGGING_CATEGORY's default severity is QtDebugMsg, so the two-arg form the existing code uses has qCDebug ON by default — Qt only hard-wires debug off for categories named "qt"/"qt.*". Adding debug lines under those declarations would have shipped a per-transition firehose to every user's stderr with nothing set. All categories move to the three-arg QtInfoMsg form. That's required, not cosmetic. And there was already a leak, on by default: kvm_codec.cpp hex-dumped 32 bytes of the compressed RLE block at qCWarning on an inflate failure. That block is framebuffer content — the remote screen — with no env var needed, repeating per failed block. Now logs the 2-byte zlib header only. redir/ and wsman/ audited and clean. Redaction is the load-bearing part, since users paste these into public issues: presence-only for the digest realm/nonce (with the cnonce and response never logged at all, since together they'd allow an offline attack on the AMT password), "(code redacted)" for the consent PIN, keys-not-values for setKvmSettings (rfbPassword lives there), and nothing whatsoever from the SOL byte stream, which carries whatever is typed at a getty prompt. test_log_redaction.cpp captures everything with all categories forced on and asserts known secrets never appear — QTest::ignoreMessage can't express "no line contains this", so it uses a message handler. Each positive assertion is paired with a negative one so a capture that caught nothing can't pass, and the PIN test is verified to fail when the redaction is removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The test failed on Windows only, and took 16s there against 4s locally: its assertions waited for a connection to a closed local port to fail, and Windows doesn't refuse as promptly as Linux/macOS, so the waits ran to their full timeouts and the expected lines were never observed. Nothing it checks actually needs the connection to fail. The open-time lines are emitted synchronously inside open(), before any socket activity, and the severity test now uses the empty-host path, which emits both a warning and a state transition without opening a socket at all. Runs in 0.87s and says nothing about platform connect behaviour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The consent status line carried the flattened bool because `m_optInRequiredRaw` didn't exist when this branch was cut. It does now, and "which protocols does this machine gate" is the question a consent report actually needs answering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
benmcollins
force-pushed
the
438-logging
branch
from
August 22, 2026 00:48
750cd19 to
7ba1c68
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plan
Four new
qumesh.app.*categories plus the redirection transcript thatredir_client.cppalready had a category for but never wrote. A three-level ladder keeps it proportionate: warning for anomalies, info for ~6–10 once-per-session facts (both on by default), debug for the phase-by-phase transcript, off unlessQT_LOGGING_RULES='qumesh.*=true'is set.Two things the issue text didn't know
1.
Q_LOGGING_CATEGORY's default severity isQtDebugMsg. The two-arg form the existing code uses therefore hasqCDebugenabled by default — Qt only hard-wires debug off for categories literally namedqt/qt.*. Adding debug lines under those declarations would have shipped a per-transition firehose to every user's stderr with nothing set. Every category moves to the three-argQtInfoMsgform; that's a required part of this change, not a style preference. (Not usingQ_STATIC_LOGGING_CATEGORY— it's Qt 6.9+, and CLAUDE.md pins the floor at 6.6.)2. There is already a leak, and it's on by default.
kvm/src/kvm_codec.cpphex-dumps 32 bytes of the compressed RLE block atqCWarningon an inflate failure. That block is framebuffer content — the remote screen — printed with no env var, repeating per failed block. Now logs the 2-byte zlib header only.redir/andwsman/audited and clean.The issue is titled "no logging in
app/src/", but the fix can't stay there: four of the six named gaps live inredir/src/redir_client.cpp, and the leak is inkvm/.Redaction-critical sites
RedirectionClientdigest challengeMachineDetailsController::sendOptInCode(code redacted). The issue text didn't flag this one.setKvmSettingsfields.keys()only —fields["rfbPassword"]is the RFB password being written to firmware.SolController's data handler0x11oemData / desyncm_inboxWhat's logged
redir_client.cpp— everysetStatetransition, the connection-open line,0x11status (with a warning naming BUSY/UNSUPPORTED, which previously reached onlylastError), the0x14auth caps and challenge, the0x40→0x41KVM round-trip, TLS peer cert and which trust branch was taken, the unexpected-frame desync, and a phase-tagged line at the singlefail()choke point.kvm_session.cpp—setStatetransitions and a phase-taggedfail().app/src/—qumesh.app.kvm,.sol,.ider,.optin: controller state transitions (not redundant with the client's — this is what QML binds to, andAwaitingTrusthas no client-side counterpart), open lines carrying the parameters that decide the session, terminal outcomes, IDE-R'sSessionInfo, and every OptIn transition guarded on change so the 500 ms poll doesn't become a firehose.Tests —
app/tests/test_log_redaction.cpp(new)QTest::ignoreMessagecan't express this: it matches and consumes one exact message, whereas the assertion is "no line anywhere contains this substring". So a customqInstallMessageHandlercaptures everything with all categories forced on — exactly what a reporter following the README would send.redirectionLoggingNeverCarriesThePassword— drives KVM and SOL through open→fail; asserts the password is absent and the username present, so a capture that silently caught nothing can't pass.consentPinIsNeverLogged— verified to fail when the PIN is deliberately interpolated.kvmSettingsLogsKeysNotValues—rfbPasswordappears as a key, never as a value.categoriesAreQuietByDefault— behavioural, not by constructing aQLoggingCategory(that would only measure the constructor's default): runs the same failure with and without the env var, and asserts warnings survive the default filter while the transcript doesn't, but does appear once it's set.Full suite green (42/42).