๐ก Mode 1: X-Ray Scanner (Live Extraction)
Splits your screen into a dual-pane workspace. On the left is the rendered PDF showing black redaction boxes, and on the right is a synchronized text terminal dumping the raw, unredacted text underneath.
โ๏ธ Mode 2: Layer Stripper (PDF-Lib Native)
Surgically traverses and scans all raw indirect PDF streams inside your browser. It swaps visual vector-drawing commands with non-paint operators, completely stripping the black masks to let you download a clean, naked document.
Case Study #1
The Taylor Wessing / Valve Catastrophe
Why did we build this? In October 2023, elite international law firm Taylor Wessing LLP attempted to redact sensitive GDPR documents for Valve Corporation using outdated, automated pipeline software (Aspose.PDF 20.8). They failed.
Instead of scrubbing the characters, they merely drew cosmetic black vector-graphic bars on top of the text. They created 830 pages of fake visual redactions, leaking thousands of unredacted private Steam account logins, emails, security logs, and telemetry directly to the public.
โ ๏ธ DIALOGUE WITH DR. PATRICK: We formally contacted Dr. Patrick (DPO/Partner) at Taylor Wessing LLP regarding this catastrophic leak. Their response confirmed that the firm is completely inadequate and has absolutely no intention of notifying affected data subjects, taking accountability, or warning the public. Therefore, the global community must proactively audit and sanitize their own files immediately. We highly advise against contacting or doing business with these clowns who are systematically unable to redact basic PDFs.
Don't make a multi-million dollar mistake like Taylor Wessing. Read our full forensic writeups and audit your files locally:
Visual Masking Vulnerability Explainer
Modern document redaction requires the complete destruction of sensitive character sequences within the PDF's internal content stream.
However, automated generators and manual editors often make a critical architectural error. Instead of deleting the target characters, they programmatically query their coordinates and draw a vector rectangle filled with solid black ink (using the re and f/F/b/B operators) on top of the text.
Because PDF text extractors and search engines parse raw character streams sequentially and ignore visual drawing layers, the "redacted" information remains 100% accessible. Anyone can copy the text, search it, or run simple tools like pdftotext to recover the hidden data instantly.
Audit Methodology
- Ingest File: Drag and drop your target PDF into the upload card.
- Metadata Scan: We scan the document headers. Red-flag signatures (like Aspose.PDF or Taylor Wessing LLP in the producer tags) will trigger immediate warnings.
- Text-Overlay Inspection: The browser renders the PDF page canvas and overlays the transparent, searchable text nodes exactly where they are situated in the document. By checking the highlighted layer, you can see if text is hidden beneath the visual black boxes.
Local Python CLI Auditing Tool
We provide a professional, offline-capable Python utility (decensor.py) that lets security researchers scan and clean documents locally on their own systems. No internet connection is ever used.
Setup Instructions
- Ensure you have Python 3 installed.
- Install PyMuPDF:
pip install pymupdf
- Execute the tool:
python decensor.py -i compromised.pdf -o unmasked.pdf
Core Python Implementation Code
import re, fitz
def strip_black_bars(input_path, output_path):
doc = fitz.open(input_path)
for page in doc:
for stream_id in page.get_contents():
stream_data = doc.xref_stream(stream_id)
text = stream_data.decode('latin-1')
modified_text, count = re.subn(
r'\bre\s+([fFbB]\*?)(?=\s|$)',
lambda m: f"re{m.group(0)[2:-len(m.group(1))]}n",
text
)
if count > 0:
doc.update_stream(stream_id, modified_text.encode('latin-1'))
doc.save(output_path, garbage=4, deflate=True, clean=True)
doc.close()
Browser-Based 100% Client-Side Engine
The web version performs the exact same structural sanitization directly in your browser. Using the pdf-lib library, the file is parsed, sanitized, and serialized in-memory. Because this is executed in your client sandbox, your files never leave your computer.
Core JavaScript Sanitizer Code
async function sanitizePdfClientSide(rawPdfBytes) {
const { PDFDocument, PDFName, decodePDFRawStream } = PDFLib;
const pdfDocInstance = await PDFDocument.load(rawPdfBytes);
const context = pdfDocInstance.context;
const indirectObjects = context.enumerateIndirectObjects();
for (let i = 0; i < indirectObjects.length; i++) {
const [ref, pdfObject] = indirectObjects[i];
if (pdfObject && typeof pdfObject.getContents === 'function' && pdfObject.dict) {
const dict = pdfObject.dict;
const type = dict.get(PDFName.of('Type'));
const subtype = dict.get(PDFName.of('Subtype'));
if (type === PDFName.of('Font') || subtype === PDFName.of('Image') || type === PDFName.of('Halftone')) {
continue;
}
try {
const rawData = decodePDFRawStream(pdfObject).decode();
const text = Array.from(rawData, byte => String.fromCharCode(byte)).join('');
const modifiedText = text.replace(/re(\s+)([fFbB]\*?)(?=\s|$)/g, 're$1n');
if (text !== modifiedText) {
const modifiedData = new Uint8Array(modifiedText.length);
for (let k = 0; k < modifiedText.length; k++) {
modifiedData[k] = modifiedText.charCodeAt(k) & 0xff;
}
const newStreamObj = context.flateStream(modifiedData);
const keys = dict.keys();
for (let k = 0; k < keys.length; k++) {
const key = keys[k];
if (key !== PDFName.of('Filter') && key !== PDFName.of('Length')) {
newStreamObj.dict.set(key, dict.get(key));
}
}
context.assign(ref, newStreamObj);
}
} catch (err) {
continue;
}
}
}
return await pdfDocInstance.save();
}