<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: PDFnoted</title>
    <description>The latest articles on DEV Community by PDFnoted (@hui_tian_5deae5180687cd80).</description>
    <link>https://dev.to/hui_tian_5deae5180687cd80</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4212997%2Fcf57dbf4-7308-4be4-beb6-607db5a026a8.png</url>
      <title>DEV Community: PDFnoted</title>
      <link>https://dev.to/hui_tian_5deae5180687cd80</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hui_tian_5deae5180687cd80"/>
    <language>en</language>
    <item>
      <title>Your PDF Has Three Layers. The One You Check Isn't the One That Leaks.</title>
      <dc:creator>PDFnoted</dc:creator>
      <pubDate>Thu, 29 Oct 2026 16:59:08 +0000</pubDate>
      <link>https://dev.to/hui_tian_5deae5180687cd80/your-pdf-has-three-layers-the-one-you-check-isnt-the-one-that-leaks-11d6</link>
      <guid>https://dev.to/hui_tian_5deae5180687cd80/your-pdf-has-three-layers-the-one-you-check-isnt-the-one-that-leaks-11d6</guid>
      <description>&lt;p&gt;A few months ago I sat in on a document review where someone had "cleaned" a contract before sending it to a partner company in another jurisdiction. They had opened it, deleted two paragraphs, drawn black rectangles over the pricing table, and saved it. It looked clean. It rendered clean.&lt;/p&gt;

&lt;p&gt;It was not clean.&lt;/p&gt;

&lt;p&gt;We pulled the text out in about ten seconds and read the deleted paragraphs back in full. The pricing table was still there, sitting underneath the rectangles, selectable in any viewer. And the file's metadata still carried the original author's name and the internal filename — which happened to include the client's name and the word "draft."&lt;/p&gt;

&lt;p&gt;Nobody in that room had done anything careless. They had done what almost everyone does, which is assume that what you can see on screen is what's in the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  A PDF isn't a document. It's a container.
&lt;/h2&gt;

&lt;p&gt;This is the mental model that fixes most of these mistakes. A PDF is closer to a zip archive than to a piece of paper. It has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A page layer&lt;/strong&gt; — the content stream that draws text and images. This is what you see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An annotation layer&lt;/strong&gt; — comments, highlights, form fields, and yes, those black rectangles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A metadata layer&lt;/strong&gt; — title, author, company, creation tool, modification dates, sometimes GPS coordinates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An object layer&lt;/strong&gt; — embedded fonts, images, attachments, and in many cases, previous versions of the file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most people only ever inspect the first one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer one: the rectangles
&lt;/h2&gt;

&lt;p&gt;The single most common false sense of security in document handling is the black box.&lt;/p&gt;

&lt;p&gt;When you draw a rectangle over text in a PDF editor, you are adding an annotation or a filled shape on top of existing content. The text underneath is untouched. It's still in the content stream. Ctrl+A, Ctrl+C, paste into a text editor, and there it is.&lt;/p&gt;

&lt;p&gt;Real redaction removes the text from the content stream itself. Not covers it — removes it.&lt;/p&gt;

&lt;p&gt;The test is simple and takes about ten seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdftotext document.pdf - | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"confidential"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns a hit on something you thought you'd removed, the redaction didn't happen. No amount of visual inspection will tell you this, because visually the file looks perfect.&lt;/p&gt;

&lt;p&gt;The same applies to white-on-white text. Some tools "delete" content by turning it white rather than removing it. It disappears on screen, stays in the file, and comes straight back the moment anyone extracts the text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer two: metadata
&lt;/h2&gt;

&lt;p&gt;Metadata is the layer nobody checks, because there's no visual cue that it exists.&lt;/p&gt;

&lt;p&gt;Word, Google Docs, and most PDF exporters embed author name, company, creation and modification timestamps, and the original filename. Filenames are the underrated leak here. &lt;code&gt;Acme_NDA_v3_FINAL_draft_internal.pdf&lt;/code&gt; tells a counterparty more than most people intend to.&lt;/p&gt;

