Obfs Encrypt is 100% offline. There is no crash SDK, no analytics, and no automatic upload. A good bug report must therefore be built in-app, privacy-safe, and copyable by the user into GitHub, email, or chat.
This document is the engineering plan and the contract for what the diagnostic pipeline is allowed to capture.
- Users can export a complete diagnostic report in one tap (Settings → Support).
- Developers get enough context to reproduce without a second round-trip.
- Never capture passwords, keyfile bytes, Keystore material, file contents, or full user paths.
- Work on API 24–35, debug and release, with and without storage permission.
- Survive process death: the last crash is persisted and included on the next launch.
- No network telemetry (would break the offline security promise).
- No Play Vitals / Firebase Crashlytics (requires network + third-party SDKs).
- No automatic issue creation.
┌─────────────────────────────────────────────────────────────┐
│ Catch sites (crypto, worker, UI, storage) │
│ │ │
│ ▼ │
│ AppLogger ──► logcat (tag: ObfsLog) │
│ │ ──► in-memory ring buffer (last 400 events) │
│ │ │
│ CrashHandler ──► files/crash_last.txt (survives restart) │
│ │ │
│ ▼ │
│ DiagnosticsCollector │
│ • app/build/device/settings snapshot │
│ • sanitized recent history (ops only, redacted names) │
│ • ring buffer + last crash │
│ • privacy scrubber │
│ │ │
│ ▼ │
│ BugReportExporter │
│ • Share sheet (ACTION_SEND text/plain) │
│ • Copy to clipboard │
│ • Save to app files (optional) │
└─────────────────────────────────────────────────────────────┘
| Component | Path | Responsibility |
|---|---|---|
AppLogger |
diagnostics/AppLogger.kt |
Structured levels, tags, ring buffer, logcat |
CrashHandler |
diagnostics/CrashHandler.kt |
UncaughtExceptionHandler, persist stack + breadcrumbs |
DiagnosticsCollector |
diagnostics/DiagnosticsCollector.kt |
Assemble sanitized markdown report |
BugReportExporter |
diagnostics/BugReportExporter.kt |
Share / copy / save |
Wired in ObfsApp.onCreate() before any work starts.
These rules are enforced in DiagnosticsCollector.sanitize* and must not be bypassed.
| Data | Treatment |
|---|---|
| Passwords, keyfile bytes, salts, nonces, Keystore keys | Never logged. Call sites must not pass them. |
| File contents | Never logged. |
| Full file paths | Replaced with <path> + extension only. |
| File basenames | Redacted: first character + … + extension (e.g. t….pdf). |
| Display names / emails | Not collected. |
| Absolute external storage roots | Collapsed to /…/ form. |
History errorMessage |
Kept (exception messages rarely contain secrets); paths inside messages scrubbed. |
| Device model, API, locale, ABI | Kept — needed for triage. |
| App version / build type / flags | Kept. |
| Feature toggles (theme, app lock, biometrics, shred, integrity) | Kept as booleans only. |
If a future field is added, it must land in this table first.
The exported markdown looks like:
# Obfs Encrypt Bug Report
## Summary
<!-- user fills: what happened, expected, steps -->
## App
- versionName / versionCode / buildType / debuggable
## Device
- manufacturer / model / API / ABI(s) / locale / low-ram
## Settings (non-secret)
- theme, language, app lock, biometric, shred, integrity, storage permission, output folder set
## Recent operations (sanitized)
- last N history rows: op, method, success, size bucket, redacted name, error class
## Last crash (if any)
- time, thread, exception, sanitized stack (top 25 frames)
## Recent log events
- timestamped AppLogger ring bufferTarget size: under ~50 KB so it pastes into a GitHub issue body.
- Uncaught exceptions (via
CrashHandler) - Encryption / decryption failures (
EncryptionHelper,ParallelEncryptionHelper) - WorkManager worker failures (
EncryptionWorker) - Keystore load/init failures (
SecureKeyStore) - Permission / SAF failures that abort an operation
- History parse failures
- Navigation hops, dialog compose, permission dialogs (existing
Log.dcan stay for debug builds)
- Password / keyfile / ciphertext bytes
- Full URI strings from the picker
- User-visible file names in free text (use
sanitizeFileName)
Allowed only for best-effort cleanup (temp file delete, stream close).
Any catch that hides a user-visible failure must call AppLogger.w/e with tag + class name.
- Settings → Support → Report a bug
Opens a bottom sheet / dialog explaining what is included and what is excluded. - User taps Share report or Copy report.
- Optional fields: user pastes a short description into the report header before sharing
(compose
Summarysection in the collector, or leave the HTML comment for them). - Help → How to report a bug FAQ explains the same path for users who land in Help first.
No email address is hard-coded; the share sheet lets the user pick GitHub, email, notes, etc.
| File | Purpose |
|---|---|
.github/ISSUE_TEMPLATE/bug_report.yml |
Structured bug form; asks for the pasted diagnostics blob |
.github/ISSUE_TEMPLATE/feature_request.yml |
Separate from bugs |
.github/ISSUE_TEMPLATE/config.yml |
Disables blank issues; links security policy |
The bug form requires the diagnostics block (or an explicit "could not export" note).
- Reproduce with the same versionCode if possible.
- Look for
Last crashfirst — stack often identifies the module immediately. - Check
Recent operationsfor method (STANDARD/FAST/STRONG), file size bucket, success flags. - Check settings flags: app lock, biometric, shred, integrity, custom output folder.
- If the ring buffer is empty, the crash happened before
ObfsApp.onCreatefinished — inspectcrash_last.txtonly path and ANR/low-memory clues on the device form.
| Phase | Scope | Status |
|---|---|---|
| P0 | AppLogger, CrashHandler, collector, exporter, ObfsApp wiring |
Done |
| P0 | Settings Support section + Help FAQ + strings (en/ar) | Done |
| P0 | Instrument crypto + worker critical catches | Done |
| P1 | GitHub issue templates | Done |
| P2 | Persist last 200 log lines to disk (optional, disk-privacy tradeoff) | Open |
| P3 | On-device "reproduce steps" wizard that times operations | Open |
- New catch sites in
crypto/,services/,security/must useAppLogger. - New settings that affect crypto behavior must be added as a boolean/category line in the collector.
- Privacy table changes require a one-line note in the PR description.
- Do not add network libraries to "improve" reporting.