Reference: Fraud signals field guide
If you are looking for prompt injection detection for PDFs, the products you will find first are guardrails, and the guardrails I could verify take a string. Prompt injection is text planted in a document so that a language model reads it as an instruction. I read seven vendors' API documentation on 2026-08-03 and re-read it on 2026-08-25 for this post. Six accept only text. The seventh accepts the file itself, but documents nothing beyond screening its text.
That matters because a PDF reaches every one of them through an extraction step. Extraction deletes exactly the evidence that separates a sentence a human saw from the same sentence painted white on white at 0.4pt. On benchmarks where that distinction is the whole question, text-only detectors measure F1 0.125 to 0.390, on a scale where 1.0 is perfect. Tamperlens works on the other side of the flattening, at the file layer. This post is precise in both directions: what that layer covers today, and the measured reason its flagship signal is not shipped yet.
Seven vendors' docs, read twice, three weeks apart
Each row below is what the vendor's own documentation says its screening endpoint accepts, not what a sales page implies. Four rows were re-read on 2026-08-25; the other three stand as verified on 2026-08-03. Nothing in the re-read had changed.
| vendor | documented input | last read |
|---|---|---|
| Lakera Guard | POST /v2/guard takes messages with string content. No file parameter. | 2026-08-25 |
| Azure AI Content Safety Prompt Shields | The documents parameter is an array of strings: "Each element in the array is expected to be a string." | 2026-08-25 |
| AWS Bedrock Guardrails | ApplyGuardrail, per the Bedrock API Reference, takes content blocks; guardrailCoverage reports exactly textCharacters and images. | 2026-08-25 |
| Google Model Armor | The outlier: accepts PDF and Office files up to 4 MB and screens them for prompt injection. No structural or rendering analysis appears anywhere in the docs. | 2026-08-25 |
| Cloudflare AI Gateway Guardrails | Prompts and responses. No file input. | 2026-08-03 |
| Pangea AI Guard (now CrowdStrike Falcon AIDR) | "Plain text or an array of messages." | 2026-08-03 |
| HiddenLayer AIDR | No file input documented. Its Model Scanner does parse files, but they are model artifacts: a different format solving a different problem. | 2026-08-03 |
The Model Armor row deserves its asymmetry spelled out. Its overview page was updated on 2026-08-21, four days before my re-read, and it really does ingest document bytes: PDF, DOCX, PPTX, XLSX. That is genuinely further than anyone else in the table goes. What the documentation describes doing with those bytes is screening their content for injection, sensitive data and malicious URLs. Nothing on the page mentions render modes, fill colours, font sizes or any other fact about how the text is painted. Absence from documentation is weaker evidence than presence in it, and I am reporting exactly what the page says and no more.
Two more precision notes, because this table is the entire width of the claim in the title. Vendors I could not verify from primary documentation are not in it, and there are many. And the file layer is not empty elsewhere. At least one commercial product in the recruiting vertical sells file-level hidden-text detection for resumes, and a handful of small open-source tools read PDF rendering instructions for this purpose. The claim is about the guardrail category as documented, not about the world.
None of this is a defect in those products. A guardrail classifies content at the prompt boundary. It catches what a file parser never can: the visible role-play, the conversation mockup, the encoded payload typed straight into the chat box. Reading a file's rendering instructions was never in its contract. The gap in this post is between layers, not between vendors.
What the flattening deletes
Between the uploaded PDF and the guardrail sits an extraction step: pdftotext, a RAG ingestion library, a document intelligence API. After it, two things become the same string. "Ignore all previous instructions" printed visibly in an article about prompt injection, and the same sentence in invisible render mode at 0.4pt.
The facts that separated them exist only in the file. Text render mode
Tr 3, fill colour against background, glyph geometry, paint
order, position relative to the visible page box. The flattening does not
carry any of them.
The measured consequence is not subtle. A USENIX Security study of about 200,000 real resumes found roughly 1% carried hidden injections. Over 90% of those were not instruction-shaped at all: fabricated skills and copied job requirements, not "ignore previous instructions". The study measured text-based detectors on that population at F1 0.125 and 0.018, while its own document-aware detector reached 86.1% precision.
Their explanation is this post's thesis: the signal lives in visual properties of the PDF, orthogonal to textual semantics. The CrackedPDFs benchmark, 29,322 PDFs, measures the same divide: text-only screening at F1 0.390, a document-aware detector at 0.960.
The standards bodies name the problem at the same layer. OWASP LLM01:2025 lists files as an indirect injection source and concedes injections need not be human-visible. NIST AI 100-2e2025 gives indirect prompt injection its own taxonomy entry, and states that current mitigations do not offer robust protection.
One term needs disambiguating, because it already means something else in this market. Several string-layer tools advertise "invisible text detection". What they detect is non-printable Unicode: zero-width characters, tag characters, the steganographic tricks that hide inside a string. That is real and worth doing. It is also structurally incapable of catching white-on-white, sub-visible font sizes or covered text. Those are made of perfectly ordinary printable characters, and their invisibility is a rendering fact, not a character property.
What ships here: the carriers with no benign population
Since engine 1.13.5, two signal families run the engine's injection
classifier over the carriers where nothing legitimate ever writes an
instruction to a model. They are document-injection-markers for
PDF and office-injection-markers for OOXML, the ZIP-based format
behind .docx and .xlsx.
The carriers are the file's own metadata slots. In a PDF: Info-dictionary
values including custom keys, the XMP packet's text nodes, annotation
contents, and embedded-file names and descriptions. In an Office file:
document properties, Word comment text, and the text of hidden
w:vanish runs.
That sentence about legitimacy is the whole false-positive argument. There is
no honest reason for an imperative addressed to an AI to sit in
/Keywords, and extraction pipelines routinely surface these
fields to the model.
The classifier is a versioned lexicon, a fixed word list rather than a model call, because the engine's contract is that identical bytes produce an identical report. It fires on two independent cue categories, or on one of the two that have no benign reading: a chat-template delimiter, or an explicit address to an AI.
The normalizer runs first, and it is the load-bearing half. It applies NFKC,
the Unicode normalisation that collapses lookalike encodings, strips
zero-width and bidirectional-control characters, and folds confusables and
accents. Without that step, ignore with a zero-width
joiner in it defeats the lexicon, and that is the first thing anybody tries.
The regression fixture I am proudest of is a paper about prompt injection. Its title, keywords and abstract all use the vocabulary, and the families stay silent on it.
A scanner's report is a string an agent will read
There is a second place the string layer bites, and it is the scanner's own output. An inspection report gets fed to a model, by the customer's pipeline or by our own MCP server. So a report that quoted a recovered payload into its prose would deliver the injection with a trusted label on it.
The engine's echo-back contract has been in force since 1.13.5. Payload text
lives only in the evidence block, truncated and stripped of control
characters, marked payloadIsUntrusted. The MCP server elides it
unconditionally and summarises cue categories and counts instead. And
?redact=payload on /api/v1/inspect gives REST
integrators the finding without the bytes.
The post on
that contract also records the humbling part. An adversarial review of
release 1.35.0 found the compare tool returning its report raw. An injection
planted in a /Title rode out through a side door the inspect
tool had closed. Fixed, with a regression test that drives the attack.
For an agent doing document intake, the useful order is inspect, then read. Triage before ingestion, a full inspect before trusting, and the injection families checked before any of the document's text enters a context window.
The flagship signal this post does not get to claim
The carrier everyone thinks of first is concealed page text: white on white, invisible render mode, sub-visible sizes, text under an opaque rectangle. The parser retains all of it, with the mechanism and the geometry. And the signal that would fire on it is deliberately not shipped. Before building it, I measured what the obvious detector would do to an honest population: 633 authentic public PDFs, court filings, government contracts, bank documents, templates.
| authentic population, 2026-08-03 | an undiscriminating detector fires on |
|---|---|
| all 633 PDFs | 10.6% |
| US court filings | 3.9% |
| bank documents, US and BR | 36.0% and 36.4% |
| all 633, after excluding layout concealment | 5.4% |
| bank documents, after the same exclusion | 16.0% and 24.2% |
The distribution is inverted from what everyone assumes. The scanned-OCR problem is real but filterable. The born-digital one is worse, because professional layout uses concealment as a design technique: printer marks outside the crop box, form labels the colour of their fields, watermarks under content. Authentic bank statements, the segment this product sells to, are the worst genre in the corpus.
A signal shipped on top of that base rate would describe the population, not the risk. So it waits on the discriminator that can excuse a printer mark: near-duplication of the concealed text against the visible text. That retention shipped in engine 1.17.0; the judgement threshold has not. Nobody publishes this number, as far as I can find, because publishing it requires an honest-population corpus and a reason to look.
What no file layer can do
- Visible instructions. Text a human can read is a document, not a concealment. If a pipeline obeys visible text, no file scanner fixes that.
- Plain lies. A resume that fabricates skills in ordinary 11pt type carries no rendering anomaly. Semantic judgement is the model layer's job, which is one reason guardrails still belong in the stack.
- Text inside pixels. An instruction rendered into a raster image and read by the customer's OCR or vision model is not in the content stream. This engine does not decode pixels, by design.
- Everything after ingestion. Retrieval poisoning, tool-call chaining, memory attacks: different layer, different product.
- Encrypted documents. The families are suppressed there, and the report discloses the suppression rather than letting silence read as safety.
- An adversary who reads the lexicon. It is public-by-design once shipped. Concealment itself is the hard half to evade, since evading it means being visible. But the evasion post exists because pretending otherwise would be marketing.
And the line that must never blur: these families report concealed and machine-addressed content. They do not certify that a document is safe to feed to a model, and no report from this engine will ever say so. A "safe for AI" badge is a liability wearing a green checkmark. Signals, not verdicts, on this surface most of all.
Check the file before your model reads it
The two layers are complements, not competitors. Keep the guardrail for what arrives as text, and inspect the file for what the text layer was never shown. The field guide covers the full set of signal families the same report carries.
Check a PDF: free, no account, the file is analysed in
memory and discarded with the response. For agents, the MCP server is
npx tamperlens-mcp, and it never echoes a payload back into
your context.