Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/beta-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:

- name: Type Check (mypy)
shell: bash
run: mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py
run: mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py help_guide.py

- name: Resolve beta suffix from tag
if: startsWith(github.ref, 'refs/tags/')
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ jobs:
run: pytest tests/ -v

- name: Type check (mypy)
run: mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py
run: mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py help_guide.py

- name: Resolve beta suffix from tag
if: inputs.is_beta && startsWith(github.ref, 'refs/tags/')
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ jobs:

- name: Type Check (mypy)
shell: bash
run: mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py
run: mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py help_guide.py

- name: Build
run: python build_local.py
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:

- name: Type Check (mypy)
shell: bash
run: mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py
run: mypy NullifyPDF.py pii_detection.py privacy_core.py update_check.py PDF_Checker.py export_engine.py pdf_sanitize.py review_dialog.py lexicon_it.py secure_store.py word_lists.py dictionary_dialog.py help_guide.py

- name: Build
run: python build_local.py
Expand Down
12 changes: 7 additions & 5 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ It supports two privacy export modes:
| `pdf_sanitize.py` | Export sanitiser (annotations, attachments, scripts, hidden layers/text, off-page text, metadata, optional TJ-gap quantisation) and `verify_export()` |
| `export_engine.py` | Qt-free export pipeline (`export_document`), neutral file names, private temp handling, audit record, local-data wipe |
| `review_dialog.py` | Qt dialog to accept or reject suggested detections |
| `help_guide.py` | In-app guide (Italian, F1) and the tooltip table applied to every widget |
| `secure_store.py` | Master key protected by DPAPI / OS keyring / key file; AES-GCM sealing; atomic private writes |
| `word_lists.py` | Encrypted blocklist/allowlist with expiry (`PDFListManager`) |
| `dictionary_dialog.py` | Qt editor for both lists with dates and default expiry |
| `lexicon_data_it.py` | Generated from ISTAT open data by `scripts/build_lexicon_data.py` (see `THIRD_PARTY_NOTICES.md`); imported lazily |
| `lexicon_data_it.py` | Generated by `scripts/build_lexicon_data.py` from ISTAT open data (municipalities, provinces, regions) and, with `--wikidata`, Wikidata (given names, surnames); see `THIRD_PARTY_NOTICES.md`; imported lazily and used only to confirm names and places |
| `PDF_Checker.py` | Heuristic post-export inspection helper |
| `build_local.py` | PyInstaller build script |
| `scripts/download_ocr_data.py` | Downloads EN/IT Tesseract `tessdata_fast` files bundled into every build |
Expand Down Expand Up @@ -149,9 +150,10 @@ Clicking an existing redaction:
- widgets are removed
- `pdf_sanitize.sanitize_document()` removes annotations, attachments, scripts, bookmarks, structure, metadata, hidden/invisible/off-page text and external links
- the final PDF is saved with cleanup options
6. `pdf_sanitize.verify_export()` re-opens the written file and checks it; a failure is reported and the file can be deleted.
7. For pseudonymization, the restore map is encrypted and written with owner-only permissions.
8. An audit record (no values) is written to `~/.nullifypdf/audit/`.
6. Before the redactions are applied, every band that stores the whole box of its word is checked; after applying them, the number of glyphs that vanished from each line must match the measured deletion rule (a glyph is deleted when the rectangle reaches its box inset by 10%), and target glyphs the band cannot reach are counted. Any survivor is reported without the value.
7. `pdf_sanitize.verify_export()` re-opens the written file and checks it; a failure is reported and the file can be deleted.
8. For pseudonymization, the restore map is encrypted and written with owner-only permissions.
9. An audit record (no values) is written to `~/.nullifypdf/audit/`.

The original open document remains editable after export because the export works on a copy.

Expand Down Expand Up @@ -320,5 +322,5 @@ pytest tests/ -v

---

*Last updated: 2026-11-08*
*Last updated: 2026-11-10*
*[Back to README](./README.md) | [Development →](./DEVELOPMENT.md)*
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### ✨ Added

