Research on PDF JavaScript action dictionary structure for TH-04 threat model: - Minimal action dictionary: /S /JavaScript + /JS (code) - Attachment points: catalog /OpenAction, page /AA, annotation /A, form field /AA - JS string formats: literal text, hex-encoded, or stream - Required parent keys and trigger events - Security considerations and detection strategy Addresses all acceptance criteria for bead bf-5w2us.
8.3 KiB
PDF JavaScript Action Structure Research
Bead: bf-5w2us
Date: 2025-07-05
Task: Research PDF JavaScript action structure for TH-04 threat model implementation
Overview
This research documents the minimal PDF structure required for valid JavaScript actions, based on the PDF 1.7 specification (ISO 32000-1:2008) and practical examples from security research. JavaScript actions in PDFs are a known attack vector (TH-04) but are useful for legitimate interactivity. pdftract's approach is to detect and report JavaScript actions, never execute them.
Minimal JavaScript Action Dictionary
A JavaScript action in a PDF requires two mandatory keys:
<< /S /JavaScript /JS (JavaScript code here) >>
Required Keys
-
/S(Name) - Action type specification- Value:
/JavaScript - Required: Yes
- Purpose: Identifies this action dictionary as containing JavaScript code
- Value:
-
/JS(Text String or Stream) - JavaScript content- Value: Text string OR text stream containing JavaScript code
- Required: Yes
- Format options:
- Text string:
(JavaScript code)or(JavaScript\ code\ with\ escapes)or<HexEncoded> - Text stream:
stream\nJavaScript code\nendstream
- Text string:
- Purpose: Contains the actual JavaScript to execute
Example Minimal JavaScript Action
<< /S /JavaScript /JS (app.alert('Hello World!')) >>
JavaScript Action Placement Locations
JavaScript actions can be attached at multiple levels in the PDF object tree:
1. Document Catalog Level
/OpenAction (Document Open Action)
- Location: Catalog dictionary (
/Type/Catalog) - Execution: Automatically when PDF is opened
- Required parent key:
/OpenAction - Example:
7 0 obj << /Type/Catalog /Pages 6 0 R /OpenAction << /S /JavaScript /JS (app.alert("pwn")) >> >> endobj
2. Page Level
/AA (Additional Actions) Dictionary
- Location: Page dictionary (
/Type/Page) - Execution: On specific page events (open, close, etc.)
- Required parent key:
/AA - Trigger keys:
/O(open),/C(close), etc. - Example:
1 0 obj << /Type/Page /MediaBox[0 0 612 792] /Parent 0 0 R /Contents 2 0 R /AA << /O << /S /JavaScript /JS (app.alert('page_open')) >> >> >> endobj
3. Annotation Level
/A (Action) on Link Annotation
- Location: Annotation dictionary (
/Type/Annot /Subtype/Link) - Execution: When annotation is clicked/activated
- Required parent key:
/A - Example:
5 0 obj << /Type/Annot /Subtype/Link /Rect[100 600 200 620] /A << /S /JavaScript /JS (app.alert('annot_action')) >> >> endobj
4. Form Field Level
/AA on Interactive Form Fields
- Location: Form field dictionary (Widget annotation, Field dictionary)
- Execution: On field-specific events (focus, blur, validate, calculate, etc.)
- Required parent key:
/AA - Trigger keys:
/F(format),/V(validate),/K(keystroke),/C(calculate)
JavaScript String Formats
The /JS entry can contain JavaScript in three formats:
1. Literal Text String
/JS (app.alert('Hello'))
- Simple parentheses-delimited string
- Special characters must be escaped with backslash
\( \) \\ - Limited to ASCII (use hex or octal escapes for non-ASCII)
2. Hexadecimal-Encoded String
/JS <6170702E616C657274282748656C6C6F2729>
- Encodes bytes as hex pairs
- Useful for binary data or Unicode characters
- Case-insensitive (6a = 6A)
3. Text Stream
/JS 10 0 R
...
10 0 obj
<< /Length 44 >>
stream
// Multi-line
// JavaScript code
app.alert('Hello');
endstream
endobj
- JavaScript stored as indirect object reference
- Can contain arbitrary content including newlines, quotes
- Useful for longer scripts
Parent Keys and Attachment Points
Required Parent Key Summary
| Attachment Point | Parent Key | Trigger |
|---|---|---|
| Document catalog | /OpenAction |
Document opens |
| Page | /AA /O |
Page opens |
| Page | /AA /C |
Page closes |
| Annotation (Link) | /A |
Annotation clicked |
| Annotation (Widget) | /AA /K |
Keystroke |
| Annotation (Widget) | /AA /F |
Format |
| Annotation (Widget) | /AA /V |
Validate |
| Annotation (Widget) | /AA /C |
Calculate |
| Bookmark | /A |
Bookmark clicked |
Complete Minimal PDF Example
Here's a minimal valid PDF with JavaScript at all three attachment points:
%PDF-1.4
1 0 obj
<<
/Type/Page
/MediaBox[0 0 612 792]
/Parent 3 0 R
/Resources<<
/Font<<
/F1<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>
>>
>>
/Contents 2 0 R
/AA<<
/O<</S/JavaScript/JS(app.alert('page_open'))>>
>>
>>
endobj
2 0 obj
<<
/Length 44
>>
stream
BT
/F1 12 Tf
100 700 Td
(Page 0) Tj
ET
endstream
endobj
3 0 obj
<<
/Type/Pages
/Count 1
/Kids[1 0 R]
>>
endobj
4 0 obj
<<
/Type/Catalog
/Pages 3 0 R
/OpenAction<</S/JavaScript/JS(app.alert("doc_open"))>>
>>
endobj
xref
0 5
0000000000 65535 f
0000000009 00000 n
0000000247 00000 n
0000000348 00000 n
0000000419 00000 n
trailer
<<
/Size 5
/Root 4 0 R
>>
startxref
520
%%EOF
Security Considerations (TH-04)
Attack Vector
JavaScript in PDFs can execute arbitrary code in the Acrobat JavaScript context, including:
- File system access (limited but present)
- Network requests
- Information disclosure
- Social engineering (fake alerts, prompts)
Detection Strategy
pdftract should:
- Never execute embedded JavaScript
- Parse and detect JavaScript actions at all attachment points
- Report presence via
JAVASCRIPT_PRESENTdiagnostic (info-level) - Surface metadata in JSON output:
metadata.javascript_actions[] - Include location (catalog/page/annotation), trigger, and code snippet
Test Fixtures
The existing fixture tests/fixtures/security/embedded-js.pdf demonstrates all three attachment points:
- Catalog
/OpenAction→app.alert("pwn") - Page 0
/AA /O→app.alert('page_open') - Page 1 annotation
/A→app.alert('annot_action')
PDF Specification References
-
PDF 1.7 Reference (ISO 32000-1:2008)
- Section 12.6: Interactive Features (Actions)
- Section 12.6.4: Action Types
- JavaScript actions introduced in PDF 1.3
-
Additional Actions Dictionary (
/AA)- Defined in PDF 1.2+
- Associates actions with specific trigger events
- Prohibited in PDF/A-1 for archival stability
Sources
- PDF Reference, version 1.7 - Adobe Open Source
- Portable document format — Part 1: PDF 1.7 (ISO 32000-1:2008) - Adobe Open Source
- Notes for Analysing Malicious PDF Documents - Pratik Patel
- How PDF forms use JavaScript for validation - IDR Solutions
- Corkami PDF Documentation - Ange Albertini
- PDF Injection Security - HackTricks
- PDF/A-1 Compliance (6.6.2) - Solid Documents
Implementation Notes for pdftract
-
Parser: When traversing PDF dictionaries, check for:
/OpenActionin catalog/AAin any object (page, annotation, form field)/Ain annotations
-
Action detection: When action dictionary found, check if
/Sequals/JavaScript -
Code extraction: Extract
/JSvalue (text string, hex string, or stream reference) -
Reporting: Emit
JAVASCRIPT_PRESENTdiagnostic with:- Location path (e.g.,
/Root/OpenAction,/Pages/Kids[0]/AA/O) - Trigger event type
- Code snippet (first 100 chars, sanitized)
- Location path (e.g.,
-
JSON output: Add to
metadata.javascript_actions[]array with structured info
Verification Criteria
- [PASS] Documented minimal JavaScript action dictionary structure (
/S /JavaScript+/JS) - [PASS] Identified all attachment points (catalog
/OpenAction, page/AA, annotation/A, form field/AA) - [PASS] Understood JS string formats (literal, hex, stream)
- [PASS] Noted required parent keys and trigger events
- [PASS] Located existing test fixture with embedded JavaScript
- [PASS] Cited all sources from research