This document explains how NullifyPDF is structured today: application layers, privacy export flow, OCR support, release variants, and the main security boundaries that matter during maintenance.
Tip
Read this after the README if you want the technical view of how detection, redaction, pseudonymization, and packaging work together.
NullifyPDF is a local-first desktop application for preparing PDFs before they are shared with third parties or uploaded to AI systems.
It supports two privacy export modes:
- Irreversible anonymization — selected content is redacted and removed from the exported PDF
- Reversible pseudonymization — selected content is replaced with stable placeholders, while original values are stored in a separate encrypted restore map
Note
Reversible mode is pseudonymization, not true anonymization, because the encrypted restore map can re-identify the original values.
| 📄 File | ✨ Responsibility |
|---|---|
NullifyPDF.py |
Main PySide6 UI, PDF rendering, AI scan orchestration, redaction, export, OCR integration |
privacy_core.py |
Privacy mode enum, placeholder registry, encrypted restore-map payloads |
PDF_Checker.py |
Heuristic post-export inspection helper |
build_local.py |
PyInstaller build script with Lite/Full release variants |
scripts/download_ocr_data.py |
Downloads EN/IT Tesseract tessdata_fast files for Full builds |
tests/ |
Unit and smoke tests for validation, privacy primitives, OCR config, and build variants |
User
|
v
PySide6 UI (NullifyPDF)
|
|-- PDFView renders the current page with PyMuPDF pixmaps
|-- Manual redaction draws pending redaction annotations
|-- Export dialog selects anonymization or pseudonymization
|
v
AIWorker (QThread)
|
|-- Extracts digital text from pages
|-- Runs OCR on scanned/image-only pages when enabled
|-- Runs Presidio + spaCy entity detection
|-- Emits detections back to UI thread
|
v
UI thread applies pending redaction annotations
|
v
Export pipeline writes a cleaned PDF copy
| 🧱 Class | 📍 Location | 🎯 Purpose |
|---|---|---|
PDFListManager |
NullifyPDF.py |
Loads and saves blocklist/allowlist files in ~/.nullifypdf |
AIWorker |
NullifyPDF.py |
Background NLP/OCR worker that emits page-level detections |
PDFView |
NullifyPDF.py |
Custom QGraphicsView for rendering pages, drawing rectangles, and zooming |
NullifyPDF |
NullifyPDF.py |
Main window and application controller |
PlaceholderRegistry |
privacy_core.py |
Creates stable placeholders such as PERSON_001 and EMAIL_ADDRESS_001 |
- User opens or drops a PDF.
NullifyPDF.load_path()validates the path and extension.- PyMuPDF opens the document.
- Password-protected PDFs are rejected.
- The first page is rendered in
PDFView. _warn_if_scanned_pdf()warns when pages look image-only and may need OCR.
- User selects language:
EN,IT, orBOTH. - User optionally enables:
OCR PDF scansionatiOscura Immagini
cmd_auto_ai()checks OCR configuration if OCR is enabled.AIWorker.run_scan()processes pages in a background thread.- For each page:
- digital text is extracted with
page.get_text() - if the page looks scanned, OCR is run with
page.get_textpage_ocr() - Presidio analyzes the extracted/OCR text
- detected entities are emitted with text, entity type, source, and optional OCR rectangles
- digital text is extracted with
apply_ai_to_page()runs on the UI thread and creates pending redaction annotations.
Redactions remain annotations until export. The original in-memory document is not flattened while the user is reviewing it.
Manual drawing over text:
- adds a redaction annotation
- records the clipped text in annotation metadata when available
- adds the text to the blocklist
Clicking an existing redaction:
- removes the pending annotation
- adds the clipped text to the allowlist when available
cmd_export() is the main export entry point.
- User chooses privacy mode:
- irreversible anonymization
- reversible pseudonymization
- User chooses output PDF path.
- For pseudonymization only:
- user chooses
.nullifypdf-mappath - user enters a password of at least 12 characters
cryptographyavailability is checked before writing output
- user chooses
- The in-memory PDF is saved to a sibling temp file:
<target>.nullifypdf.tmp
- The temp PDF is reopened as
ex_doc. - Each page is processed:
- overlapping links are deleted for redacted areas
- redactions are applied with PyMuPDF
- widgets are removed
- Document metadata and selected catalog keys are cleared.
- The final PDF is saved with cleanup options.
- The temp file is removed.
The original open document remains editable after export because the export works on a disk-backed copy.
Irreversible anonymization applies redaction annotations directly. The exported PDF does not contain a restore map.
This mode should be used when the output PDF must not contain recoverable personal data from the selected redaction areas.
Warning
Residual risk still exists for complex PDFs. NullifyPDF removes selected redacted content and common metadata, form, and link structures, but the output should still be reviewed before high-risk sharing.
Reversible pseudonymization replaces each selected value with a placeholder.
Example placeholders:
PERSON_001
EMAIL_ADDRESS_001
IBAN_CODE_001
PHONE_NUMBER_001
DATA_001
The mapping is stored in a separate encrypted file:
document.nullifypdf-map
The restore map contains:
- source filename
- source SHA-256 when available
- output SHA-256
- placeholder entries
- original values
- entity types
- page numbers
The map is encrypted with cryptography using Fernet and a key derived from the user password with PBKDF2-HMAC-SHA256.
Important
The restore map is never embedded in the pseudonymized PDF.
OCR is implemented through PyMuPDF's integrated Tesseract support.
Runtime behavior:
find_tessdata_dir()resolves Tesseract language data in this order:TESSDATA_PREFIX- bundled
ocr/tessdataviaresource_path() - PyMuPDF's
fitz.get_tessdata() - common platform locations
ocr_language_for_choice()maps UI language to Tesseract codes:EN->engIT->itaBOTH->eng+ita
AIWorker._page_needs_ocr()runs OCR only when text extraction is missing or very poor.- OCR text is used for Presidio detection.
- OCR search rectangles are used to place redactions on scanned pages.
The Full release bundles EN/IT OCR data. The Lite release requires local Tesseract tessdata when OCR is enabled.
The UI thread owns rendering and document mutation.
The worker thread performs text extraction, OCR, and NLP analysis. Access to the shared PyMuPDF document is serialized with QMutex.
Important rules:
AIWorkershould not mutate page annotationsapply_ai_to_page()runs on the UI thread- export happens after detection and review, not during the worker scan
This separation prevents UI freezes and reduces concurrent document-mutation risks.
Lite builds do not bundle OCR language data. They are smaller and still support OCR if the user has Tesseract tessdata configured locally.
Full builds bundle:
ocr/tessdata/eng.traineddata
ocr/tessdata/ita.traineddata
If the files are missing, build_local.py --full automatically downloads them from tesseract-ocr/tessdata_fast through scripts/download_ocr_data.py.
The .traineddata files are ignored by git and are not stored in the repository.
GitHub Actions build both variants on each supported OS:
- Windows Lite / Full
- macOS Lite / Full
- Ubuntu Lite / Full
- Fedora Lite / Full
Beta releases build and upload all artifacts. Stable releases promote the latest verified beta artifacts without recompilation.
Expected artifact names include:
NullifyPDF_vX.Y.Z_Windows_Lite.exe
NullifyPDF_vX.Y.Z_Windows_Full.exe
NullifyPDF_vX.Y.Z_macOS_Lite.zip
NullifyPDF_vX.Y.Z_macOS_Full.zip
NullifyPDF_vX.Y.Z_Ubuntu_Lite.deb
NullifyPDF_vX.Y.Z_Ubuntu_Full.deb
NullifyPDF_vX.Y.Z_Fedora_Lite.rpm
NullifyPDF_vX.Y.Z_Fedora_Full.rpm
Current tests cover:
- list persistence
- resource path resolution
- OCR configuration helpers
- privacy placeholder registry
- encrypted restore-map round trip
- build variant selection
- Full build OCR data inclusion behavior
Run:
pytest tests/ -v- Detection is probabilistic. Presidio, spaCy, and OCR can miss data or create false positives.
- OCR depends on Tesseract language data and scan quality.
- Handwriting is not reliably supported.
- Complex PDFs may contain structures not covered by automated cleanup.
- Pseudonymized PDFs are still personal-data processing when the restore map exists.
- Digital signatures are invalidated by privacy export.
Warning
For high-risk workflows, exported PDFs should be reviewed independently before sharing.
Last updated: 2026-07-23
Back to README | Development →