Frontend Error Support Bundles: User Consent, Privacy Budgets, and Proof of Redaction
Browser errors need context, but full-page recordings, request bodies, and console output can easily capture tokens and personal data. This article defines a client-redacted, budgeted Support Bundle that helps support teams reproduce UI failures without copying user sessions by default.
Key Points
- Topic Focus: React、TypeScript、DevOps
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
If “Download diagnostics” simply packages console.log, HAR data, the DOM, and LocalStorage into an archive, the support team gains valuable context, but it may also receive login tokens, chat messages, form input, and internal URLs. Collecting nothing, however, leaves an intermittent blank screen with little more than “refreshing fixed it.” React error boundaries provide fallback UI when rendering fails, while Web Performance RUM aggregates metrics. A Support Bundle is a previewable, expiring diagnostic artifact that the user intentionally generates for one specific failure.
It also differs from a debug reproduction evidence bundle for coding agents: the latter freezes a repository, runtime, and test inputs so an engineering runner can replay them. This article covers only client-side state that the browser has already exposed to the end user and excludes source code, working trees, and server-side data by default. The two artifacts may reference the same anonymous error fingerprint, but they must never copy raw content between them.
Define capturePolicy.v1 First
The server issues a read-only web.supportCapturePolicy.v1 containing the applicable release, permitted route templates, a field allowlist, per-category count and byte limits, the redactor version, retention period, and supported bundle schema. The browser generates web.frontendSupportBundle.v1, pinning the policy digest, application release, browser capabilities, error fingerprint, relative timeline, permitted network metadata, counts of applied redaction rules, and the user’s consent receipt. Full URLs, request and response bodies, cookies, Authorization headers, LocalStorage, DOM text, and clipboard contents are prohibited by default.
schema: web.frontendSupportBundle.v1
policyDigest: sha256:5a7...
release: web@sha256:901...
routeTemplate: /orders/:orderId
budget: { bytes: 262144, seconds: 60, network: 100, events: 200 }
redactionReport: { removed: 47, blockedArtifacts: 2 }
Client-side state is restricted to COLLECTING -> SANITIZING -> USER_REVIEW -> SEALED -> UPLOADED. Closing the page or exhausting the budget transitions the bundle to DISCARDED; blobs that have not completed redaction cannot be persisted. Only after the user confirms the SEALED summary does the server issue a one-time upload authorization bound to the user, tenant, policy digest, bundle digest, nonce, and short expiry. Cross-tenant uploads, bundle substitution, and replay are always rejected. A policy upgrade produces a new digest, and an older reader rejects the upload if it does not recognize a required field.
Even a successful upload enters only RECEIVED_QUARANTINED -> SERVER_VALIDATED -> AVAILABLE on the server. Support personnel cannot access the quarantine area. Validation failures transition to REJECTED, and temporary chunks are destroyed. A client claim of removed: 47 is never sufficient for approval; the client-side redaction report is only a reconciliation signal.
Redaction Happens on the User’s Device
Network entries retain only the method, route template, status code, protocol, duration, and allowlisted response headers. Query strings and fragments are removed before an entry reaches the queue. After an error stack is mapped against the source map for its release, only function names and in-application locations are stored; the source map itself remains in a controlled service. User actions are represented by stable component IDs such as click:save-button, without preserving typed characters. Screenshots, DOM fragments, and session replay require separate, additional consent and are disabled by default. They cannot be hidden behind a general “improve the product” toggle.
W3C Trace Context can use traceparent to correlate server-side traces, but its identifier can also become a cross-system correlation key. Retain it only within the same incident domain, and rewrap the reference for the support tenant. High-cardinality account IDs, email addresses, and order IDs cannot be processed with an ordinary hash because dictionary attacks can reverse them. When correlation within one bundle is genuinely required, use a short-lived, per-bundle keyed token and destroy the key when it expires. OpenTelemetry’s guidance on sensitive data emphasizes minimization, deletion, and hashing in the Collector or application. This design goes further by requiring the browser to complete the first deletion pass before persistence.
Before upload, the interface displays field categories, entry counts, total size, retention period, and blocked items. The user can deselect categories individually. The consent receipt is bound to the bundle digest; yesterday’s consent cannot authorize today’s new bundle. Support personnel receive least-privileged access scoped by ticket, tenant, and time, while every download and export is audited. Revocation stops new access and generates a deletion receipt. If an artifact has already been lawfully exported to another system, the product must disclose that boundary in advance. The server-side access and retention controls from Prompt and Trace Redaction Governance can be reused, but the browser policy remains independently versioned.
The quarantine validator streams the archive while limiting compressed bytes, total decompressed size, compression ratio, file count, individual file size, and nesting depth. It rejects path traversal, symlinks, duplicate names, and trailing data. It reparses every file type with a fixed parser, reconstructs objects from the server-side allowlist, discards unknown fields, and then performs a second redaction pass and an independent secret scan. An artifact reaches AVAILABLE only after the reconstructed digest, identity binding, budget checks, and scans all pass. Support personnel cannot download the raw archive.
Turn Leakage Paths into Adversarial Fixtures
The test page deliberately places secrets in URL queries, fragments, GraphQL variables, error messages, console output split across chunks, DOM inputs, filenames, source-map sourceContent, WebSocket frames, and third-party properties. It then injects compression bombs, nested archives, huge numbers of small files, invalid UTF-8, forged client reports, replayed upload tokens, and expired policies. The client or server must delete or block them, or return POLICY_EXPIRED, BUDGET_EXCEEDED, UNSAFE_ARTIFACT, or UPLOAD_REPLAY; it must never fall back to the unredacted original.
The release gate uses at least 10,000 adversarial samples. Detection and blocking rates for known secrets must both be 100%, prohibited fields reaching AVAILABLE must be 0, parseability after truncation must be 100%, bundles must not exceed 256 KiB, bypasses of decompression limits or compression-ratio limits must be 0, each one-time authorization must allow exactly one successful upload, network requests after cancellation must be 0, and failed temporary chunks must be cleared within 30 seconds. Blind tests using known incidents must classify the correct root cause at least 90% of the time, and that rate cannot be improved by relaxing restrictions on private fields.
Initial rollout runs the sanitizer in memory and has the server validate synthetic bundles without enabling downloads. Internal accounts and 1% of users who actively submit feedback then begin using it. If even one prohibited field, authorization replay, upload after cancellation, or deletion timeout occurs, immediately revoke the policy, disable the entry point, and quarantine every bundle from the same generation. The client returns to displaying only an error ID. Recovery increments the policy generation and reruns the adversarial fixtures. The principle of minimal exposure in MCP audit logs applies here as well.
Related
Related Content
Article
Is Next.js App Router a Good Fit for a Technical Community?
Technical trends, engineering practices, and independent development perspectives centered on Next.js and React.
Article
SEO Design for a Content Community with Next.js
Technical trends, engineering practices, and an indie developer's perspective on Next.js and TypeScript.
Article
The Engineering Value of TypeScript in AI Application Frontends
Technical trends, engineering practices, and an independent developer perspective on TypeScript and React.
Article
What Value Do Server Components Offer Content Sites?
Technical trends and engineering practices around Next.js and React, from an independent developer's perspective.