fix(runner): gate the Outlook-scanner and edgeless-504 populations at beforeSend (DEV-2858) - #325
Merged
Conversation
…ulations (DEV-2858) Adds two beforeSend gates, in a new import-free apps/authoring/src/eventGate.ts, for populations the Sentry triage classified NOT-OURS: the Microsoft Outlook/Office safelink scanner's injected unhandled rejection (DEMOS-5F, 24 events) and the edgeless-foreign Tier-2 session-start facet (DEMOS-9, 786 events). Pinned by new cases in pipeline/sentry-gating.test.mjs. This is code, not a Sentry dashboard filter, because custom message filters are plan-locked on this project (one DSN shared across all three environments) — there is no per-environment filter to add instead. isEdgelessForeignSessionStart keeps a `!cf_ray` conjunct that is unreachable today: sessionDiagnostics.ts's responseOrigin() only ever returns "foreign" when there is no ray, so every event this gate can currently see already lacks one. It stays anyway. The session-lifecycle.ts precedent that forbids guessing at unobserved inputs applies to WIDENING a predicate to a new case with no evidence; `!cf_ray` is the opposite, a NARROWING conjunct that can only ever suppress fewer events than the gate would without it, never more. It is what keeps the gate correct if the origin taxonomy ever starts emitting "foreign" for a ray-bearing response — which would be our side of the edge, and the real capacity signal DEMOS-9 exists to protect. This does not zero DEMOS-9's 786 events: ~88 of them predate the DEV-2559 diagnostics-tag instrumentation and carry no tags at all, so they keep reporting — correctly, since an untagged event carries no evidence of being not-ours. DEMOS-4P (the preview harness's apparent componentDidCatch) is deferred, not implemented here: grepping all source for componentDidCatch and getDerivedStateFromError returns nothing in this repo. The boundary that produced that Sentry title belongs to the framework or the previewed project, not to code this repo owns, so no gate here can reach it. Verification: pnpm typecheck (run first, before writing tests, to catch any assignability mismatch between Sentry.ErrorEvent's optional exception.values[].mechanism.handled / tags index signature and eventGate.ts's structural types — none found), the 28 new/existing sentry-gating cases via node --experimental-strip-types --test, and the full pnpm test suite (1057 tests, 1055 pass, 2 pre-existing todo, 0 fail). Revert-check: hollowing both predicates to `return false` flips exactly the 4 positive cases red (24 pass / 4 fail) while all negative cases stay green, confirming the tests exercise the real predicates rather than tautologies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
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.
Two
beforeSendsuppression gates for populations the DEV-2852 triage classified NOT-OURS: Sentry DEMOS-5F (24 events) and DEMOS-9 (786 events — the largest issue in the project).Why this is code and not a dashboard filter
Sentry custom message filters are plan-locked on this project (one DSN across all three environments, so rate limits can't be per-environment either). Message-shaped filtering has to be a code gate. There is no dashboard-only option.
Where the predicates live
A new zero-import module
apps/authoring/src/eventGate.ts, not inline insentry.ts. That file readsimport.meta.envand imports@sentry/react, so nothing in it can be unit-tested — the same reasoning that produced the existingreportingGate.ts. Parameters are structurally typed with no imports at all (node --experimental-strip-typescannot resolve@sentry/reacttypes or./x.jsspecifiers).Both gates are inserted immediately after
isUnhandledNoiseand before thesurface === DEMO_SURFACEre-homing branch.isForeignUnhandledsits after that branch because relays carry preview-origin frames by definition; neither new gate needs that protection, and the insertion comment records the discriminator rather than just the issue id — 5F requiresmechanism.handled === falsewhile every relay arrives viacaptureException(handled: true), and DEMOS-9 requires tags thatreportDemoEventnever sets.Gate 1 —
isOfficeScannerRejection(DEMOS-5F)The Microsoft Outlook / Office safelink scanner injecting script into the page. Unhandled rejection, no first-party stack frame, nothing fixable on our side.
Conjuncts: some
exception.values[]hasmechanism.handled === falseand itsvalueortypematches/Object Not Found Matching Id/i.The integers after
Id:andParamCount:are deliberately left out of the pattern rather than\d+-ed, andMethodName:is excluded entirely — it varies in the wild (update,getInstance, …) and the prose is already discriminating. Degrade direction, in the house style: the string comes from third-party injected script, so if Microsoft rewords it the predicate stops matching and the event reports again — noisy, never silent.Gate 2 —
isEdgelessForeignSessionStart(DEMOS-9)Confirmed NOT-OURS by its own DEV-2559 instrumentation:
session_response_origin=foreign,session_response_type=basic,session_elapsed_bucket=<1sclustered (a fixed ceiling above our handler, not slow container boots), andcf_rayempty on all 25 sampled foreign-origin events — the request never reached our Cloudflare edge, so something upstream answered.Verified this is reported client-side:
ContainerRuntime.mountships in the browser bundle and the report site is inApp.tsx. It does not belong worker-side —workers/api/src/sentry-gate.tsonly resolves DSN/environment and re-homes budget alerts, and more decisively, a 504 that never reached our edge produced no Worker invocation at all.context === "tier2-session-start"is a scoping conjunct so a future reuse ofsession_response_originelsewhere isn't silently silenced.session_statusis deliberately not a conjunct, matching the existing precedent that "the tier turns on where the response came from, not on the status."The
!cf_rayconjunct is unreachable today. Please don't delete it.sessionDiagnostics.tsreturns"cloudflare"whenever a ray is present, so"foreign"already implies no ray, andforeign+cf_raycannot occur right now. It looks like dead code, and there is a house rule that would seem to license removing it — the one saying a predicate must not recognise a wording no event has produced on its path, because "widening this predicate for it would be a guess dressed as a fact."That rule forbids widening to unobserved inputs, because widening is what silences unverified things.
!cf_rayis a narrowing conjunct: it can only ever suppress less, never more. Direction is the discriminator. It is what keeps the gate correct if theresponseOrigintaxonomy is ever changed to emit"foreign"for a ray-bearing response — and a ray-bearing 504 is our side of the edge, i.e. the real capacity signal we must not silence. The code carries this argument as a comment, and test N1 pins it with an input that cannot occur yet.This does not zero the 786
The ~88 events predating the DEV-2559 instrumentation carry no diagnostics tags at all and keep reporting. That is correct: an untagged event carries no evidence of being not-ours.
Verification
pnpm typecheck— run first, deliberately, since the real risk was assignability betweenSentry.ErrorEvent'smechanism.handledoptionality /tagsindex signature and the structural shapes. All 4 packages green.pipeline/sentry-gating.test.mjs— 28/28pnpm test—1057 tests / 1055 pass / 0 fail / 2 todo(todos pre-existing)Revert-check, with a correction worth recording. The instructed check — comment out the two
sentry.tswiring lines — turned out to be a no-op: the test file importseventGate.tsdirectly and never loadssentry.ts. Verified empirically (28/28 still passed). The meaningful check was hollowing both predicates toreturn false: 24 pass / 4 fail, and the 4 failures were exactly the 4 positive cases (5F-P1, 5F-P2, 9-P1, 9-P2), with every negative staying green.Negative coverage is the point of this change, so it's spelled out: a DEMOS-9-shaped event with a
cf_raystill reports;origin: "cloudflare"+ ray still reports;"unreadable"(every cross-origin dev machine) and"headerless"still report, so one taxonomy value is suppressed rather than three; an event with no diagnostics tags still reports; a differentcontextstill reports; and for 5F, the same message withhandled: truestill reports.Known gap, flagged rather than hidden
The two
beforeSendcall sites themselves have no test coverage. A typo, an inverted condition, or an accidental deletion of either line would go undetected, becausesentry.tscannot be loaded under plain Node. This is not new — it is the same structural limitation the file already has forisUnhandledNoiseand theDEMO_SURFACEre-homing branch, andsentry-gating.test.mjs's own header comment acknowledges it. Non-blocking, but a future reviewer should not assume the wiring is pinned just because the predicates are.DEMOS-4P deferred, on structural grounds
The task originally scoped a third item: fix the preview harness's
componentDidCatch. There is nocomponentDidCatchorgetDerivedStateFromErroranywhere in the repo — the generated React harness is a barecreateRoot(...).render(...). So the boundary that producedThe above error occurred in the <ExampleComponent> component:is not one we own; it's the framework's own boundary for the Tier-2 SSR frameworks, or one inside the previewed project. Both viable fixes land inpackages/runtime/src/monitor.ts, which DEV-2853 owned. Now that DEV-2853 has merged, this is unblocked as a follow-up — recommended scope is teaching the console interceptor to recognise React's two-part output, which currently falls through to a stacklessconsole-error.Also withdrawn from this task: the original claim that DEMOS-18 / DEMOS-2N bypass
reportDemoEvent. They don't. But a bypassing population does exist — DEMOS-1H and DEMOS-1E, which carry noDemoErrorname at all — and those are left unresolved in Sentry with a note pointing here.🤖 Generated with Claude Code
Note
Medium Risk
Changes Sentry ingest filtering for real production errors; mistakes could drop actionable session-start signals, though narrow conjuncts and extensive negative tests aim to prevent that.
Overview
Adds code-side Sentry suppression for two triaged NOT-OURS populations (DEMOS-5F, DEMOS-9) because dashboard message filters are plan-locked on the shared DSN.
A new import-free module
eventGate.tsholdsisOfficeScannerRejection(unhandledmechanism.handled === falseplus Microsoft safelink scanner wording) andisEdgelessForeignSessionStart(tier2-session-start+session_response_origin: foreign+ no non-emptycf_ray).sentry.tswires both intobeforeSendright afterisUnhandledNoiseand before demo-runtime re-homing, with comments explaining why relays are unaffected.pipeline/sentry-gating.test.mjspins positive/negative cases for both predicates.run-and-deploy.mdnotes these gates stay when demo-runtime monitoring is removed.Reviewed by Cursor Bugbot for commit 21611fe. Bugbot is set up for automated code reviews on this repo. Configure here.