Reference: Detect an edited PDF
If you searched "how to tell if a PDF was edited", the first advice you will
find is to open the document properties. Compare CreationDate
against ModDate: if they differ, the file was edited. That check
fails in both directions, and this post shows the failure with numbers instead
of opinion. In the honest direction, modified files are ordinary. 48 of 190
PDFs that US banks publish on their own domains carry more than one revision,
and one carries 24. In the dishonest direction, both dates are strings that any
metadata tool rewrites in one command.
The date checks that survive measurement are the impossible ones. Modification before creation, dates in the future, timezone offsets no place on Earth uses, syntax no generator writes, and the file's two metadata stores disagreeing with each other. The evidence of editing, when it exists at all, lives in the revision structure. The honest position is printed here twice: a divergent date pair alone proves nothing, and a matching pair proves nothing either.
What the advice actually tests
A PDF's Info dictionary carries two timestamps. CreationDate
is when the document was created; ModDate is when the file
was last written. The advice on page one of that search treats a
difference between them as the tell. Read literally, it is testing
whether a file was ever saved more than once, or by a tool that stamps
the two fields from different clocks. That is not a fraud question. It
is barely a question at all.
The failure has two directions, and the second is worse than the
first. Direction one: honest documents move their ModDate
constantly, so the check flags the innocent. Direction two: both
fields are plain strings inside the file. A forger who has just rebuilt a
statement in an editor sets ModDate equal to
CreationDate with one command. The same stroke that makes the
fake look pristine makes an honest, twice-saved fee schedule look suspicious
by comparison. A check that a careless amateur trips and a careful forger
passes is selecting for carelessness, not for fraud.
Honest documents are modified documents
The first population: 190 PDFs downloaded from 24 US financial institutions' own domains, collected 2026-07-26 and parsed with this engine. The corpus teardown publishes them in full, with a SHA-256 provenance record per file. 142 of the 190 are single-revision. 48 are not, and one document carries 24 revisions.
These are fee schedules, agreements and disclosures that the banks themselves serve to the public. So the point lands cleanly: a quarter of the genuine population was modified after it was first written, with no fraud anywhere in sight. A bank updates a disclosure for a new year, a compliance team re-saves a form, an accessibility processor appends its pass. Every one of those writes is a modification after creation.
Court records say it louder. When this engine's noise floor was measured on US public documents, one signal that inferred re-writing fired on 240 of 255 authentic court filings. The reason: the filing system itself rewrites every document that passes through it. A modified file is what institutional document handling produces at scale.
And a detail from the other side of the ledger, found while testing
what defeats each signal (engine 1.13.4): even removing
metadata is a write. exiftool -all= does not scrub a file
in place; it appends a new revision whose trailer omits the Info
dictionary and marks the old metadata objects free. The original bytes, dates
included, stay in the file underneath. A stripped ModDate is not
an absent fact. It is one more layer of history.
My own engine made the same inference, and the measurement said no
The reason I trust this critique is that I ran it against my own product
first. Through engine 1.16, two Tamperlens signal families made the ModDate
mistake in structural clothing. Each inferred a change after creation from one
artifact: a divergent trailer /ID pair in one case, a second
subset of the same font in the other. Neither checked
whether the file had ever been written more than once.
On curated corpora they looked reasonable. So on 2026-08-04 I measured two populations nobody curated. One is the Library of Congress "1000 .gov PDF dataset" (985 documents). The other is a random sample of the Brazilian web, drawn from evenly spaced Common Crawl index blocks (743 documents, 421 hosts). 1,728 documents, zero parse failures.
| signal, before engine 1.17.0 | fired on US .gov | fired on BR web | of those firings, on files written exactly once |
|---|---|---|---|
id-inconsistency | 44.7% | 36.7% | 83.4% and 88.6% |
font-anomalies | 6.0% | 76.3% | 27.1% and 92.5% |
incremental-updates (the control) | 8.1% | 5.1% | 0.0%, by construction |
The control row is the whole argument. incremental-updates is
derived from the revision count, so it cannot disagree with the parser about
whether a second write happened. The other two were inferring a second write
from artifacts that ordinary producers emit on the first one. Distiller
writes several subsets of one typeface in a single pass, and every document
merge does the same to honest files.
Since engine 1.17.0 both families are gated on
revisions > 1. On a single-revision file they still report, at
severity info, with the divergent /ID pair or the
conflicting subset prefixes intact in the evidence. What they no longer do is
claim a history the file's own structure contradicts. The full accounting,
including what the recalibration did to the false-positive rate, is in
the measurement post.
ModDate ≠ CreationDate is the same category of inference with
less structure behind it. It reads two strings and concludes a history. The
lesson the measurement taught applies here with nothing changed but the
artifact. Something honest producers emit in ordinary workflows cannot, on
its own, carry a claim of later editing.
The four date checks that survive
None of this means dates are worthless. It means the useful questions
are the ones an honest workflow cannot answer wrong. The engine's
date-anomalies family ships exactly four, and a later
ModDate is deliberately not among them:
| check | why it survives |
|---|---|
| Modification before creation | No correctly running generator produces it. It means a timestamp was set by hand or by something outside the pipeline that wrote the document. |
| A date in the future | Clock skew explains minutes. It does not explain more than a day, which is where the threshold sits. |
| An impossible timezone offset | No real timezone exceeds ±14:00. An offset beyond that was typed, not stamped. |
| Malformed date syntax | Generators emit the PDF date grammar mechanically. A string that fails both the PDF and ISO-8601 syntax was edited by something that does not write dates for a living. |
A finding here is severity medium, or low when the
only problem is syntax. Even an impossibility has non-fraud explanations: a
batch tool with a broken clock is not a forger.
One engineering note matters for reproducibility. "In the future" requires a clock, and since engine 1.13.3 that clock is read exactly once per inspection and can be pinned by the caller. So identical bytes produce an identical report. A forensic check that answers differently on Tuesday is not a check.
The fifth check: a PDF carries its dates twice
Nearly every modern PDF stores its metadata in two places: the Info dictionary and an embedded XMP packet. A single tool writing a file once fills both consistently. Consumer editors very often update one and leave the other stale, and nothing in the format synchronizes them.
The engine's metadata-mismatch family compares the two stores
field by field, with a one-minute tolerance on dates because the stores round
differently. A plain disagreement is medium: multi-stage
publishing pipelines produce it legitimately. It escalates only when the
divergence is what reveals a consumer editor. One store names the tool and
the other never recorded it, which is a stronger fact than either date on its
own. The hand-run version of this comparison, with the grep to find the XMP
packet, is in
the metadata forensics guide.
Where "was it edited" actually gets answered
PDF supports incremental update. A tool can append new objects, a new cross-reference section and a new trailer to the end of a file, instead of rewriting it. The old bytes stay. That makes the revision count a structural fact about how many times the file was written, not a string anyone typed. It is the fact the two recalibrated signals now defer to. You can see the raw markers yourself:
grep -abo startxref statement.pdf
One caveat the raw grep misses. A linearized "fast web view" file writes two cross-reference sections in a single save, and this engine counts that pair as one write. So a pristine export from Word or a browser does not read as edited.
When the count is genuinely above one, the engine reports what each revision
touched, and the severity follows the content rather than the count. Appended
revisions are medium, because they have entirely benign readings:
a signature, a filled form, an annotation. high comes only when a
later revision replaced page content that an earlier revision wrote.
A page that says something different from what it said when it was generated
is the thing every other check only gestures at.
The per-revision accounting has its own post, down to the object counts, and the guide on incremental update versus full rewrite covers the benign causes.
A whole-file rewrite flattens all of this. Open a statement in an editor, change a number, export a new PDF, and the result is a clean single-revision file whose dates say whatever the exporter stamped. Its absence of history is not evidence of authenticity. That is why no output of this engine ever says "authentic": a quiet report describes what the file carries, not what happened in the world. And recall, meaning how often forgeries are caught, is still unmeasured here, because no public corpus carries labelled tampered documents. Signals, not verdicts.
So how do you tell if a PDF was edited?
Here is the order the evidence deserves, not the order the advice usually
gives it. Count the revisions first, because the revision structure is the
only part a forger cannot retype in place. Compare the two metadata stores
second, because a disagreement is a fact about the tools that touched the
file. Read the dates third, and only for impossibilities: order, future,
timezone, syntax. Treat ModDate ≠ CreationDate on its own as
what the measurement above says it is, a description of ordinary document
life. The full hand-run walkthrough, reader by reader and command by command,
is the reference guide:
detect an edited PDF.
Check a PDF: free, no account, analysed in memory and discarded with the response. The report runs every check in this post, including the revision history. It also tells you when a finding has benign readings, which is most of the time.