&lt;p&gt;On mobile-generated PDFs and some scanners, you can also get GPS coordinates.&lt;/p&gt;

&lt;p&gt;Check it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdfinfo document.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or in most viewers, File → Properties. If you see a name that shouldn't be there, it shouldn't be there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer three: what the file remembers
&lt;/h2&gt;

&lt;p&gt;This is the layer that surprises even technical people.&lt;/p&gt;

&lt;p&gt;PDFs support incremental updates. When a file is edited and saved, the change can be appended to the end of the file rather than rewriting it. The old bytes stay. "Save as" does not reliably strip them, and neither does most PDF editing software by default.&lt;/p&gt;

&lt;p&gt;What this means in practice: a file can contain an earlier version of itself, and a determined recipient can recover it.&lt;/p&gt;

&lt;p&gt;A reasonable check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strings document.pdf | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"internal&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;draft&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;confidential"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crude, but it catches the overwhelming majority of real-world cases. If you want to be thorough, compare the file's declared size and object count before and after a proper flatten-and-rebuild pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more across borders
&lt;/h2&gt;

&lt;p&gt;Domestically, a metadata slip is usually embarrassing. Cross-border, it can be a compliance event.&lt;/p&gt;

&lt;p&gt;Data protection reviews — GDPR, PIPL, whatever framework applies — tend to focus on what's intentionally in the document. Consent, lawful basis, transfer mechanism, retention. Those are the right things to focus on. But the leak that's hardest to notice is rarely the visible content. It's the author field, the filename, the deleted-but-not-deleted paragraph, the previous version sitting in the file.&lt;/p&gt;

&lt;p&gt;And unlike a policy failure, this one is trivially preventable. It's a five-minute check.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist that takes five minutes
&lt;/h2&gt;

&lt;p&gt;Before any sensitive PDF leaves your organization:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extract the text and search it.&lt;/strong&gt; &lt;code&gt;pdftotext file.pdf - | grep -i "confidential\|internal\|draft"&lt;/code&gt;. If it hits, the content is still there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the metadata.&lt;/strong&gt; &lt;code&gt;pdfinfo file.pdf&lt;/code&gt;, or File → Properties. Clear anything you wouldn't want a counterparty to see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redact properly, don't cover.&lt;/strong&gt; Use a tool that removes content from the stream, then verify with step 1. Never trust the visual.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rebuild, don't just save.&lt;/strong&gt; Flatten the file so incremental history is dropped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-run step 1 after every edit.&lt;/strong&gt; This is the step people skip, and it's the only one that actually proves anything.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this requires buying anything. Every command above ships with poppler, which is a one-line install on every platform. The hard part isn't the tooling — it's getting into the habit of verifying rather than assuming.&lt;/p&gt;

&lt;p&gt;The uncomfortable version of this advice: if you've never run step 1 on a file you considered clean, it's worth doing once. It's a five-minute experiment and it tends to change how teams handle documents.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>Three kinds of PDF watermarks, and why most removers only handle one</title>
      <dc:creator>PDFnoted</dc:creator>
      <pubDate>Wed, 28 Oct 2026 13:13:54 +0000</pubDate>
      <link>https://dev.to/hui_tian_5deae5180687cd80/three-kinds-of-pdf-watermarks-and-why-most-removers-only-handle-one-1lld</link>
      <guid>https://dev.to/hui_tian_5deae5180687cd80/three-kinds-of-pdf-watermarks-and-why-most-removers-only-handle-one-1lld</guid>
      <description>&lt;p&gt;"Just remove the watermark" is one of those tasks that sounds trivial until you open the file. I've lost count of how many times someone has told me a tool "doesn't work", when the real issue was that they were dealing with a different kind of watermark than the tool was built for.&lt;/p&gt;

&lt;p&gt;There are three, and they live in completely different places in the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type 1: an annotation
&lt;/h2&gt;

