Repository navigation
Release v2.2.0: PDF reconstruction, Italian PII recognition, single build artifact - #111
Merged
Merged
Conversation
…, main guard) - Fix issue templates: default labels pointed at "bug"/"enhancement", which don't exist in the repo, so issues filed via template got no label at all. Now use the real type:bug/type:feature labels. - project-automation.yml: label PRs by conventional-commit title prefix (feat/fix/docs/chore/ci/...) and by changed files (new .github/labeler.yml), so non-dependabot PRs get repo labels too, not just assignment. - release.yml: verify CHANGELOG.md has a dated heading for the tagged version and an empty [Unreleased] section before promoting a beta to stable, enforcing the develop-Unreleased -> main-version changelog flow. - New guard-main-source.yml: reject PRs into main whose head branch is not develop, since main must only ever be updated via develop.
Verified in an isolated Python 3.13 environment combined with PR #104's bumps: 27/27 tests pass (including the Fernet encrypt/decrypt round-trip in privacy_core.py, which is unaffected by the PKCS7/FFDH changes in 50.0.0), mypy unchanged (18 pre-existing errors), and both Lite/Full PyInstaller builds succeed. This resolves the same open Dependabot security alert (CVE-2026-69247) as PR #99, which targeted main directly via GitHub's security-update path and will be closed as a duplicate once this lands.
Verified: v7's config schema (changed-files / any-glob-to-any-file) matches our current .github/labeler.yml exactly (confirmed against the v7 README). The v5->v7 jump is internal only (ESM migration in v7, minor bug fixes in v6.x), no breaking config changes. Already rebased cleanly onto develop including the guard-main-source/labeler.yml commit (7117bdd).
…with 4 updates (#104) Verified in an isolated Python 3.13 environment combined with PR #101's cryptography bump: 27/27 tests pass, mypy unchanged (18 pre-existing errors), and both Lite and Full PyInstaller builds succeed (confirmed PyMuPDF 1.28.2 still ships cp310-abi3 wheels, so the Python 3.13 compatibility premise holds). Dropped a requirements.txt comment that no longer matched the pinned spacy version.
…with 2 updates (#105) Verified in an isolated Python 3.13 environment: 27/27 tests pass, mypy unchanged (18 pre-existing errors), Lite PyInstaller build succeeds with pyinstaller 6.22.3, and the Fernet encrypt/decrypt round-trip works correctly with cryptography 50.0.1. Both updates are patch-level bugfixes only (cryptography 50.0.1 rebuilds wheels against OpenSSL 4.0.2; pyinstaller 6.22.3 fixes Windows bootloader edge cases), no API/behavior changes relevant to this codebase.
…taller 6.22.3)
Scope Qt6/PySide6 enum aliases (Qt.LeftButton -> Qt.MouseButton.LeftButton and similar), rename render() -> render_page() to remove an accidental signature clash with QWidget.render(), use getattr(sys, "_MEIPASS") for the PyInstaller-injected attribute, and add a DetectionDict TypedDict for the OCR-assisted AI detection dict. No behavior change; verified with mypy (0 errors), pytest (27/27), an offscreen GUI construction smoke test, and a Lite PyInstaller build.
… batch (#108) The concurrent multi-file upload in softprops/action-gh-release@v3 was timing out on the large binaries (100-300MB each) with an opaque "Error saving asset" and no per-file retry, leaving the v2.1.1-beta.1 release with a partial (and then, after a retry, empty) set of assets. Replaced with a sequential gh release upload loop with per-file retry.
…otion too (#109) The v2.1.1-beta.1 release had an empty title because gh release create was called without --title. Fixed in beta-release.yml, and applied the same fix plus the sequential-upload-with-retry pattern (from the beta workflow's fix for large-asset upload timeouts) to release.yml's stable promotion step, which had the identical softprops/action-gh-release batch-upload of the same large binaries and would hit the same failure mode on the next promotion.
…list-change confirmations (#110) * feat: fix manual redaction selection, extend AI PII recognition, add list-change confirmations - Fixed a crash in PDFView.mousePressEvent/mouseMoveEvent: event.position() returns a QPointF, but this PySide6 version's QGraphicsView.mapToScene() only accepts QPoint, so drawing a manual redaction raised an uncaught TypeError on every mouse press. Only AI-assisted detection actually redacted anything until now. - Deferred point_clicked/rect_drawn emission from mousePressEvent to mouseReleaseEvent, gated by the final swept distance, so a drag that starts on top of an existing redaction can no longer pop a modal mid-gesture or fire both signals for one gesture. - Extended the Presidio AI engine with Italian-specific recognizers (IT_FISCAL_CODE, IT_VAT_CODE, IT_DRIVER_LICENSE, IT_IDENTITY_CARD, IT_PASSPORT) and added "IT" to the phone-number recognizer's supported regions (Italian numbers without a "+39" prefix were never matched). _infer_entity_type() gained matching regexes for manually-selected text. - Manually drawing a redaction now asks whether to add the word to the permanent blocklist instead of silently persisting it. Removing an existing redaction now offers a 3-way choice: exclude only from this document, add to the permanent whitelist, or cancel. - Opening a PDF with no extractable text now shows a popup (in addition to the existing log message) confirming it looks scanned and pointing at the OCR toggle; manual selection already works on such pages via the existing image-redaction fallback. - Renamed the "Esporta Privacy" button to "Esporta PDF". - Bumped to 2.2.0-beta.1: the published v2.1.1-beta.1 predates all of the above (new features, not a patch), so that line is being retired in favor of a new beta cycle. * docs: update remove-redaction and detected-entity docs for the new UX TROUBLESHOOTING.md's 'Wrong Redactions Applied' section still described the old single-click-silently-allowlists behavior; USER_GUIDE.md's AI scan entity list didn't mention the new Italian national-ID formats.
apply_redactions() was called with images=PDF_REDACT_IMAGE_REMOVE, which deletes an image entirely if it intersects any redaction rectangle. On a scanned PDF (the whole page is one image), redacting even a single word wiped out the entire page, leaving only the black bar/placeholder on an otherwise blank sheet. Switched to PDF_REDACT_IMAGE_PIXELS, which blanks only the pixels under each redaction rect. Verified with an end-to-end offscreen test (real cmd_export call, not mocked): after the fix, text outside the redacted region survives export in both anonymized and pseudonymized mode, and the embedded page image is no longer deleted. Also verified PDF_REDACT_IMAGE_PIXELS does not leave a visible fringe for the existing whole-image redaction feature (Oscura Immagini), including under page rotation.
…re map Every build/release artifact now bundles OCR data unconditionally instead of only "Full" builds, dropping --lite/--full and the _Lite/_Full naming from build_local.py, the release workflows, and docs. Adds a "Ricostruisci PDF" action that decrypts a .nullifypdf-map restore map and rewrites a previously pseudonymized PDF with the original values back in place of their placeholders. Refuses to proceed if the PDF's SHA-256 doesn't match the output_sha256 recorded in the map.
Reconstruction located placeholders by searching the exported PDF for
their text. On a real scanner PDF that search silently finds nothing:
apply_redactions() lays its replacement text out as a text box, so a
narrow-and-tall redaction box (the usual shape when drawing over a short
field on a scan) makes MuPDF wrap the placeholder and break it mid-token,
"DATA_" + "001". The page still reads DATA_001 and the export looked
correct, but search_for("DATA_001") returned zero hits, so every
placeholder was skipped, nothing was raised, and the reconstructed output
was identical to the input.
Restore maps are now version 2 and record each placeholder's page and
redaction box (PlaceholderOccurrence), not just the page of its first
occurrence. reconstruct_pdf addresses those boxes directly; version 1
maps still work via a character-level fallback that also finds
placeholders wrapped across lines. fit_redaction_fontsize() picks a size
that never breaks a token, used on both the export and reconstruct sides.
A second defect rode along: the restored count was incremented when a
redaction was requested, but apply_redactions() drops its replacement
text entirely when it can't be laid out in the box (e.g. long unbreakable
values like fiscal codes or IBANs) -- so a dropped value used to be
reported as restored. The count is now read back off the saved file, and
cmd_reconstruct warns when it falls short of what the map expects instead
of reporting success. The completeness check counts occurrences, not
entries, so a repeated value only partially restored across its pages no
longer passes as complete, and a same-page duplicate-box guard was
narrowed from any-intersection to containment so two adjacent but
distinct fields can't shadow each other.
Reproduced end to end against a real NAPS2 scanner PDF before fixing.
Regression tests cover the narrow-box wrap, the version 1 fallback, the
dropped-value undercount, adjacent touching boxes, and a repeated value
restored from its own per-page box.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
developto stablev2.2.0, validated through betas 1-4.QPointF/mapToScene), blank scanned-page export, and three separate reconstruction bugs (placeholder text wrapped mid-token on narrow boxes, values silently dropped when they didn't fit their box, repeated values on other pages never restored) — see CHANGELOG for full detail._Lite/_Fullfilename suffix.cryptography50.0.1, closing Dependabot alert Fix: stable cross-platform build and versioning #6 (CVE-2026-69247) once merged, since GitHub only scans the default branch.CHANGELOG.md[Unreleased] has been moved under## [2.2.0] - 2026-09-18per the release process.Test plan
pytest tests/ -v— 35/35 passing (both PyMuPDF 1.27.2.3 and the pinned 1.28.2)mypy NullifyPDF.py privacy_core.py --ignore-missing-imports— cleanv2.2.0-beta.1..v2.2.0-beta.4), including a real scanner-produced PDF for the reconstruction fixes