Files
HiveGrid-Wireframes/hivegrid-cloud/docs/observability/sentry-replay-masking.md
T

5.3 KiB

Sentry Replay — PII Masking Conventions (Web)

This document describes how the boilerplate masks personally identifiable information (PII) in Sentry Session Replay videos so that DSGVO / GDPR obligations are met by default.

The defaults are configured in apps/web/app/sentry.client.ts:

Sentry.replayIntegration({
  maskAllInputs: true,
  maskAllText: false,
  blockAllMedia: false,
});

Why not maskAllText: true? Masking every text node makes the replay video useless for reproduction — buttons, headings, navigation labels all disappear. We instead opt-in to masking on the small set of nodes that actually contain PII.

Why not blockAllMedia: true? Blocking all <img> / <video> removes brand assets and UI screenshots that are needed for triage. Operators can flip this per-environment via VITE_SENTRY_REPLAY_BLOCK_ALL_MEDIA if their UI legitimately displays user-uploaded media on every page.

How to mark a region as sensitive

Three options are available, in increasing strictness:

Marker Effect
className="sentry-mask" Text content is replaced with * placeholders in the replay.
data-sentry-mask attribute Same as above; useful where you cannot edit the className (e.g. third-party widgets).
className="sentry-block" The entire DOM subtree is replaced with a placeholder rectangle — nothing is recorded.

Inputs are masked automatically thanks to maskAllInputs: true, but the sentry-block marker is the safer choice for forms that contain free-text PII (chat composer, profile editor, etc.) because it also covers labels and helper text adjacent to the inputs.

To debug masking locally without sending data to Sentry, add data-sentry-mask-debug="1" to the <html> element. The helper CSS in apps/web/app/styles/sentry-masking.css then renders a dashed outline around every marked region.

Required annotations (must-do list)

The following components/routes MUST be annotated by the time the staging-smoke test runs:

Authentication & Account

  • apps/web/app/routes/login.tsx — password input → sentry-mask
  • apps/web/app/routes/register.tsx — password input → sentry-mask
  • apps/web/app/routes/forgot-password.tsx — email input → sentry-mask
  • apps/web/app/routes/reset.tsx — password inputs → sentry-mask
  • apps/web/app/routes/auth.totp.tsx — TOTP code input → sentry-mask
  • apps/web/app/routes/auth.magic-link.tsx — email + token field → sentry-mask
  • apps/web/app/modules/users/components/UserProfileSettings.tsx — email, name, phone → sentry-mask
  • apps/web/app/modules/users/components/UserProfileSettings.tsx — password and account-deletion forms → sentry-block

Billing & Payment

  • Any Stripe Elements iframe — automatically masked by Stripe (no action needed).
  • Custom payment forms (e.g. invoice address) → sentry-block.
  • Saved-card list display → sentry-mask.

Tenant data (consider per-feature)

  • Customer/contact detail views (crm/*) → sentry-block for email, phone, address columns.
  • Helpdesk threads (help-desk/*) — body content of customer messages → sentry-block.
  • Email-marketing composer body → sentry-block.
  • File preview/download → already covered by blockAllMedia if enabled per-environment.

Admin surface

  • User-management table — email/phone columns → sentry-mask.
  • Tenant-admin impersonation banner (apps/web/app/components/impersonation-banner.tsx) — both email props → sentry-mask.
  • Audit-log viewer (apps/web/app/routes/admin/audit-trails.tsx) — metadata cells → sentry-mask.

Network recording

Sentry Replay can also record network responses. We keep this off by default because API responses can contain JSON with PII (tenant data, user attributes). Operators may turn it on per-issue inside Sentry when reproduction without network detail is impossible. To enable globally:

Sentry.replayIntegration({
  // ...,
  networkDetailAllowUrls: [], // explicitly allow specific paths only
  networkRequestHeaders: ["X-Request-Id"],
  networkResponseHeaders: ["X-Response-Source"],
});

Never add * to networkDetailAllowUrls.

Verifying masking on staging

  1. Trigger a real user error on staging (e.g. submit a form with an invalid value to a route that throws).
  2. In Sentry UI → Replays → open the captured session.
  3. Step through the timeline and check:
    • All password fields show only * placeholders.
    • Email/profile fields in marked regions are redacted.
    • <img> tags load if blockAllMedia was left false.
    • Network tab does not contain JSON response bodies (only headers + status codes).
  4. If any sensitive value is visible, file a BLOCKERS.md entry referencing this document and add the missing annotation in a follow-up commit.

Cross-references

  • Task spec: docs/improvement-plan/02-observability/sentry-wiring-web.md
  • Server-side PII strategy: apps/web/app/sentry.server.ts (requestDataIntegration with ip: false)
  • Query-string scrubbing: apps/web/app/sentry.client.ts#scrubSensitiveQueryParams