Oscura is an Android app that takes a document — Word, Excel, PowerPoint, PDF, image, plain text, or a photo straight from your camera — and produces a copy in the same format with the sensitive data irreversibly destroyed, not hidden. Names, addresses, fiscal codes, IBANs, phone numbers, emails, ID numbers: gone from the file, unrecoverable by anyone.
Built for the age of AI: share contracts, medical records, invoices and letters with chatbots, clouds or strangers after the personal data has left the file. Everything runs on your device — the app doesn't even declare the INTERNET permission — so nothing you redact ever touches a server. It also doubles as a full document scanner (edge detection, perspective correction, scan filters, multi-page PDF) that replaces the paid tier of typical scanner apps, entirely offline and open source.
Leggi il README in italiano.
- Redaction is real. Characters are overwritten inside the file (
█), pixels are painted over, PDFs are rebuilt image-only with no text layer. There is nothing to "un-hide" — unlike black rectangles drawn over a PDF text layer, the classic redaction failure. - Fails closed. If any part of a document can't be rewritten, the whole run aborts. Emitting unredacted bytes is never a fallback.
- Metadata too. Office author/last-editor/company fields, comment and tracked-changes authors, embedded page-one thumbnails, image EXIF (including GPS) — scrubbed along with the body text.
- Precision you can audit. Structured data is confirmed by checksums (MOD-97, Luhn, codice fiscale check character) before anything is touched; the whole detection engine is deterministic, pure JVM, and covered by unit tests.
- Private by construction. No network permission, no telemetry, no analytics, no backup of app data. Temporary files live in the private cache and inputs are deleted when processing ends — even when cancelled.
| Category | How it's recognised |
|---|---|
| Person names | Open lexicons (~9,100 given names, ~36,700 surnames) + honorifics (Sig., Dott.ssa…) + field labels (Cliente:…) + document-wide coreference |
| Codice fiscale | Structure (spaces tolerated) + checksum, with repair of OCR-confused characters (0↔O, 1↔I, 8↔B…) |
| Partita IVA | 11 digits + official checksum + issued office-code range |
| IBAN / cards / BIC | MOD-97 (ISO 13616), Luhn + network prefixes, labelled BIC |
| Addresses & places | Street prefixes (Via, P.zza, Corso… + civic number), postal code + town (ISTAT lexicon), personal context (residente a…, nato a…) |
| Birth dates | Context only (nato il…, data di nascita:) — ordinary dates survive |
| ID documents & plates | Passport, CIE, driving licence, health card (prefix + Luhn), vehicle plates, labelled document numbers |
| Emails & phones | Strict patterns + plausibility rules (Italian/international prefixes, Tel. labels) — protocol numbers and dates don't trigger |
| Custom terms | Words you choose to always redact, case- and accent-insensitive |
Three tiers of evidence, most reliable first:
- Checksums (CF, P.IVA, IBAN, cards, health card): a sequence is redacted only when it is mathematically valid data — zero structural false positives.
- Context (titles, labels,
residente a…): the context itself is the proof, and it also confirms foreign names absent from any lexicon. - Lexicons + supporting rules: a known given name confirms the whole sequence ("Michele Di Pietro", "ROSSI MARIO"); a safe surname catches unknown given names ("Yuki Esposito"); the ~4,300 ambiguous entries ("Rosa", "Ferrari", "Milano") never trigger alone, so ordinary prose survives.
On top of all this sits document-wide coreference: once "Mario Rossi" is confirmed anywhere, every other "Rossi" — a signature, a header, a different sheet of the same workbook — is redacted across the entire document.
When in doubt, the engine over-redacts: for a privacy tool, the false negative is the only truly expensive error.
| Format | Strategy |
|---|---|
Text (.txt/.csv/.md/.log) |
In-place character replacement with █ |
Word (.docx) |
Run-level XML text (even split across formatting runs), footnotes, headers/footers, comments, tracked-changes authorship |
Excel (.xlsx) |
Shared and inline strings, comments and threaded comments, drawing text, sheet headers; numeric cells holding validated PII (mobile numbers, P.IVA, cards) become redacted text cells |
PowerPoint (.pptx) |
Slide, notes and comment text, classic and modern comment authors |
| Rasterise → full-page OCR → black boxes → PDF rebuilt in streaming (constant memory per page) | |
Images (.jpg/.png/…) |
OCR → black boxes over the pixels; re-encoding also strips all EXIF (GPS included) |
| Camera scan | Edge detection → perspective correction → scanner filters → JPEG (single page) or PDF (multi-page), then choose: redact or keep |
All Office formats additionally get their identifying metadata blanked (author, last editor, company, comment/revision authors) and the embedded first-page thumbnail removed — it would otherwise leak the unredacted content wholesale.
The scanner is implemented entirely in-app — no computer-vision library, no runtime-downloaded module:
- Sheet detection: Sobel gradients + a gradient-guided Hough transform on a small analysis raster; the four corners always remain hand-adjustable with drag handles.
- Perspective correction: projective transform with proportions recovered from the quad's opposing sides.
- Filters: Document (per-cell illumination flattening + auto white balance + unsharp text — shadows disappear, paper turns white) and B/W (soft antialiased binarisation). The same enhancement feeds the OCR, so it improves redaction too.
- Multi-page: multiple shots become a single streamed PDF; at the end you choose to redact the result or save it as-is.
The camera is the system one — the app doesn't request the CAMERA permission. The math core (detection, geometry, filters) is pure Kotlin/JVM in domain/scan/, unit-tested on synthetic images.
- Text / Office: sensitive characters are replaced inside the XML, not hidden — the original no longer exists in the file. If a part can't be rewritten, processing fails closed.
- PDF / images: pages are rasterised and the pixels under the boxes are destroyed. The output PDF is image-only, with no text layer at all — which transparently covers scanned and photographed PDFs too.
domain/pii/ Detection engine: Lexicon (canonical form), centralized Patterns
and Validators, composable finders, PiiDetector with multi-chunk
scanning and coreference. Pure Kotlin/JVM — the entire accuracy
surface is covered by local unit tests.
domain/scan/ Scanner math: quad detection (Hough), geometry, pixel filters.
domain/redaction/ Irreversible replacement with █
data/processing/ One processor per format + RedactionService (single factory)
data/pdf/ ImagePdfWriter: minimal streaming PDF writer (O(1 page) memory)
data/ocr/ On-device ML Kit; OcrPage maps page-level matches to word boxes
data/ LexiconRepository (assets), PreferencesStore (versioned preset)
ui/ Jetpack Compose + Material 3 (Home / Scan review / Progress / Result)
assets/lexicons/ Versioned, regenerable data packs (see DATA_SOURCES.md)
Every processor extracts text its own way and hands it to the same DocumentScanner in a single call — that is what makes coreference work across pages, sheets and paragraphs. Patterns, validators, strings and versions are each defined exactly once.
The lexicons are plain text files in assets/lexicons/, generated from open datasets (ISTAT, MIT-licensed word lists) by tools/build-lexicons.ps1 and validated by smoke tests. Improving recognition doesn't require touching code: regenerate or hand-fix the data pack. Sources and licences in DATA_SOURCES.md.
Tuned for modern 64-bit ARM SoCs — Snapdragon 8 Gen 1/2/3 and 8 Elite, MediaTek Dimensity 9300/9400 and newer, Samsung Exynos 2400/2500 — while staying compatible down to Android 8.0:
- Release builds ship arm64-v8a + armeabi-v7a only, R8 full-mode minified with resource shrinking (~20 MB APK, OCR model included).
- 16 KB page-size compliant (verified with
zipalign -P 16), ready for Android 15+ devices booting with 16 KB pages. - Baseline profiles from Compose and AndroidX are compiled into the APK and installed on first run via
profileinstaller— peak AOT performance without JIT warm-up. - The image pipeline works on flat
IntArrays with fixed-point arithmetic and single-pass separable filters; PDF pages stream through one at a time, so memory stays constant regardless of document size. - No Play-encrypted dependency blob (
dependenciesInfooff) and no VCS metadata in the APK: builds are reproducible.
Requirements: JDK 17+ and Android SDK Platform 37 (or an up-to-date Android Studio).
./gradlew :app:testDebugUnitTest # the engine: validators, finders, coreference, OOXML, PDF writer
./gradlew :app:assembleDebug # debug APK
./gradlew :app:assembleRelease # release APK (signed if keystore.properties exists)To sign releases, copy keystore.properties.sample to keystore.properties and point it at your keystore. CI builds an unsigned release APK on every push; tagged releases are published by the release workflow.
- No network permission, no telemetry,
allowBackup=false, cloud backup and device transfer disabled. - Temporary files live in the private cache; the imported input is deleted when processing ends, even if cancelled. Output is shared only on your explicit action.
- Office documents get metadata and thumbnails scrubbed; images get EXIF stripped.
The full statement is in PRIVACY.md.
- Legacy formats (
.doc/.xls/.ppt) are unsupported; OfficecustomXml/parts and Excel chart labels are not scanned. - Name detection is optimised for Italian documents; structured detection (IBAN, cards, emails, phones) is already international.
- OCR uses ML Kit's bundled on-device model — the one closed-source component (which also keeps the app off F-Droid's main repo for now). The
PiiFinderseam is designed so an open on-device NER/OCR backend can be added as a drop-in finder without touching the rest.
Bug reports, lexicon fixes and detection test cases are the most valuable contributions — see CONTRIBUTING.md. Every change to the detection engine must come with a unit test that pins the new behaviour.
Apache-2.0 © blast752. Third-party components are listed in THIRD_PARTY_NOTICES.md.