Embedded Object Analysis

Goal Document Integrity Frequency Rare Category Mortgage Documents Published View source on GitHub ↗

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

  1. Full object parsing: Analyze all PDF objects
  2. File extraction: Identify embedded files
  3. Form analysis: Check field values vs display
  4. Text layer comparison: Find hidden text
  5. Annotation review: Read all comments
  6. 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 TypeRisk ScoreAction
Hidden text with different values0.5Fraud investigation
Embedded PDF (original)0.4Compare to visible
Form field value mismatch0.35Verify true value
Editing comments in annotations0.3Review for intent
JavaScript with value access0.25Analyze code
Standard embedded files0.1Note presence

References