Embedded Object Analysis
Issue: AI System Fails to Detect Hidden or Suspicious Objects Embedded in PDFs
Frequency: Rare
Symptoms
- Hidden JavaScript in PDF not detected
- Embedded files not analyzed
- Annotations contain malicious content
- Form fields with hidden data
- Invisible text layers
- Encrypted payloads embedded
Root Cause PDF documents can contain embedded objects beyond visible content: JavaScript, attached files, form fields, annotations, and multimedia. While rare in mortgage fraud, these objects can contain hidden data, malware, or information that contradicts visible content. AI systems that only parse visible text miss this embedded layer.
Example
Scenario 1: Hidden text layer with different data
PDF structure:
- Visible layer: "Gross Income: $85,000"
- Hidden text layer: "Gross Income: $45,000" ← HIDDEN
- Font size: 0pt (invisible)
When copied/searched:
- Search for "$85,000": Not found
- Search for "$45,000": Found ← Reveals hidden text
Analysis:
- Visible text was overlaid on original
- Original (true) data hidden but present
- Simple text extraction may find hidden data
← Hidden layer contains different value
← Document visually altered
---
Scenario 2: Embedded file attachment
Bank statement PDF contains:
- Visible: Statement content
- Embedded: "original_statement.pdf" ← ATTACHED FILE
Analysis of embedded file:
- Original balance: $12,500
- Visible balance: $45,000
Why embedded?
- Fraudster edited PDF
- Original embedded by editing software
- Forgot to remove original
← Original document reveals true data
← Editing artifact left behind
---
Scenario 3: Form field with hidden value
PDF form:
- Visible field: "Income" showing "$75,000"
- Field properties:
- Export value: "45000" ← DIFFERENT
- Default value: "45000" ← DIFFERENT
- Current display: "75,000"
Analysis:
- Form field displays one value
- Exports/calculates with different value
- May have been manually overridden
← Form field value manipulated
← Backend value differs from display
---
Scenario 4: JavaScript validation reveals truth
Tax return PDF contains:
- Visible AGI: $125,000
- JavaScript validation function:
function validateAGI() {
var agi = this.getField("agi").value;
if (agi > 100000) { // Hard-coded limit
app.alert("AGI exceeds expected range");
}
}
Analysis:
- JS suggests original had lower AGI
- Someone increased visible value
- Validation logic not updated
← JavaScript contains validation hints
← Suggests original had different values
---
Scenario 5: Annotation layers with comments
Pay stub PDF:
- Visible content: Standard pay stub
- Annotations (hidden by default):
- Sticky note: "Changed gross to 6500"
- Comment: "Original was 3500"
- Markup: Strikethrough on original amounts
Analysis:
- Annotator left editing notes
- Comments reveal original values
- Annotations not visible in print view
← Annotations document the fraud
← Editor left evidence in comments
---
Embedded object types in PDFs:
Object Type | Risk Level | What to Check
-----------------|------------|------------------
JavaScript | High | Validation logic, alerts
Embedded files | High | Original documents
Form fields | Medium | Value vs display
Annotations | Medium | Comments, markups
Hidden text | High | 0pt font, white text
XFA forms | Medium | XML data structure
Multimedia | Low | Usually benign
Detection methods:
- Parse all PDF objects
- Extract non-visible text
- List embedded files
- Analyze form field properties
- Read annotation content
- Execute JS in sandbox
Key Statistics From PDF Analysis (2025-2026):
- PDFs with embedded JavaScript: 5-10%
- PDFs with embedded files: 2-3%
- PDFs with hidden text: 0.5-1%
- Suspicious embedded objects: 0.1-0.2%
- Embedded objects indicating fraud: 0.05%
Contributing Factors
- Only visible content extracted
- PDF object structure not parsed
- Embedded files not detected
- Form field properties not checked
- Annotations ignored
- JavaScript not analyzed
Mitigation Strategies
Prevention
- Full object parsing: Analyze all PDF objects
- File extraction: Identify embedded files
- Form analysis: Check field values vs display
- Text layer comparison: Find hidden text
- Annotation review: Read all comments
- JavaScript analysis: Parse and review code
Implementation
from dataclasses import dataclass
from typing import List, Optional, Dict, Any
from enum import Enum
import re
class EmbeddedObjectType(Enum):
JAVASCRIPT = "javascript"
EMBEDDED_FILE = "embedded_file"
FORM_FIELD = "form_field"
ANNOTATION = "annotation"
HIDDEN_TEXT = "hidden_text"
XFA_DATA = "xfa_data"
MULTIMEDIA = "multimedia"
@dataclass
class EmbeddedObject:
type: EmbeddedObjectType
location: str
content: Any
risk_level: str
analysis: str
class EmbeddedObjectAnalyzer:
"""Analyze embedded objects in PDF documents"""
SUSPICIOUS_JS_PATTERNS = [
r"app\.alert",
r"this\.submitForm",
r"this\.getField",
r"eval\s*\(",
r"unescape\s*\(",
r"XMLHttpRequest",
r"\\x[0-9a-f]{2}", # Hex encoding
]
def analyze_pdf(self, pdf_document: dict) -> dict:
"""Analyze PDF for embedded objects"""
result = {
"objects_found": [],
"suspicious_objects": [],
"hidden_data": [],
"risk_score": 0.0
}
# Analyze JavaScript
js_objects = self.extract_javascript(pdf_document)
for js in js_objects:
result["objects_found"].append(js)
if js.risk_level in ["high", "medium"]:
result["suspicious_objects"].append(js)
result["risk_score"] += 0.2
# Analyze embedded files
files = self.extract_embedded_files(pdf_document)
for file in files:
result["objects_found"].append(file)
if file.risk_level == "high":
result["suspicious_objects"].append(file)
result["risk_score"] += 0.3
# Analyze form fields
fields = self.analyze_form_fields(pdf_document)
for field in fields:
if field.risk_level != "low":
result["objects_found"].append(field)
if field.risk_level == "high":
result["suspicious_objects"].append(field)
result["hidden_data"].append({
"field": field.location,
"display": field.content.get("display"),
"actual": field.content.get("value")
})
result["risk_score"] += 0.35
# Analyze annotations
annotations = self.extract_annotations(pdf_document)
for annot in annotations:
result["objects_found"].append(annot)
if annot.risk_level == "high":
result["suspicious_objects"].append(annot)
result["risk_score"] += 0.25
# Find hidden text
hidden = self.find_hidden_text(pdf_document)
for text in hidden:
result["objects_found"].append(text)
result["hidden_data"].append({
"type": "hidden_text",
"content": text.content
})
result["risk_score"] += 0.4
result["risk_score"] = min(result["risk_score"], 1.0)
return result
def extract_javascript(self, pdf: dict) -> List[EmbeddedObject]:
"""Extract and analyze JavaScript"""
results = []
js_objects = pdf.get("javascript", [])
for js in js_objects:
code = js.get("code", "")
location = js.get("location", "unknown")
# Analyze code
risk = "low"
analysis_notes = []
for pattern in self.SUSPICIOUS_JS_PATTERNS:
if re.search(pattern, code, re.IGNORECASE):
risk = "medium"
analysis_notes.append(f"Pattern found: {pattern}")
# Check for value manipulation
if "getField" in code and ("value" in code or "setValue" in code):
risk = "high"
analysis_notes.append("JavaScript manipulates form values")
# Check for validation with hard-coded values
if re.search(r"if\s*\([^)]*>\s*\d+", code):
analysis_notes.append("Contains validation with numeric limits")
results.append(EmbeddedObject(
type=EmbeddedObjectType.JAVASCRIPT,
location=location,
content=code[:500], # Truncate for storage
risk_level=risk,
analysis="; ".join(analysis_notes) or "No suspicious patterns"
))
return results
def extract_embedded_files(self, pdf: dict) -> List[EmbeddedObject]:
"""Extract embedded file attachments"""
results = []
embedded = pdf.get("embedded_files", [])
for file in embedded:
name = file.get("name", "unknown")
size = file.get("size", 0)
file_type = file.get("type", "unknown")
# Analyze risk
risk = "low"
analysis = []
# PDF embedded in PDF is suspicious
if file_type == "application/pdf":
risk = "high"
analysis.append("PDF file embedded - may be original document")
# Certain file types are higher risk
if file_type in ["application/x-javascript", "text/html"]:
risk = "high"
analysis.append("Executable content embedded")
# Files with similar names to parent
if any(x in name.lower() for x in ["original", "backup", "old"]):
risk = "high"
analysis.append("File name suggests original version")
results.append(EmbeddedObject(
type=EmbeddedObjectType.EMBEDDED_FILE,
location=name,
content={
"name": name,
"size": size,
"type": file_type
},
risk_level=risk,
analysis="; ".join(analysis) or "Standard embedded file"
))
return results
def analyze_form_fields(self, pdf: dict) -> List[EmbeddedObject]:
"""Analyze form field values vs display"""
results = []
fields = pdf.get("form_fields", [])
for field in fields:
name = field.get("name", "unknown")
display_value = field.get("display_value")
actual_value = field.get("value")
default_value = field.get("default_value")
export_value = field.get("export_value")
# Check for mismatches
risk = "low"
analysis = []
if display_value and actual_value:
if str(display_value) != str(actual_value):
risk = "high"
analysis.append(
f"Display '{display_value}' differs from value '{actual_value}'"
)
if actual_value and export_value:
if str(actual_value) != str(export_value):
risk = "medium"
analysis.append(
f"Value '{actual_value}' differs from export '{export_value}'"
)
if risk != "low":
results.append(EmbeddedObject(
type=EmbeddedObjectType.FORM_FIELD,
location=name,
content={
"display": display_value,
"value": actual_value,
"default": default_value,
"export": export_value
},
risk_level=risk,
analysis="; ".join(analysis)
))
return results
def extract_annotations(self, pdf: dict) -> List[EmbeddedObject]:
"""Extract and analyze annotations"""
results = []
annotations = pdf.get("annotations", [])
# Suspicious keywords in comments
suspicious_keywords = [
"original", "changed", "edited", "modified",
"was", "before", "old", "updated", "fake"
]
for annot in annotations:
content = annot.get("content", "")
annot_type = annot.get("type", "unknown")
page = annot.get("page", 0)
risk = "low"
analysis = []
# Check content for suspicious keywords
content_lower = content.lower()
for keyword in suspicious_keywords:
if keyword in content_lower:
risk = "high"
analysis.append(f"Contains '{keyword}' - may document edits")
break
# Certain annotation types are more concerning
if annot_type in ["StrikeOut", "Highlight"]:
if any(kw in content_lower for kw in ["amount", "income", "balance"]):
risk = "medium"
analysis.append("Markup on financial data")
if risk != "low":
results.append(EmbeddedObject(
type=EmbeddedObjectType.ANNOTATION,
location=f"Page {page}",
content=content,
risk_level=risk,
analysis="; ".join(analysis)
))
return results
def find_hidden_text(self, pdf: dict) -> List[EmbeddedObject]:
"""Find hidden text (0pt font, white on white, etc.)"""
results = []
text_objects = pdf.get("text_objects", [])
for text_obj in text_objects:
text = text_obj.get("text", "")
font_size = text_obj.get("font_size", 12)
color = text_obj.get("color", [0, 0, 0])
bg_color = text_obj.get("background", [255, 255, 255])
is_hidden = False
reason = ""
# Zero-point font
if font_size == 0 or font_size < 1:
is_hidden = True
reason = "Zero-point font"
# White on white
if color == bg_color:
is_hidden = True
reason = "Same color as background"
# Very light color
if all(c > 250 for c in color):
is_hidden = True
reason = "Nearly white text"
if is_hidden and text.strip():
# Check if hidden text differs from visible
results.append(EmbeddedObject(
type=EmbeddedObjectType.HIDDEN_TEXT,
location=str(text_obj.get("position", "unknown")),
content=text[:200],
risk_level="high",
analysis=f"Hidden text found: {reason}"
))
return results
def compare_visible_to_embedded(self,
visible_data: dict,
embedded_data: dict) -> List[dict]:
"""Compare visible extraction to embedded data"""
discrepancies = []
for key in visible_data:
visible_val = str(visible_data[key])
embedded_val = str(embedded_data.get(key, ""))
if embedded_val and visible_val != embedded_val:
discrepancies.append({
"field": key,
"visible": visible_val,
"embedded": embedded_val,
"risk": "high" if self.is_financial_field(key) else "medium"
})
return discrepancies
def is_financial_field(self, field_name: str) -> bool:
"""Check if field is financial data"""
financial_keywords = [
"income", "wage", "salary", "balance", "amount",
"gross", "net", "payment", "total", "agi"
]
return any(kw in field_name.lower() for kw in financial_keywords)
Risk Scoring for Embedded Objects
| Object Type | Risk Score | Action |
|---|---|---|
| Hidden text with different values | 0.5 | Fraud investigation |
| Embedded PDF (original) | 0.4 | Compare to visible |
| Form field value mismatch | 0.35 | Verify true value |
| Editing comments in annotations | 0.3 | Review for intent |
| JavaScript with value access | 0.25 | Analyze code |
| Standard embedded files | 0.1 | Note presence |