DEV Community

thomasmoore5082
thomasmoore5082

Posted on

True Lease Redaction — Removing PDF Text Instead of Hiding It

Short answer: drawing a black rectangle over text changes what a reader sees, while true redaction removes the sensitive content from the document before a sanitized artifact is released. For scanned property documents, that distinction is the release boundary: OCR may make a tenant name, signature, bank detail, or access code searchable even when the page looks safely covered. The defensible design is to identify sensitive regions, remove both the visible pixels and any associated text representation, rebuild the output, and then test the released file as an adversary would.

Treat redaction as destructive data transformation, not annotation. A black box can be useful during review, but it is not proof that the underlying content is gone.

How can a black box still disclose the text?

PDF is a container for page content, not a flattened screenshot. A page can combine scanned images, text objects, annotations, forms, and other structures described by the PDF specification. A rectangle drawn above a text object may merely alter the painting order. The covered text can remain in the file, available to selection, copy and paste, search, extraction software, or a later edit that removes the rectangle.

Scanned leases add another layer. The original page may be an image, but an OCR stage can add a machine-readable text representation so staff can search addresses and clauses. If a redaction workflow covers pixels yet carries that OCR text into the released PDF, visual inspection reports success while extraction reports a breach. If it deletes text but leaves the source pixels untouched, removing or misplacing an overlay can expose the scan. Both representations matter.

Consider a bounded incident scenario: a property manager receives a 40-page scanned lease package, runs OCR, and prepares a copy for a maintenance dispute. Pages 7 and 31 contain sensitive tenant data. The reviewer places opaque rectangles over those areas, exports the file, and sees clean black regions in the viewer. The invariant is still violated because release approval was based on rendering alone; no independent check established that the covered strings and pixels were absent from the deliverable.

That is the incident lesson. A redaction is complete only when the released artifact no longer contains the protected information in recoverable form.

The release pipeline needs two independent views

The practical architecture separates detection, transformation, and verification. Detection produces regions and a reason for each proposed removal. Transformation applies those decisions to a new artifact. Verification then approaches that artifact without trusting the transformer's internal success response.

For a scanned-document pipeline, retain the restricted original under its own access policy and generate a derived release candidate. OCR the source for discovery, map findings back to page coordinates, and require review for ambiguous matches. Then remove content intersecting approved regions, rasterize affected pages when necessary, and build a fresh PDF rather than layering marks onto the source. A rasterized page trades away selectable text and may increase render and storage cost, but it sharply narrows the number of PDF structures that can carry a secret. If searchability is required, OCR the already-redacted pixels to create a new text representation; never reuse the pre-redaction OCR output.

Verification needs both a visual pass and a structural pass. Render every released page and check that the intended regions are covered without deleting unrelated clauses. Separately extract text from the candidate and search for the exact values and normalized variants supplied by the case record. Fail closed on parser errors, missing pages, unexpected page-size changes, or a mismatch between the approved region count and the applied region count.

One warning deserves its own line.

Do not log the secret you are trying to remove.

Store a case-scoped digest or opaque finding ID in audit events, alongside page number, region, rule revision, reviewer decision, and output digest. Logs should prove which decision path ran without becoming a second copy of tenant data.

Fidelity and render cost are an SLO decision

Property records make blanket rasterization tempting because it creates a simpler release surface, but the operational cost is real: every page must be rendered, the resulting file can grow, small type may degrade, and downstream users lose clean text selection unless the sanitized pages are OCRed again. Selective object removal preserves more fidelity and usually avoids rerendering unaffected content, yet it demands a parser and writer capable of handling every structure admitted at ingestion. Unknown structures turn into an on-call problem.

I would make that trade with explicit objectives rather than a vague promise that redaction is "secure." Define a correctness SLO first: every approved sensitive finding must be absent from visual and extracted representations of the release candidate. Then define latency and capacity targets from the actual workload: pages per case, peak cases per minute, page dimensions, OCR concurrency, render memory, and retry rate. A queue absorbs bursts, but it does not erase CPU-seconds per page.

The capacity-planning reflex matters here. If one worker can safely process r pages per second at the chosen resolution and the peak arrival rate is p pages per second, the theoretical concurrency floor is ceil(p/r). That is not the production allocation. Add measured headroom for long documents, OCR retries, corrupted inputs, and verification, then load-test the full distribution rather than one tidy lease.

Approach Fidelity Compute and storage Failure surface Appropriate boundary
Visual overlay only Preserves appearance Low Underlying pixels or text can remain Review markup, never final release
Selective content removal Often preserves search and vectors Moderate Complex page structures and coordinate errors Constrained, well-tested input profiles
Rasterize sanitized pages Can soften text or alter appearance Higher and resolution-dependent Missed regions, stale OCR, render defects High-assurance release with visual comparison
Rebuild from approved fields Deliberately changes layout Work shifts to templates and validation Omitted context or mapping errors Standardized disclosures, not faithful copies

