# SafeHTML Component - Double-Layer Sanitization ## Overview The `SafeHTML` component provides **double-layer security** for rendering HTML from the server: 1. **Backend Sanitization** (PHP) - Using WordPress's `esc_attr()` and `wp_kses_post()` 2. **Frontend Sanitization** (Vue + DOMPurify) - Client-side validation and sanitization ## Architecture ### Security Layers ``` ┌─────────────────────────────────────────────────────────────┐ │ Layer 1: Backend (PHP) │ │ │ │ SignatureEntryHooks.php: │ │ └─ formatSignatureValue() │ │ └─ Returns: │ │ │ │ Result: Server-sanitized HTML │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Layer 2: Frontend (Vue + DOMPurify) │ │ │ │ SafeHTML.vue: │ │ └─ DOMPurify.sanitize(html, config) │ │ ├─ Field-specific allowed tags │ │ ├─ Field-specific allowed attributes │ │ └─ URI validation (data URLs, https, etc.) │ │ │ │ Result: Double-sanitized, field-specific HTML │ └─────────────────────────────────────────────────────────────┘ ``` ## Usage ### In Entry Display ```vue ``` ## Component API ### Props ```typescript interface Props { // The HTML string to sanitize and render (required) html: string // Field type for field-specific sanitization rules (optional) fieldType?: string // 'signature', 'rich-text', 'chart', etc. // Custom allowed HTML tags (optional, overrides field-type rules) allowedTags?: string[] // Custom allowed attributes (optional, overrides field-type rules) allowedAttributes?: Record } ``` ### Field-Specific Configurations #### Signature Field ```typescript fieldType: 'signature' DOMPurify Config: - ALLOWED_TAGS: ['img'] - ALLOWED_ATTR: ['src', 'alt', 'style', 'class', 'width', 'height'] - ALLOWED_URI_REGEXP: /^data:image\/(png|jpeg|jpg|gif|svg\+xml);base64,/i Additional Validation: - Ensures src contains 'data:image' - Blocks non-image data URLs ``` #### Rich Text Field ```typescript fieldType: 'rich-text' DOMPurify Config: - ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'i', 'u', 'a', 'ul', 'ol', 'li', 'h1'-'h6', 'blockquote', 'code', 'pre', 'span', 'div'] - ALLOWED_ATTR: ['href', 'target', 'rel', 'class', 'style'] - Links validated for safe protocols (http, https, mailto) ``` #### Chart/Visualization ```typescript fieldType: 'chart' | 'visualization' DOMPurify Config: - ALLOWED_TAGS: ['div', 'svg', 'canvas', 'img'] - ALLOWED_ATTR: ['class', 'style', 'width', 'height', 'viewBox', 'src', 'alt', 'data-*'] ``` #### Default (Unknown Types) ```typescript fieldType: undefined or unknown DOMPurify Config: - ALLOWED_TAGS: ['span', 'div'] // Very restrictive - ALLOWED_ATTR: ['class'] ``` ## Custom Configuration ### Example: Custom Allowed Tags ```vue ``` ## Security Features ### 1. **Field-Type Based Rules** Each field type has its own whitelist of allowed tags and attributes. ### 2. **URI Validation** - Data URLs: Only `data:image/*` for signature fields - External URLs: Only `http`, `https`, `mailto`, `tel` - Blocks: `javascript:`, `data:text/html`, `vbscript:`, etc. ### 3. **Attribute Filtering** Only explicitly allowed attributes pass through. Dangerous attributes like `onclick`, `onerror`, `onload` are automatically blocked. ### 4. **XSS Protection** DOMPurify removes: - ` ``` **After SafeHTML:** ```html ``` ### Rich Text Field **Input:** ```html

Hello world!

Click me ``` **After SafeHTML:** ```html

Hello world!

Click me ``` ## Testing ### Security Test Cases ```typescript // Test 1: Script injection const malicious1 = '' // Expected: (onerror removed) // Test 2: JavaScript protocol const malicious2 = 'Click' // Expected: Click (href removed) // Test 3: Data URL for non-signature const malicious3 = '' // Expected: Empty or without src (depends on field type) // Test 4: Valid signature const valid = 'Signature' // Expected: Same output (valid) ``` ## Integration with Pro Plans ### Adding New HTML Field Types 1. **Backend**: Create entry hooks in `{Plan}/Entries/{FieldType}EntryHooks.php` 2. **Frontend Hook**: Register in `plans/{plan}/hooks/entryHtmlFieldsHooks.ts` 3. **SafeHTML Config**: Add field-type case in `getDOMPurifyConfig()` Example for a new "video" field: ```typescript // SafeHTML.vue case 'video': return { ...baseConfig, ALLOWED_TAGS: ['video', 'source'], ALLOWED_ATTR: ['src', 'type', 'controls', 'width', 'height', 'poster'], ALLOWED_URI_REGEXP: /^https:\/\//i, // Only HTTPS videos } ``` ## Benefits Over v-html | Feature | `v-html` | `SafeHTML` | | --------------------- | ----------------- | ------------------- | | Backend Sanitization | ✅ | ✅ | | Frontend Sanitization | ❌ | ✅ | | Field-Specific Rules | ❌ | ✅ | | XSS Protection | ⚠️ (Backend only) | ✅✅ (Double layer) | | Custom Configurations | ❌ | ✅ | | Error Handling | ❌ | ✅ | | Bundle Size | 0KB | +32KB | ## Migration from v-html ```vue ``` ## Dependencies ```json { "dependencies": { "dompurify": "^3.0.0" }, "devDependencies": { "@types/dompurify": "^3.0.0" } } ``` ## Files - **Component**: `ivyforms/frontend/src/views/_components/SafeHTML.vue` - **Usage**: `ivyforms/frontend/src/views/admin/entries/entry-page/section/EntryPageSectionContent.vue` - **Backend**: `ivyforms-pro/backend/src/{Plan}/Entries/*EntryHooks.php` - **Frontend Hooks**: `ivyforms-pro/frontend/src/plans/{plan}/hooks/entryHtmlFieldsHooks.ts` ## Conclusion The `SafeHTML` component provides enterprise-grade security for rendering dynamic HTML in WordPress forms, with double-layer sanitization, field-specific rules, and comprehensive XSS protection.