Yes — a PDF is not a passive picture of a page. The format can embed JavaScript, declare actions that run automatically on open, ask the reader to launch external programs, and carry entire files inside itself. Every one of those capabilities announces itself in the file's object dictionaries, which means you can read what a document intends to do before any reader executes it. This page shows the manual check, what each marker means, and the automated version.
Scope, stated plainly. A structural check reads what the document asks its reader to do. It does not decode payloads, emulate JavaScript or match virus signatures — that is an antivirus's job, and the two checks answer different questions. "This bank statement carries JavaScript and an auto-run action" is a fact worth acting on regardless of whether any scanner recognises the payload.
1. How a PDF becomes active
Four dictionary keys turn a static document into one that does things:
-
/JavaScriptand/JS— embedded script, run by readers that support it (Acrobat does; most browser viewers heavily restrict it). -
/OpenAction— an action executed automatically the moment the document opens, before the user does anything. The classic delivery mechanism is an OpenAction pointing at embedded JavaScript. -
/Launch— a request to run an external program or open another file. Modern readers warn or refuse, but the intent is recorded in the file either way. -
/EmbeddedFile— a complete file carried inside the PDF, of any type, extractable by the reader.
Historically, PDF exploits have also lived in malformed structures — fonts, images and streams crafted to crash a specific parser. That is exactly why nothing in this page involves opening the file: every check below reads bytes without rendering them.
2. Check by hand with pdfid
The standard first-look tool is Didier Stevens' pdfid, a small Python script that counts the interesting names in a file without parsing — deliberately, so a malformed document cannot exploit the tool that inspects it:
A file that does things when opened
$ pdfid.py statement.pdf
/JS 1
/JavaScript 1
/OpenAction 1
/Launch 0
/EmbeddedFile 0
Non-zero counts for /JS, /JavaScript,
/OpenAction or /Launch are your cue to look
closer (Stevens' companion pdf-parser.py extracts the
actual objects) — or simply to not open the file outside a sandbox.
One caveat comes with the counting approach: pdfid matches name
strings anywhere in the raw bytes, so a PDF about JavaScript
— this page printed to PDF, say — can trigger counts from its own
prose. Position matters: a marker only means something in a real
dictionary-key position, which is what a structural parser checks.
3. What each marker means in a document workflow
Context decides severity. In a form distributed by a tax authority,
JavaScript is furniture. In a bank statement, a payslip or a proof of
address — documents that are generated, printed-equivalent artifacts —
there is no legitimate reason for any of the four markers to exist.
A statement that carries an /OpenAction is anomalous
twice over: something added active content, and it did so to a
document class that never carries it. The question "is this PDF
dangerous?" and the question
"was this PDF tampered with?"
overlap here: active content in a generated document is evidence
something other than the generator wrote to the file.
4. Benign causes — most active PDFs are legitimate
Interactive forms validate fields with JavaScript. Government and
insurance PDFs auto-fill dates on open. E-invoice standards
(ZUGFeRD, Factur-X) attach machine-readable XML as embedded files —
an /EmbeddedFile is the point of the format.
Portfolios carry whole documents inside a cover PDF. Which is why a
counter of markers is a screening tool, not a verdict: the finding is
"this document asks its reader to run things", and whether that is
normal depends entirely on what the document claims to be.
5. What an antivirus adds, and when to use one
A structural check tells you a payload exists; a scanner tries to tell you whether the payload is known to be hostile — signature matching, heuristics, sometimes detonation in a sandbox. For a file you personally received and intend to open, VirusTotal runs seventy-odd engines and is the right second step (with the obvious caveat that uploading confidential documents to a public multiscanner shares them). For a pipeline receiving customer documents, the two layers answer different business questions: the scanner protects your infrastructure; the structural check protects your decision — it flags the statement that carries script as not being the artifact a bank generates, whether or not the script matches any signature.
6. Screening uploads automatically
Tamperlens reports active content as one of its ten signal families:
active-content
fires on JavaScript, OpenAction, Launch actions and embedded files,
with the object positions as evidence. Detection is scoped to genuine
dictionary-key positions in parsed objects — a /JS that
merely appears inside a page's text does not fire, which removes the
false-positive class that raw byte counting carries.
The engine never executes or opens anything it inspects: documents are parsed in memory as bytes, no JavaScript runs, no embedded file is extracted, nothing is written to disk. The same POST returns the other nine families — revision history, metadata mismatches, signature coverage — so an upload pipeline gets the security screen and the tampering screen in one report.
Check a PDF now, free
Drop a file into the checker on the home page — no account, nothing stored, full report including active content. When you want it in your pipeline, an API key with 25 documents a month is free: see the API quickstart or create an account.
Tamperlens reports risk signals, not authenticity verdicts — and it is not an antivirus. Signals can have benign causes; combine them with your own decision logic.
Related reading
- The active-content signal family Exactly what fires it, its severity logic and its benign causes, in the field guide.
- How to detect if a PDF was edited Metadata, revisions, /ID pairs and signatures — the tampering side of the same inspection.
- How to tell if an image was edited or AI-generated The image counterpart: EXIF, editor fingerprints, generator metadata and container structure.
- API quickstart Auth, curl examples, the report JSON annotated, quotas and pricing.