Choose the simplest representation that meets the fidelity requirement and that the team can verify under its error budget. A legal copy that must preserve exact pagination may justify selective removal plus aggressive structural testing. A disclosure whose meaning matters more than its original typography may be safer as a rebuilt document.

A preventative gate in Go

The release service should accept evidence from independent stages and reject incomplete evidence. The following Go example models the gate rather than PDF parsing; concrete parsing belongs behind separately tested adapters because a few string searches cannot establish that a binary document is sanitized.

package redaction

import (
    "errors"
    "fmt"
)

type Evidence struct {
    SourcePages       int
    OutputPages       int
    ApprovedRegions   int
    AppliedRegions    int
    VisualCheckPassed bool
    TextCheckPassed   bool
    ParserWarnings    []string
    OutputDigest      string
}

func ApproveRelease(e Evidence) error {
    if e.SourcePages < 1 || e.OutputPages != e.SourcePages {
        return fmt.Errorf("page count mismatch: source=%d output=%d", e.SourcePages, e.OutputPages)
    }
    if e.ApprovedRegions < 1 || e.AppliedRegions != e.ApprovedRegions {
        return fmt.Errorf("region mismatch: approved=%d applied=%d", e.ApprovedRegions, e.AppliedRegions)
    }
    if len(e.ParserWarnings) != 0 {
        return fmt.Errorf("unresolved parser warnings: %d", len(e.ParserWarnings))
    }
    if !e.VisualCheckPassed || !e.TextCheckPassed {
        return errors.New("independent verification incomplete")
    }
    if e.OutputDigest == "" {
        return errors.New("output digest missing")
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

The deliberately strict checks expose several useful operational states. A document with zero approved regions should follow a separate no-redaction release path instead of smuggling a sentinel value through this one. Parser warnings quarantine the job. The digest binds the approval record to the exact bytes released, preventing a verified candidate from being confused with a later export.

Instrument counts and durations, not tenant values: pages processed, findings proposed, regions approved, candidates rejected, parser-warning classes, OCR retry counts, and time spent in each stage. Alert on sustained rejection-rate changes and queue-age SLO burn. A sudden fall to zero findings can be more alarming than a spike because it may mean detection silently stopped working.

Build, buy, or constrain the input?

The useful comparison is ownership, not a feature checklist. Managed processing can reduce implementation time, but the platform team still owns data classification, retention, verification, incident response, and the behavior of every format accepted at the boundary. Self-hosting can improve control over data location and version rollout while moving parser patching, capacity, and pager duty onto the team. Building a PDF engine is a much larger commitment than building a policy service around a mature engine.

Decision What the team keeps What it takes on Exit test
Managed transformation Policy, review, release verification Provider boundary, integration failure modes, lock-in Can cases and audit evidence move without re-reviewing every document?
Self-hosted engine Runtime control and upgrade timing Patching, scaling, malformed-file handling, on-call load Can another conforming engine satisfy the same adapter contract?
In-house implementation Full behavior and roadmap control Specification coverage, security research, fuzzing, long-term maintenance Is PDF internals work truly a core capability?
Constrained ingestion A smaller test matrix Conversion rules and fidelity exceptions Can rejected documents follow a documented manual path?

My default architectural preference is a narrow internal contract: input bytes and an approved region set go in; sanitized bytes, a digest, and verification evidence come out. Keep policy and audit records outside the PDF engine. That boundary makes substitution possible without pretending substitution is free.

The limitation of this approach is equally concrete: it is unsuitable as an automatic release path for every document. Digitally signed PDFs require a deliberate signature policy because creating a changed artifact changes what can be validated. Documents whose evidentiary value depends on original pixel characteristics may need a disclosure generated from approved fields rather than a modified facsimile. Handwriting, rotated scans, marginal notes, and low-contrast photocopies also demand human review because OCR output is evidence for locating content, not proof that every sensitive mark was found. The alternative in those cases is a quarantined manual workflow or a rebuilt disclosure, accepting lower layout fidelity in exchange for a smaller disclosure surface.

The final decision rule is plain: overlays are review UI; sanitized derivatives are release artifacts. Preserve the restricted source, bind approval to the derivative's digest, and permit release only after independent visual and structural checks agree. The extra render cost is justified where it buys a smaller, testable disclosure surface; where fidelity forbids rasterization, pay instead for stricter input constraints, parser coverage, and a larger verification budget.

Sources

Top comments (0)