Reference
Privacy & redaction
Redaction happens on the device, before anything is written, with the same rules in every SDK (checked by shared conformance fixtures). It catches credentials. It does not catch personal data on its own; that part is your job.
Names, emails, addresses, order contents and other personal data that isn't a credential are not removed automatically. Add redactKeys, redactPatterns, beforeRecord/beforeCheckpoint hooks and exclude rules, then read real capsules with bugcapsule inspect --json before widening a rollout.
Built-in rules
| Where | Rule |
|---|---|
| By key name (bodies, storage, state, action payloads, checkpoints) | Names are normalized (lowercase; _ - . and spaces removed). Substring match: password, passwd, token, secret, apikey, authorization, cookie, cardnumber, creditcard, privatekey, sessionid. Exact match: pin, pan, otp, ssn, cvv, cvc, passcode, iban. Plus configured redactKeys and remote redactKeys. |
| Headers | authorization, cookie, set-cookie, proxy-authorization, x-api-key, x-auth-token, configured redactHeaders, and any header matching the key rules. Other header values are scrubbed as free text. |
| URLs (requests, routes, route checkpoints) | User info replaced. Query and fragment params matching the key rules or code, sig, key, auth, sid, session, *signature*, *credential* replaced. |
| Free text (failure messages, stacks, logs, labels, tracked text, text bodies, every string) | Bearer/basic tokens, JWTs, well-known API key formats, key=value / key: value / "key": "value" pairs with a sensitive key, Luhn-valid card numbers, configured redactPatterns. A string holding JSON is redacted as JSON. |
| Tracked text | Replaced whole when the id matches the key rules or the field is marked sensitive. Always for Android password input types (Views), iOS isSecureTextEntry, web type=password and data-bugcapsule-sensitive. |
The placeholder is [REDACTED]. Regex word boundaries are ASCII in every runtime. The rules used are recorded in manifest.redaction, and replay applies the same rules so request keys computed from redacted bodies still match.
What it doesn't find
- Secrets in URL path segments (
/reset/8f2c…). UsesanitizeLocationandexclude. - Personal data that isn't a credential.
- Secrets under innocuous key names that don't match a pattern.
- Binary bodies.
- State an integration chose not to describe (serializers decide what's captured).
Controls by SDK
| Control | Flutter | Android | iOS | Web |
|---|---|---|---|---|
| Extra keys / headers / patterns | redactKeys, redactHeaders, redactPatterns in the config (Android: builder methods, redactPattern(regex)) | |||
| Edit or drop any event | beforeRecord(type, data): return null to drop | |||
| Edit checkpoint data | beforeCheckpoint(integration, data) | |||
| Exclude endpoints (keep method/URL/status/timing) | BugCapsuleDio(exclude:), BugCapsuleHttp(exclude:) | BugCapsuleOkHttp(exclude =) | BugCapsuleURLSession(exclude:) | networkIntegration({ exclude }) |
| Rewrite route locations | BugCapsuleGoRouter(sanitizeLocation:) | BugCapsuleNavigation(sanitizeLocation) | you call setRoute | historyIntegration({ sanitizeLocation }) |
| Storage allowlist | allowPrefixes / denyPrefixes (default: all keys) | allowFile, allow, deny (default: nothing) | keys, prefixes, denyPrefixes (default: nothing) | allowPrefixes required |
| State | serializers project the state you choose; recordStateData off in production by default | you write the serializer; only registered state is captured | ||
| Body capture off | captureNetworkBodies: false locally, or remotely (also strips bodies already buffered). Capsules become partial; request bodies ≤ 64 KiB are still hashed (never stored) for matching. | |||
| Everything off | Local enabled: false (no capture code runs) or the remote kill switch | |||
beforeRecord: (type, data) {
if (type == 'log' && '${data['message']}'.contains('@')) return null; // drop logs that mention an email
if (type == 'http' && data['responseBody'] is Map) {
(data['responseBody'] as Map).remove('shippingAddress');
}
return data;
},
What's collected and where it goes
Only when capture is enabled and the session is sampled (debug and QA always; production at most productionSampleRate, default 10%). A capsule is written only on a failure or a QA capture.
Not collected: screenshots or screen recordings, platform channel traffic, native SDK data, location, contacts, device identifiers (no advertising or hardware id; the session id is random per launch).
| Where | Retention |
|---|---|
| Device memory | The session (discarded at exit or kill switch) |
| Device files | 20 newest saved captures; outbox ≤ 10 capsules / 10 MiB / 7 days; deleted on kill switch |
| Your BugCapsule project | Your project's retention period (30 days unless agreed otherwise), then deleted automatically |
| Share links | Until expiry (default 72 h, max 30 days), revocation or capsule deletion |
| Developer machines | Outside BugCapsule's control (pull, run, regression files) |
Deleting one capsule: delete it in the dashboard; it disappears with its runs and share links at once. One end user's data: BugCapsule has no user identifiers, so you find capsules by time, app version or labels, then delete them one by one. For anything else (retention, whole-project deletion, a data processing agreement) contact your BugCapsule team.
This describes the product, not a legal assessment. Whether it meets your obligations is for you and your counsel.
Keeping your keys safe
- The ingest key is public. Anyone can extract it from a shipped app, so it can only upload capsules and read capture settings. That's why it's the only key that goes in an app.
- The secret key is your project's admin key. Whoever holds it can read, share and delete your capsules and change capture settings. Keep it in the CLI config and CI secrets only, share it with as few people as possible, and rotate it in the dashboard when someone leaves.
- Share links are public by design. Anyone with one can download that capsule until it expires or you revoke it.