Build1 distinct publisher3 min readUpdated
A local-first workout app enforced its privacy rules in beforeSend and unit tested them green. The data that left the browser came from the SDK's own context, on a path no test had inspected.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The failure sits upstream of the hook, in the fixture. The Kotlin test builds a `SentryEvent`, sets the three extras the author already suspected, and asserts those three come back null [4]. It passes, and it could only ever pass, because its input was assembled from the same list as the policy [3]. Nothing in that loop ever handled an event the SDK populated for itself, which is exactly where `location.href` arrives [7][3].
So both things that went over the wire were defaults. The browser SDK attaches `location.href` to every event, transaction and replay on its own [7], and the automatic fetch breadcrumb records request URLs verbatim [13]. Neither is an argument to a capture call [1]. The one line the author guarded, a `captureMessage` carrying a coarse `reason` tag and nothing else [6], behaved exactly as its comment promised. The envelope it travelled in did not: decoded, the intercepted request held `Push Day A`, `Incline Barbell Bench`, and a note reading `left shoulder clicks past parallel` [10]. That is two of the six categories the policy declared could never appear in an event [3][4], and the second one is a description of somebody's joint.
The waitlist finding is the more instructive of the two, because nothing about it looks like instrumentation. Deduplicating signups by using the email address as the Firestore document id is a defensible design, and it puts the address in the request path [12]. Once any library in the page logs request URLs, that design becomes a copier: the email would have been attached to every error occurring later in the session [13]. No policy about event fields has an opinion on this, because the address was never a field.
Count the controls. The written policy, the `beforeSend` hook, the unit test and the comment all governed the path from application code into the SDK. One artifact, a request listener that greps outbound `sentry.io` bodies for known-sensitive strings, governed the path from the SDK to the network [8][2]. It failed on the first run [9].
The cost of that asymmetry is not really the bug, it is the copy. WhyRep's offline, accountless, on-device design is the product claim, not a slogan [1], and the page shipping the leak carried a privacy policy stating the site had no third-party trackers [2]. Base64 in a URL fragment is not encryption [10], so a share link opened by anyone with a decode error handed a third-party service a training log it was written specifically to withhold [5]. The author's own summary is the useful part: every guarantee he wrote covered data his code passes to Sentry, and none covered what the SDK collects by itself [11].
That is a boundary problem dressed as a scrubbing problem. A unit test on a scrubber proves a function removes the fields you named. Only the bytes prove the product does.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
Sentry's browser SDK attaches location.href to every event, every transaction and every replay by itself as standard context, and the URL fragment goes with it.
Decoded, the intercepted Sentry request contained 'Push Day A', 'Incline Barbell Bench', and a note reading 'left shoulder clicks past parallel'; the author notes base64 is not encryption.
The author's conclusion: 'Every guarantee I had written was about data my code passes to Sentry. None of them were about data the SDK collects on its own.'
WhyRep is a workout tracker built local-first: training data is created and read on the device, and the tracker works offline with no account, described by the author as the architecture rather than a marketing line.
The leak happened on a page whose privacy policy said the site had no third-party trackers.
Before writing instrumentation code, the author wrote a scrubbing policy into the repo listing fields that may never appear in an event: exercise names, weights, reps, RIR, session notes, chat content.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Specific and code-level, but single-author and unverified
The account carries unusually concrete artifacts for a self-published postmortem: the passing Kotlin scrub test, the captureMessage call with its comment, the Puppeteer interceptor that failed on first run, the decoded envelope strings, the literal Firestore request path, and the three-hook remediation. What is missing is anything externally checkable — no SDK version, no vendor documentation citation for the location.href and breadcrumb defaults, no repository or reproduction link, and no second observer. So the mechanism is well described but rests entirely on one author's report.
One shipped project, two confirmed exposure paths
Real-world footprint is narrow but not hypothetical: two distinct exposure paths were observed in a deployed site and landing page, and a remediation was applied across three hooks. There is no evidence of the pattern being confirmed elsewhere, no user, session or signup counts, and no indication that other teams have adopted the envelope-interception test, so adoption signal stays low.
Slightly understated relative to what was demonstrated
Framing is self-critical rather than promotional: the author claims a mistake, not a discovery, and the headline finding is backed by the decoded envelope rather than by rhetoric. The generalized maxim that a control covering one of three exits is not a control reaches a little past one project's evidence, but the demonstrated mechanism — SDK-automatic context defeating code-path scrubbing — is arguably more broadly consequential than the modest personal-blog framing suggests, so the gap sits marginally negative.
Contest submission plus founder product exposure
Two disclosed incentives shape the telling. The piece is explicitly a submission to DEV's Summer Bug Smash: Smash Stories, which rewards dramatic bug narratives, and it doubles as exposure for the author's own product, whose local-first, trust-in-four-seconds positioning and waitlist are described in the same post. Neither incentive undermines the code artifacts, but both favor a vivid, self-flattering-through-humility framing and discourage disclosure of unresolved items such as data purging, notification, or exposure counts.
Mechanism credible, scope and verification thin
Confidence is moderate: the described failure mode is internally coherent, technically plausible and demonstrated with a detection method that would fail loudly if wrong, which supports the core claims. It is held down by the single-publisher cluster, absence of vendor or version corroboration for the default behaviours, and complete silence on exposure scale, purge and notification, leaving the consequences of the incident undetermined even where the mechanism is convincing.
build
"No key, no SDK" is not a guarantee on Android: the manifest merger gets there first1 distinct publisher
build
A GPU SQL Engine Lost to One CPU Thread Because a Dispatcher Constant Was 128x Too Small1 distinct publisher
build
Four indexes, none of them covering: the 78-second page and the one index that fixed it1 distinct publisher
build
Short Build Roots Do Not Fix MAX_PATH: Unreal Cook Failures Are a Windows Setting1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 22, 2026