Repository navigation
fix(datadog): keep error causes that would need redaction out of Error Tracking - #1799
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces validation for the error.causes chain within the Datadog integration's beforeSend hook. Because error.causes is unmodifiable in the SDK, any event containing a cause that requires redaction or is relayed is dropped entirely to prevent leaking sensitive information. The changes include implementing the causesNeedNoRedaction helper, updating the DatadogErrorEvent interface, and adding comprehensive unit and integration tests. There are no review comments, so I have no feedback to provide.
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
This was referenced Oct 11, 2026
The RUM SDK flattens an error's `cause` chain into `error.causes[]` and sends it, and `beforeSend` never looked at it, so a cause's message and stack bypassed every redaction layer (#1665). Rewriting the causes in place is not possible: `error.causes` is not on the SDK's modifiable-field allowlist, and `limitModification` silently restores any write to it. So `beforeSend` now checks each cause against the same rules the top-level text gets (URL-path and credential redaction, and the relayed-type withhold, which a relayed error passes down its chain) and drops the event when any cause would change, or is in a shape other than the SDK's own. Clean causes are sent as before; the four cause-bearing events in the last 30 days of production RUM all are. Closes #1665 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rule for A cause can only be checked, not rewritten, so a field the SDK starts recording later would ship unchecked. Accept only the fields browser-core's computeErrorBase records per cause (source, type, message, stack), each a string or absent; anything else withholds the event, which the SDK-pipeline test then reports on the bump that adds it. Refs #1665 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cret names A failed set/delete/grant/revoke secret rejected with a plain Error carrying Harper's response text, and React Query's global handler passes that to console.error, which the RUM SDK reports. Harper composes those messages by interpolating the secret and component names (harper-pro core/components/secretOperations.ts), so they reached Error Tracking as written (#1664). All three throw sites now throw a named SecretsOperationError, and that name joins RELAYED_ERROR_TYPES, so beforeSend withholds the message and the stack header the same way it does for SSEOperationError and ImportJobError. The class carries no cause: under #1665 a cause on a relayed error would withhold the whole event, losing the failure rate. The secrets dialogs and grants editor toasted String(error), which would now read "SecretsOperationError: ..."; they toast describeError's message instead, as the global handler does. The shared SecretModals also serve the env-var editor, whose toasts now show Harper's response text (or axios's own message when there is none) rather than "AxiosError: Request failed with status code ...". Closes #1664 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wrapping every rejection in SecretsOperationError also withheld failures that carry no Harper text at all: a network error or a bug reached Error Tracking only as the withheld placeholder. Only a response body with Harper's message now becomes a SecretsOperationError; anything else is rethrown unchanged, keeping its own type and message. That also stops a structured body from turning into "[object Object]". Cite the OSS harper path for the interpolated secrets messages. Refs #1664 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ructured A body with a structured `error` and a string `message` short-circuited on the object and fell through to the raw AxiosError, so the string never became a SecretsOperationError. Prefer whichever field is a string. Refs #1664 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dawsontoth
added this pull request to stack #1921
October 12, 2026 20:40
dawsontoth
marked this pull request as ready for review
October 12, 2026 20:40
dawsontoth
force-pushed
the
claude/1665-redact-error-causes
branch
from
October 12, 2026 20:42
e3eaa8c to
1e05581
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.
📦 This PR now carries #1806 too
Rebased into 5 linear, signed commits on current
stage, with no merge commits: #1799 (keep error causes that would need redaction out of Error Tracking, Closes #1665) and #1806 (withhold failed secrets-operation text, #1664).Rebase: the only conflict was
RELAYED_ERROR_TYPESinredactRelayedMessage.ts, plus its test.stagehad addedDeployRolloutError(from #1868) to the same set where #1806 addsSecretsOperationError. The set now holds all four types, the doc comment names each one, and both tests stay. Every other file's changes are identical to what was reviewed. Gate on the final tree:tsc -b, oxlint,dprint check, vitest 474/474 files.⊙ Problem
The RUM SDK flattens an error's
causechain intoerror.causes[]and sends it.beforeSendnever read that field, so a cause's message and stack skipped every redaction layer: URL-path redaction, credential params, and the relayed-type withhold (#1665). Nothing leaks today. In the last 30 days, 4 of 4,407 production error events carried causes, and all four were anAbortErrorwith no customer text in it. But a future{ cause }that wraps server or customer text would have gone to Error Tracking as written.Closes #1665.
💡 Solution
The issue suggests walking
causes[]and redacting each entry. That can't work.error.causesis not on the SDK's modifiable-field allowlist, and the SDK'slimitModificationputs back whateverbeforeSendwrites there. The new SDK-pipeline test shows the rewrite being undone.So
beforeSendnow checks each cause instead of rewriting it. It withholds the whole event when any of these is true:messageorstackwould change under the rules the top-level text already gets (redactErrorTextandredactCredentialParams).SSEOperationError,ImportJobError). The flag is seeded from the error's own type and only ever set further down the chain. A relayed error's causes are the server's chain, so they are withheld just like its message.CAUSE_FIELDS) issource,type,messageandstack, each a string or absent, withmessagerequired. If a future SDK starts recording a new field, no rule here covers it, so the event is withheld. The SDK-pipeline test then fails on that bump.Clean causes ship unchanged. I replayed the 4 production cause-bearing events through the new
beforeSend: all 4 were kept, with their causes untouched. The check runs aftershouldKeepEventand inside the existingtry/catch. A malformed or hostile cause therefore returnsfalseinstead of escaping into the SDK's fail-open wrapper. An event with no causes costs one property read.⚖️ Alternatives
causeat Studio's throw sites. This misses unhandled errors and third-party errors that never pass through Studio code. It also contradicts the team's cause-chaining convention.pnpm patchthe SDK allowlist so causes can be rewritten. The SDK's lockfile entry has changed 12 times since July. A patch is pinned to one version, and the repo's last vendor patch (router-core) had to be retargeted and then removed.SSEInconclusiveErrorevents and protect nothing more.🔧 Changes
src/integrations/datadog/beforeSend.ts: adds the call that runs the cause check only whencausesis present, andcausesNeedNoRedaction, which holds the field allowlist, the shape checks, and the relayed flag that passes down the chain.src/integrations/datadog/redactRelayedMessage.ts: exportsisRelayed, so the relayed-type decision stays in one module.src/integrations/datadog/shouldKeepEvent.ts: addscausestoDatadogErrorEvent.✅ Verification
beforeSend.sdk.test.tsruns the installed SDK 7.15.0's real error collection, assembly andlimitModificationthrough toRUM_EVENT_COLLECTED. It shows that an error with a clean cause is sent, that an error with an unsafe cause sends nothing, and that abeforeSendrewrite of a cause is undone. That last case pins the premise of the whole fix.beforeSend.test.tscover a message, a stack, or a credential that needs redaction; a relayed error, a relayed cause, and a cause below a relayed one; a 10-link chain; empty and non-Error causes being kept; and ten malformed shapes plus a throwing getter. Each malformed shape returnsfalseand none throws.origin/stage'sbeforeSend, 17 of the new tests fail, including the SDK-pipeline test.pup) all went through the realbeforeSendand were kept, with causes unchanged. That evidence is off-repo.tsc -b,oxlintanddprint checkall exited 0.Cross-model review. A planning round (codex) returned
chosen-approach-sound. I adopted its two refinements: malformed shapes fail closed, and the SDK-pipeline test is committed. Two implementation rounds ran with codex and gemini. Two legs failed for infrastructure reasons: the domain adjudicator (Claude OAuth expired) and Cursor (the 1Password SSH agent refused to sign). With no adjudicator, I triaged the outside findings myself:beforeSend. The check runs inside the existingtry/catch, and the throwing-getter test passes..tsimport-extension nit. Imports in this repo are extensionless.Stacked: the #1664 PR builds on this branch.
🤖 Generated by Claude Code (Anthropic Claude); posted via @dawsontoth.
🤖 Generated with Claude Code
Related PRs: #1666 overlaps (merged; added the relayed-type withhold this extends to causes)
Complexity: medium
Review-Coverage: authored=claude; ran=codex,gemini; blocked=cursor-composer(no-receipt),domain(auth); declined=cursor-grok,cursor-kimi,cursor-muse; rounds=2; full=1 @ e3eaa8c
Review-Attention: read ~3m (raised: degraded review) @ e3eaa8c