feat(edit-pdf): secure redaction with fail-closed verify - #98
Merged
McanKul merged 8 commits intoSep 10, 2026
Merged
Conversation
…erify Add a distinct redact object and tool in Edit PDF. Save rasterizes only pages that have regions, burns fill/label into the page image, and fail-closes if source page-content probes remain. Leftover annotations, form values, and attachments are warned, not stripped. Checkpoint before review-bug fixes: rotate fill mapping, missing pdftoppm must not publish, and mixed-job verify probes.
Rasterize a /Rotate-zero sibling so fill lands on the covered words, fail closed when pdftoppm is missing, and probe only the redacted page stream. Preview uses a see-through hatch; the tradeoff banner shows only while the Redact tool is selected.
Address PR McanKul#98 review: scale the raster/fill by /UserUnit, search verify probes only on the redacted page, flatten widgets/annots before the burn, and ignore objectRotate on redact objects.
…cts; warn leftover /V and FileAttachment
McanKul
requested changes
Sep 8, 2026
McanKul
left a comment
Owner
There was a problem hiding this comment.
Thanks for the substantial work here. This now conflicts with the current development branch, so please resolve those conflicts first. Because redaction is security-sensitive, please also run and report the unchecked manual cases in the PR description—especially text recovery/search, rotated pages, mixed redacted/unredacted pages, forms/annotations, and original-file preservation.
…ripping Resources Redact inspector no longer shows Opacity (Save already burns opaque). applyUpdate ignores opacity on redact objects, matching objectRotate. collect_pages_tree_nodes only walks /Type /Pages so a typeless Kids leaf keeps its /Resources. Drop the narrating OVERWRITE keepGreen line.
McanKul
approved these changes
Sep 10, 2026
McanKul
left a comment
Owner
There was a problem hiding this comment.
Conflict resolved against the current development branch. The merged result passes the production build, 306 frontend tests, and 288 Rust tests locally.
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
Adds a distinct Redact tool in Edit PDF (not a black rectangle stamp). On Save, only pages with a redaction region are rasterized; fill/label are burned into the page image; leftover annotations, form values, and attachments are warned, not stripped. Verification fail-closes if source page-content probes remain. The original file is never overwritten.
Fixes #10.
How it works
kind: "redact"object + canvas tool (separate from Rectangle).pdftoppm→ paint fill/label on the bitmap → replace page contents with one image XObject./Rotatepages rasterize a sibling with rotate forced to 0 so the fill lands on the covered words.pdftoppmfails closed (RENDERER_MISSING); no white-page fallback.#34validate_staged_pdf/qpdf --checkstill gates dest publish. Redacted pages get a rebuilt content digest.Test plan
Owner-review run (2026-09-10) on
feat/10-secure-redaction@c7fbb06. Command:/Users/mustafa/AllTogether/offpdf-local-pipeline/tools/gates.sh. Localpdftoppm26.08.0 at/opt/homebrew/bin/pdftoppm(qpdf 12.3.2).developmentwas not merged into this branch (maintainer owns merge). PR is stillCONFLICTING/DIRTY. Conflict remainssrc-tauri/Cargo.tomlonly: keep bothtauri-plugin-single-instance(fromdevelopment) and[dev-dependencies] flate2(this branch) when that merge happens.Cargo.lockandpdf_engine/mod.rsauto-merge.This pass did not drive the Tauri desktop UI and did not open
manual-qa/files in a viewer. Cargo stream-absence is not claimed as “cannot be selected/searched in a viewer.” Checkboxes below are only for what this run actually executed.npm test/npm run typecheck/cargo test --libnpm test: 35 files, 288 passed.tsc --noEmitclean.cargo test --manifest-path src-tauri/Cargo.toml --lib: 275 passed, 0 failed, 0 ignored.cargo test --manifest-path src-tauri/Cargo.toml --lib edit_redact -- --test-threads=1 --nocapture: 34 passed, 0 failed; 0skip: pdftoppm not availablelines. Redact integ ran (not skipped).serde_roundtrip_redact_kind) / OVERWRITE (export_rejects_hard_linked_destination). Frontend:redactObject.test.ts,ObjectList.test.tsx,redactRotate.test.ts.Redact vs Rectangle distinct in toolbar and object list
kind: "redact"≠"rect"and the object-list label Redaction. Live toolbar click / hatch vs solid rect still needs a human in the app.Draw a redaction over selectable text, Save to a new file; covered text cannot be selected/searched; original unchanged
SECRET/OCRPROBE/ corpusHi; R-VERIFY fail-closed leaves source + existing dest; OVERWRITE hard-linked dest is refused and source bytes stay. That is stream-absence, not viewer Find.Two-page PDF: redact page 1 only; page 2 text still selects
unredacted_page_on_mixed_redact_job_keeps_source_stream(page 2KEEPME) and R-MIXDUP (identical siblingSECRETstays; no fail-close). “Page 2 still selects” is desktop.Rotated QA file (
qa-rotate90-trim-inside-crop.pdf): black patch sits on the word you coveredgeom-rotate-90/geom-crop-offset(dest/ImRinterior is the fill in unrotated MediaBox pixel space). Combined rotate+Trim⊂Crop visual is still desktop.Form/notes leftover: warning on the job; values not auto-stripped
qa-form-fill.pdf/ job card. Cargo this pass: R-ANNOT / R-FORM / R-FORMV / R-FATT — warn, do not strip. Job-cardJobUpdate.messageafter Save is desktop.Control: black Rectangle over the same word — text still selectable under the box
rectand does not rasterize; that is not the same as “text still selectable in a viewer.”Redact-tool banner only while Redact is selected; Output warning once a redaction exists
tool === "redact"and the Output alert onobjects.some(kind === "redact").