- **User guide in the program**: a new "Guida" button (or F1) opens a guide in Italian that explains the whole workflow: opening a PDF, language and options, Auto Redact, review of suggestions, manual corrections, dictionaries, export modes, reconstruction, health documents, local data and shortcuts.
- **Tooltips on every element**: all buttons, options, fields, tables and indicators of the main window and of the dialogs now show a short description when the pointer rests on them.
- **Check after redaction for surviving characters**: besides searching the written file, the export now checks that every character of a redacted word was really deleted, and reports it (without the value) if one was not.
- **Check for updates**: the Info dialog has a new "Controlla aggiornamenti" button. It queries the GitHub releases of this repository (on click, or at startup only if you enabled the opt-in startup check) and, if a newer version exists, shows the release link, the release notes and a **Download** button that saves the installer for the running OS into the user's system Downloads folder (resolved per platform), verifying its size and the SHA-256 published by GitHub. Linux distributions outside the Debian/Ubuntu and Fedora/RHEL families only get the release link. Stable builds are only offered newer stable releases; beta builds are offered both stable and beta releases.
- **Optional check at every start**: a "Controlla ad ogni avvio" checkbox in the Info dialog (off by default, stored in `~/.nullifypdf/update_check.json`) runs the same check silently at startup and opens the update window, with the release notes, only when a newer version exists. Failures are logged, never shown.
- **Repository link in the Info dialog**.
Expand All @@ -24,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Signature and stamp suggestions**: with the health-document profile, curve-rich ink and small images next to "firma", "timbro", "il medico"... are proposed for review (never applied automatically).
- **Postcodes and house numbers**: a street and its house number are redacted as one address, and postcodes are recognised only in an address context (never in prices, dates or quantities). Health card number, prescription number (NRE) and exemption codes follow the published structure (length, region code, lot code); the check digit is not verified, and the prescription number and exemption codes need their label.
- **Suggestions in the page preview**: pending suggestions are outlined in orange on the page (view only; they never reach an export). Click one to censor or discard it; selecting a row in the review dialog highlights it.
- **Given names and surnames from Wikidata** (CC0): a few hundred given names and about one thousand surnames are used to confirm names, never to reject them; names that are also ordinary words count less. Coverage is small.
- **ISTAT place names**: Italian municipalities, provinces and regions (ISTAT open data, CC BY 4.0) are used to confirm places; a place missing from the list is never rejected for that reason.
- **Export audit record** in `~/.nullifypdf/audit/` (counts per type, sanitiser actions, verification result, output SHA-256; no personal data), and an "Elimina dati locali" button that wipes logs, audit records and temporary files.

Expand All @@ -43,6 +47,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Placeholders were silently dropped in small boxes**: the placeholder is now drawn after the redactions are applied, with a compact form ("P9F2A", "9F2A") when the full one does not fit, so short words keep a visible placeholder. Restore maps are version 4 (version 1-3 maps still load).
- **Restore wrote neighbouring text back**: the typography captured for a redaction could include fragments of the lines above and below when the leading was tight; only the redacted line is captured, and the restore writes the original value.
- **Wipe of local data left the active log file**, and "Oscura Immagini" added duplicate image redactions on every apply.
- **Labels on images and signatures survive small boxes**: the "[IMMAGINE RIMOSSA]" / "[FIRMA RIMOSSA]" labels are drawn after the redactions, with shorter forms when the area is small, instead of being dropped.
- **Master key in the system keyring on macOS and Linux**: when a keyring is available the encryption key of the dictionaries is stored there instead of in a local key file (Windows keeps using DPAPI).
- **Local logs are rotated** (512 KiB, 3 files, 30 days) instead of growing indefinitely.
- **Redacted text could remain in the PDF content stream**: with PyMuPDF 1.28.x, `apply_redactions()` did not remove text drawn with the `'` operator after `TL`/`Td`; the text disappeared from the extracted text but was still stored in the file. Fixed by PyMuPDF 2.x; covered by a regression test that scans every stream of the exported file.

Expand Down
5 changes: 3 additions & 2 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,8 @@ isort NullifyPDF.py
| `secure_store.py` | Master key protection (DPAPI / keyring / key file), AES-GCM sealing |
| `word_lists.py` | Encrypted blocklist/allowlist with expiry |
| `dictionary_dialog.py` | Dictionary editor dialog |
| `scripts/build_lexicon_data.py` | Generates `lexicon_data_it.py` from ISTAT open data (development only) |
| `help_guide.py` | In-app guide and tooltip table (keep the guide in sync with the UI: `tests/test_gui_help.py` checks it) |
| `scripts/build_lexicon_data.py` | Generates `lexicon_data_it.py` from ISTAT open data and (with `--wikidata`) Wikidata names; development only |
| `setup_env.py` | Environment setup |
| `build_local.py` | PyInstaller build |
| `PDF_Checker.py` | Post-processing utility |
Expand Down Expand Up @@ -589,5 +590,5 @@ The release workflows fill each GitHub release body with `scripts/extract_releas

---

*Last updated: 2026-11-08*
*Last updated: 2026-11-10*
*← [Troubleshooting](./TROUBLESHOOTING.md) | [Back to README →](./README.md)*
Loading
Loading