Skip to content

feat: configurable event scrubber chain and data minimization options - #55

Draft
gradinarufelix wants to merge 4 commits into
flownative:mainfrom
gradinarufelix:feature-scrubber-chain
Draft

feat: configurable event scrubber chain and data minimization options#55
gradinarufelix wants to merge 4 commits into
flownative:mainfrom
gradinarufelix:feature-scrubber-chain

Conversation

@gradinarufelix

Copy link
Copy Markdown
Contributor

Adds a configurable event scrubber chain so integrators can remove or redact data from events before they leave the server — our use case is GDPR data minimization for privacy-sensitive customers, where request bodies, query strings, IPs and email addresses must not reach Sentry.

Stacked on #52, #53 and #54 (their commits are included here); marked draft until those land, then this rebases to the scrubbing commit only.

Mechanism

  • EventScrubberInterface::scrub(Event $event, ?EventHint $hint): ?Event — may mutate, replace, or return null to discard.
  • Explicit registration via settings, keyed by a stable identifier:
Flownative:
  Sentry:
    scrubbers:
      'Acme.Request':
        className: 'Flownative\Sentry\Scrubbing\RequestScrubber'
        position: 100
        options:
          queryParamAllowlist: ['search']
  • Positions use Flow's PositionalArraySorter; entries are disabled with ~. The whole chain is validated and instantiated at boot, so misconfiguration fails the deployment instead of surfacing as a runtime scrubbing failure.
  • Composed into before_send, before_send_transaction and before_send_check_in, after the existing exception excludes.
  • Fail-closed + fail-loud: if a scrubber throws, the event is discarded and a synthetic, built-from-scratch replacement event is sent (scrubber ID, normalized class names, integer exception code, discarded event ID — nothing derived from the failed event), so scrubbing failures stay visible in Sentry instead of degrading silently.

Bundled scrubbers

  • RequestScrubber — keep-only request interface (method, URL, query string, headers); query string filtered against an allowlist without parse_str() (repeated params and raw encoding survive), fragments dropped, body/cookies/env always removed; also filters breadcrumb URLs.
  • ValuePatternScrubber — named built-in patterns (url-credentials with scheme allowlist, email, ipv4, ipv6, iban, phone) applied literally via callback across messages, exception values, breadcrumbs, extras, contexts, tags, fingerprints, transaction names, spans and check-in slugs; canonicalized sensitive-key redaction (api_key/api-key/X-Api-Key); objects are replaced without serializing their content. The user interface is deliberately untouched.
  • FrameVarsScrubber — strips frame vars, relativizes absolute file paths, normalizes anonymous-class path segments.
  • SpanDataScrubber — drops span data values, removes URL query strings from transaction names/span descriptions/span tags (SQL ? placeholders survive).

Data-minimization toggles (defaults preserve current behavior)

attachSessionTag, attachProcessInfo, captureStacktraceVariables (also gates debug_backtrace() args for message events), userContext: service|username|sha1|none (none also removes a scope user set by foreign code via Scope::removeUser()).

Notes for review

  • Settings schema included; PHPUnit suite (46 tests) covers the scrubbers, the chain, boot validation and client-level integration via ClientBuilder with an in-memory transport (verified against sentry/sentry 4.30.0 — e.g. Span::setData() merge semantics are accounted for).
  • SDK floor stays ^4.0; before_send_check_in and some setters were only verified against 4.30. Happy to raise the floor or add a CI matrix if you prefer.
  • Naming (scrubbers / EventScrubberInterface) and the reserved-options list from feat: add sdkOptions passthrough for Sentry SDK client options #52 are open to bikeshedding.

… in Objects.yaml

DefaultUserContextService implements UserContextServiceInterface, which is
also the interface SentryClient injects. The previous binding targeted the
UserContext value-object interface and only worked through Flow's implicit
single-implementation resolution.
Flownative.Sentry.sdkOptions lets projects configure any YAML-representable
Sentry SDK option (e.g. max_request_body_size, send_default_pii) without a
package change. Options set explicitly by this package always win; the
before_send* callbacks, ignore_exceptions and integrations are reserved and
rejected at boot with the offending settings path; in_app_exclude entries
are merged with the package defaults.
… the global scope

captureThrowable() and captureMessage() previously wrote extras, tags, user
and the session tag into the hub's scope permanently. In long-running CLI
processes (queue workers) data from one capture leaked into all subsequent
events: stale Reference Codes, exception_code tags and WithExtraDataInterface
payloads. Per-event data is now applied inside withScope(), so it evaporates
after each capture; the process-stable flow_version/flow_context tags remain
on the global scope, set once at initialization.
@gradinarufelix
gradinarufelix force-pushed the feature-scrubber-chain branch from 01df5a5 to d2664c8 Compare August 16, 2026 09:57
…ions

- EventScrubberInterface with explicit registration via
  Flownative.Sentry.scrubbers (stable IDs, PositionalArraySorter positions,
  per-entry options, disable via ~); the chain is validated, sorted AND
  instantiated at boot so misconfiguration fails the deployment loudly
- chain composed into before_send, before_send_transaction and
  before_send_check_in, after the configured exception excludes
- fail-closed + fail-loud: a throwing scrubber discards the event and sends
  a synthetic, built-from-scratch replacement event carrying only the
  scrubber ID, normalized class names, integer code and discarded event ID
- bundled scrubbers: RequestScrubber (keep-only request interface,
  delimiter-preserving query parameter allowlist, header allowlist),
  ValuePatternScrubber (url-credentials/email/ipv4/ipv6/iban/phone patterns
  applied literally via callback, canonicalized sensitive-key redaction,
  object replacement without serialization, coverage incl. transaction
  names, spans, breadcrumbs, tags, fingerprints, check-in slugs),
  FrameVarsScrubber (also relativizes absolute file paths and strips
  anonymous-class path segments), SpanDataScrubber
- data minimization toggles, defaulting to previous behavior:
  attachSessionTag, attachProcessInfo, captureStacktraceVariables,
  userContext (service|username|sha1|none; none also removes a
  foreign-code scope user via Scope::removeUser())
- flow_version/flow_context tags re-applied per capture as before, so they
  survive foreign scope replacement
- settings schema, PHPUnit suite incl. client-level integration tests with
  an in-memory transport
@gradinarufelix
gradinarufelix force-pushed the feature-scrubber-chain branch from d2664c8 to 343bd5d Compare August 16, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant