Repository navigation
fix(#369): scrub logentry.params, and pin the three Sentry fields that were scrubbed but untested - #371
Conversation
…e params leak Against unmodified main: 26 of 29 pass, 3 fail. The three failures are all logentry.params, which ScrubAccountNames passes through verbatim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… two text forms SentryMessage.Params serialises as logentry.params and was the one field the #367 rebuild passed through verbatim. Strings only: Params holds arbitrary objects, and stringifying the rest to run them past the redactor would change the serialised payload for every event that has one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Requesting a review on head |
|
/agentic_review |
PR Summary by QodoScrub account names from Sentry log message parameters
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the day💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR |
|
Shepherd re-review on the same head Three things worth a reviewer's attention, all in
|
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit ea38b2c |
|
Qodo-clean, CI green — ready for review. Two graded rounds on Verification is CI's own run on The Re-derived independently, all exact: diffstat 2 files +225/−10; merge-base = One finding, disclosed not patched. The string-only projection leaves a path inside a non-string element unscrubbed — measured: a nested Flag for a sweep, not a blocker: #371 has not been built with #368 or #370. Its CI builds Not merging. |
closes #369
Not merging — for review.
Two pieces of work with very different justifications, kept apart below.
1.
logentry.paramswas passing through verbatim — hardening, not a live leakScrubAccountNames(SentryEvent)rebuilt the message withMessageandFormattedredacted and copiedParamsacross untouched. It serialises aslogentry.params.I re-proved the ticket's reachability claim rather than taking it, and it holds: this is NOT reachable today.
SentrySdkis called from exactly four places in this repo —Init,CaptureExceptiontwice,AddBreadcrumbtwice,ConfigureScopetwice — andCaptureMessageandCaptureEventfrom nowhere.SentryTargetis deliberately not wired into NLog (AppLogger.cs:183), so no logging integration builds one either. Nothing in the app produces an event carrying alogentryat all, so nothing leaks today and no user data has gone anywhere. Treat this as defensive hardening of a privacy path: it is the same shape #366 was — a redactor that is correct and simply is not on the path — caught one step before anyone walks it. The firstCaptureMessage("… {0}", path)added would leak silently.The fix reuses the existing redactor and the existing hook. One new private overload beside the
stringone, and one changed line at the call site.ParamsisIEnumerable<object>, so it needed a decision the string fields did not. Only string elements go through the redactor; everything else is passed on as it arrived:string42as"42"— a payload change for every event with a parameter list, well past a scrubnullExtraloop already drawsDisclosure, added while shepherding — the boundary above has a consequence the table does not spell out. A path carried inside a non-string element is not scrubbed. Measured on
ea38b2c, through the shipped options and read back out of the serialised envelope:new[] { "/Users/octocat/…/DAQifiProfilesConfiguration.xml" }["/Users/octocat/…"]stringproperty holding a path{"Location":"/Users/octocat/…"}new Uri("file:///Users/octocat/Library/uri.xml")"file:///Users/octocat/Library/uri.xml"ToString(){}ToString()The
Urirow is the one that strains the rationale: it is not astringelement, so theis stringtest skips it, but its serialised JSON type is a string, so redacting it would cost no payload compatibility at all. Same for any type with aSystem.Text.Jsonconverter that renders to a JSON string —UriandVersionbeing the two that can carry a filesystem path.Not fixed here, deliberately: this PR is settled and a change to where the leaf boundary sits is its own decision. Filed as #373. It is not reachable today for the same reason the rest of this PR is not — nothing builds a
logentryat all.This is the same rule
ScrubAccountNames'Extraloop follows, deliberately, so there is one rule to remember rather than two. The projection is materialised withToList()so the serializer walks a caller's enumerable once rather than twice.2. Three scrubbed fields had no test — the higher-value half
logentry, breadcrumbdataandserver_namewere all scrubbed by #367 and pinned by nothing. #366 existed precisely because a correct redactor was not on the path; an untested scrub is that failure waiting to recur, and a future refactor could silently unwire any of the three.Tests are at the boundary, not at the helper — the lesson #367 established, and the whole reason #366 was invisible. Every new test assembles an event, pushes it through
ConfigureSentryOptions(the configuration the app ships), and reads the field back out of the serialised envelope. They extend #367's existing suite rather than starting a parallel one. Nothing leaves the machine: the same unroutable.invalidDSN and the same in-memoryRecordingWorker.server_namegets both halves the issue asks for: an event that carries one has it redacted, and the shipped options never populate the field at all (SendDefaultPii = false), so the default envelope has noserver_name. Without the first assertion the scrub line is unpinned; without the second, the belt-and-braces claim in the remarks is.Evidence
Against unmodified
main— the two figures are different in kind, so they are split.The whole file compiles against
main(every member it names already exists there), so all 10 new cases genuinely ran there. That is not true of #367's suite, which could not compile against its own base — worth saying, because "does not compile" is not "0 failed".mainlogentry.params(1 fact + 2 of the 5 table rows)logentry, breadcrumbdata,server_name26 passed / 3 failed of 29 in the class. The three passing field tests are therefore uninformative as regression evidence on their own, so each was mutation-tested instead: the scrub for one field unwired at a time on otherwise-unmodified
main, rebuilt, re-run. Each mutation was killed by exactly the test claiming that field and by nothing else:Message/Formattedstop being redactedA_path_in_a_log_message_is_redacteddatavalues stop being redactedA_path_in_breadcrumb_data_is_redacted@event.ServerName = …removedA_path_in_the_server_name_is_redacted_and_the_field_is_absent_by_defaultA fourth mutation was run against this branch, on the new code, to show the over-redaction guard is not vacuous: replacing the string test with
RedactAccountNames(parameter?.ToString())— the over-eager version of this same fix — is killed byLog_message_parameters_that_are_not_strings_keep_their_serialised_type, and by nothing else.Every mutation was applied by rewriting the source in place, never by a backup-file rename, so no run could have been served a stale assembly.
Over-redaction is the failure mode of this class of fix and #367 shipped a real instance of it (a profile of
/Users/octocatturned a sibling's/Users/octocat2/…into~2/…). The new parameter path is table-driven over the adjacency cases: a strict-prefix sibling account, the account name mid-path under a non-home root, an account name that is an ordinary English word sitting in prose beside a real path, and a string with no path at all that must arrive byte-identical.Gate
Measured on this branch and on its own merge-base (
origin/main), both sides force-rebuilt with--no-incremental— an incremental build reports zero warnings, which reads exactly like a clean result.Release/iossimulator-arm64(all-project)dotnet testBoth denominators are labelled because they differ. The first-party figure is validated by the identity rather than asserted: the vendored
third_party/oxyplot-avaloniawarnings count 20 at each end (21 log lines carrying both "warning" and "oxyplot", one of which is that project's own summary line, not a warning), and 155 + 20 = 175 = the all-project total. Filtering onAVP1002alone would have undercounted — only 13 of the 20 are that code.No warning in any of the four build logs cites
AppLogger.cs. Both builds judged by process exit code, not by grepping for a literal —-tl:onwrites ANSI escapes between "Build" and "succeeded", which is also why the per-code tally above had to be done on paths rather than onwarning CSxxxx. Neither build was a re-run following an XAML-step failure, so neither could be reporting a bogus success.git status --short -- '**/packages.lock.json'was empty after the no-RID restore; no lock-file drift to revert.origin/mainwas re-fetched after the measurements and is stillc45fe32, so these merge-base numbers are against the same base the PR targets.The Android head does not build on this machine, so I did not claim it locally — CI has since built it green (
Android head, 3m21s), along withLibrary + Desktop head + capture harness.Final diff, re-derived from
git diff --numstat origin/main...HEADafter the last commit: 2 files, +225 / −10 —SentryAccountNameRedactionTests.cs+199/−5,AppLogger.cs+26/−5.Scope
Legitimately additive, and mostly tests. No second scrubber, no strategy interface, no config knob — the existing redactor and the existing two hooks do all of it; the production change is one new 4-line private overload and one changed line.
tagsremain unscrubbed by design (a stated decision inAppLogger's remarks), and server-side scrubbing is #100 — neither is touched here. No Sentry event was emitted and the live project's settings were not touched.Checked against the other open PRs: #368 (
LoggedSeriesLegendItem.cs,LegendFormattingTests.cs) and #370 (SummaryFlyout.axamland its two test files) share no file with this branch.🤖 Generated with Claude Code