&lt;p&gt;This is the easy one. The watermark is an object in the page's &lt;code&gt;/Annots&lt;/code&gt; array — the same mechanism as a sticky note or a highlight. It's drawn on top of the page, but it isn't part of the page content.&lt;/p&gt;

&lt;p&gt;Anything that can edit annotations can delete it. On the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# see what annotations exist&lt;/span&gt;
pdfinfo &lt;span class="nt"&gt;-meta&lt;/span&gt; file.pdf
qpdf &lt;span class="nt"&gt;--show-object&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;trailer file.pdf | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most "PDF watermark remover" tools stop here. Which is fine, as long as that's what you actually have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type 2: a Form XObject
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. The watermark is a reusable content object referenced from the page's resource dictionary and painted via a &lt;code&gt;Do&lt;/code&gt; operator in the content stream. It &lt;em&gt;is&lt;/em&gt; page content now — there's no annotation to select and delete.&lt;/p&gt;

&lt;p&gt;Removing it means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;finding the XObject in &lt;code&gt;/Resources /XObject&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;following the mapping through &lt;code&gt;/Form&lt;/code&gt; and the &lt;code&gt;Matrix&lt;/code&gt; × &lt;code&gt;Do&lt;/code&gt; CTM combination (a naive search misses nested forms)&lt;/li&gt;
&lt;li&gt;deleting the object &lt;em&gt;and&lt;/em&gt; its reference&lt;/li&gt;
&lt;li&gt;writing the stream back and clearing the stale filter keys (&lt;code&gt;Filter&lt;/code&gt;, &lt;code&gt;DecodeParms&lt;/code&gt;, &lt;code&gt;F&lt;/code&gt;, &lt;code&gt;FFilter&lt;/code&gt;, &lt;code&gt;FDecodeParms&lt;/code&gt;, &lt;code&gt;DL&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2 is why simple text-matching approaches fail. The content stream is compressed, the object may be nested several levels deep, and the positioning math is composed at draw time rather than stored as coordinates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type 3: baked into a scan
&lt;/h2&gt;

&lt;p&gt;If the PDF is a scanned image, the watermark is pixels. No PDF-level tool will remove it — that's image editing, and results are usually ugly.&lt;/p&gt;

&lt;p&gt;Worth checking before you try anything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdftotext file.pdf - | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns almost nothing, you're looking at a scan. Stop trying PDF tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to tell which one you have
&lt;/h2&gt;

&lt;p&gt;Quick heuristic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdfinfo file.pdf | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; pages
pdftotext file.pdf - | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;        &lt;span class="c"&gt;# text present?&lt;/span&gt;
qpdf &lt;span class="nt"&gt;--qdf&lt;/span&gt; &lt;span class="nt"&gt;--object-streams&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;disable file.pdf out.pdf
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"/Annots&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;/XObject&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;/Watermark"&lt;/span&gt; out.pdf | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;/Annots&lt;/code&gt; shows up with a &lt;code&gt;/Subtype /Watermark&lt;/code&gt; entry, it's type 1. If you find &lt;code&gt;/XObject&lt;/code&gt; forms that don't correspond to visible images, it's probably type 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually matters
&lt;/h2&gt;

&lt;p&gt;Whatever you remove, verify afterwards. A lot of tools — including some well-known paid ones — "remove" content by drawing an opaque rectangle over it. The underlying text is untouched and trivially recoverable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdftotext cleaned.pdf - | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="s2"&gt;"confidential&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;salary&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;身份证"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that prints anything, the file is not clean. I've seen contracts go out to counterparties with the "redacted" salary figure still in the text layer, because whoever prepared it trusted the visual preview.&lt;/p&gt;

&lt;p&gt;For type 1 and 2, a proper removal should survive this check. For type 3, it won't — and that's the honest signal that you need a different approach.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I work on &lt;a href="https://pdfnoted.com" rel="noopener noreferrer"&gt;PDFnoted&lt;/a&gt;, which does object-level watermark removal and re-scans the file after cleaning. The three-type distinction here is tool-independent though — knowing which one you have will save you most of the time regardless of what you use.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cli</category>
      <category>software</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
