PluginProbe
The Innovative Form Builder – IvyForms / 1.0
The Innovative Form Builder – IvyForms v1.0
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / documentation / SAFE_HTML_COMPONENT.md

SAFE_HTML_COMPONENT.md in The Innovative Form Builder – IvyForms 1.0, at frontend/documentation/SAFE_HTML_COMPONENT.md

346 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 # SafeHTML Component - Double-Layer Sanitization
2
3 ## Overview
4
5 The `SafeHTML` component provides **double-layer security** for rendering HTML from the server:
6
7 1. **Backend Sanitization** (PHP) - Using WordPress's `esc_attr()` and `wp_kses_post()`
8 2. **Frontend Sanitization** (Vue + DOMPurify) - Client-side validation and sanitization
9
10 ## Architecture
11
12 ### Security Layers
13
14 ```
15 ┌─────────────────────────────────────────────────────────────┐
16 │ Layer 1: Backend (PHP) │
17 │ │
18 │ SignatureEntryHooks.php: │
19 │ └─ formatSignatureValue() │
20 │ └─ Returns: <img src="<?= esc_attr($value) ?>" /> │
21 │ │
22 │ Result: Server-sanitized HTML │
23 └─────────────────────────────────────────────────────────────┘
24 │
25 ▼
26 ┌─────────────────────────────────────────────────────────────┐
27 │ Layer 2: Frontend (Vue + DOMPurify) │
28 │ │
29 │ SafeHTML.vue: │
30 │ └─ DOMPurify.sanitize(html, config) │
31 │ ├─ Field-specific allowed tags │
32 │ ├─ Field-specific allowed attributes │
33 │ └─ URI validation (data URLs, https, etc.) │
34 │ │
35 │ Result: Double-sanitized, field-specific HTML │
36 └─────────────────────────────────────────────────────────────┘
37 ```
38
39 ## Usage
40
41 ### In Entry Display
42
43 ```vue
44 <template>
45 <tbody>
46 <tr v-for="field in filteredFields" :key="field.id">
47 <td>{{ getFieldLabel(field) }}</td>
48
49 <!-- HTML fields with SafeHTML -->
50 <td v-if="shouldRenderAsHtml(field)">
51 <SafeHTML :html="getFieldDisplayValue(field)" :field-type="getFormField(field)?.type" />
52 </td>
53
54 <!-- Regular text fields -->
55 <td v-else>{{ getFieldDisplayValue(field) }}</td>
56 </tr>
57 </tbody>
58 </template>
59
60 <script setup>
61 import SafeHTML from '@/views/_components/SafeHTML.vue'
62 </script>
63 ```
64
65 ## Component API
66
67 ### Props
68
69 ```typescript
70 interface Props {
71 // The HTML string to sanitize and render (required)
72 html: string
73
74 // Field type for field-specific sanitization rules (optional)
75 fieldType?: string // 'signature', 'rich-text', 'chart', etc.
76
77 // Custom allowed HTML tags (optional, overrides field-type rules)
78 allowedTags?: string[]
79
80 // Custom allowed attributes (optional, overrides field-type rules)
81 allowedAttributes?: Record<string, string[]>
82 }
83 ```
84
85 ### Field-Specific Configurations
86
87 #### Signature Field
88
89 ```typescript
90 fieldType: 'signature'
91
92 DOMPurify Config:
93 - ALLOWED_TAGS: ['img']
94 - ALLOWED_ATTR: ['src', 'alt', 'style', 'class', 'width', 'height']
95 - ALLOWED_URI_REGEXP: /^data:image\/(png|jpeg|jpg|gif|svg\+xml);base64,/i
96
97 Additional Validation:
98 - Ensures src contains 'data:image'
99 - Blocks non-image data URLs
100 ```
101
102 #### Rich Text Field
103
104 ```typescript
105 fieldType: 'rich-text'
106
107 DOMPurify Config:
108 - ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'i', 'u', 'a', 'ul', 'ol', 'li',
109 'h1'-'h6', 'blockquote', 'code', 'pre', 'span', 'div']
110 - ALLOWED_ATTR: ['href', 'target', 'rel', 'class', 'style']
111 - Links validated for safe protocols (http, https, mailto)
112 ```
113
114 #### Chart/Visualization
115
116 ```typescript
117 fieldType: 'chart' | 'visualization'
118
119 DOMPurify Config:
120 - ALLOWED_TAGS: ['div', 'svg', 'canvas', 'img']
121 - ALLOWED_ATTR: ['class', 'style', 'width', 'height', 'viewBox', 'src', 'alt', 'data-*']
122 ```
123
124 #### Default (Unknown Types)
125
126 ```typescript
127 fieldType: undefined or unknown
128
129 DOMPurify Config:
130 - ALLOWED_TAGS: ['span', 'div'] // Very restrictive
131 - ALLOWED_ATTR: ['class']
132 ```
133
134 ## Custom Configuration
135
136 ### Example: Custom Allowed Tags
137
138 ```vue
139 <SafeHTML
140 :html="customHTML"
141 field-type="custom"
142 :allowed-tags="['div', 'p', 'img', 'a']"
143 :allowed-attributes="{
144 img: ['src', 'alt'],
145 a: ['href', 'target'],
146 }"
147 />
148 ```
149
150 ## Security Features
151
152 ### 1. **Field-Type Based Rules**
153
154 Each field type has its own whitelist of allowed tags and attributes.
155
156 ### 2. **URI Validation**
157
158 - Data URLs: Only `data:image/*` for signature fields
159 - External URLs: Only `http`, `https`, `mailto`, `tel`
160 - Blocks: `javascript:`, `data:text/html`, `vbscript:`, etc.
161
162 ### 3. **Attribute Filtering**
163
164 Only explicitly allowed attributes pass through. Dangerous attributes like `onclick`, `onerror`, `onload` are automatically blocked.
165
166 ### 4. **XSS Protection**
167
168 DOMPurify removes:
169
170 - `<script>` tags
171 - Event handlers (`onclick`, `onerror`, etc.)
172 - `javascript:` URIs
173 - Form elements (`<form>`, `<input>`) unless explicitly allowed
174 - Iframes unless explicitly allowed
175
176 ### 5. **Error Handling**
177
178 If sanitization fails, returns empty string and logs error to console.
179
180 ## Performance
181
182 ### Optimization Strategies
183
184 1. **Lazy Rendering**: SafeHTML only renders when the container is mounted
185 2. **Watch Optimization**: Only re-sanitizes when `html` prop changes
186 3. **Config Caching**: DOMPurify config is determined once per render
187
188 ### Bundle Size
189
190 - **DOMPurify**: ~30KB minified + gzipped
191 - **SafeHTML Component**: ~2KB
192 - **Total Overhead**: ~32KB for XSS protection
193
194 ## Example Outputs
195
196 ### Signature Field
197
198 **Input:**
199
200 ```html
201 <img src="data:image/png;base64,iVBORw0KGgo..." alt="Signature" style="max-width: 100%;" />
202 ```
203
204 **After SafeHTML:**
205
206 ```html
207 <img src="data:image/png;base64,iVBORw0KGgo..." alt="Signature" style="max-width: 100%;" />
208 ```
209
210 ### Malicious Input (Signature Field)
211
212 **Input:**
213
214 ```html
215 <img src="data:image/png;base64,..." onerror="alert('xss')" />
216 <script>
217 alert('xss')
218 </script>
219 ```
220
221 **After SafeHTML:**
222
223 ```html
224 <img src="data:image/png;base64,..." alt="" />
225 <!-- script tag completely removed -->
226 ```
227
228 ### Rich Text Field
229
230 **Input:**
231
232 ```html
233 <p>Hello <strong>world</strong>!</p>
234 <a href="javascript:alert('xss')">Click me</a>
235 ```
236
237 **After SafeHTML:**
238
239 ```html
240 <p>Hello <strong>world</strong>!</p>
241 <a>Click me</a>
242 <!-- href removed because javascript: protocol -->
243 ```
244
245 ## Testing
246
247 ### Security Test Cases
248
249 ```typescript
250 // Test 1: Script injection
251 const malicious1 = '<img src=x onerror="alert(1)">'
252 // Expected: <img src="x"> (onerror removed)
253
254 // Test 2: JavaScript protocol
255 const malicious2 = '<a href="javascript:alert(1)">Click</a>'
256 // Expected: <a>Click</a> (href removed)
257
258 // Test 3: Data URL for non-signature
259 const malicious3 = '<img src="data:text/html,<script>alert(1)</script>">'
260 // Expected: Empty or <img> without src (depends on field type)
261
262 // Test 4: Valid signature
263 const valid = '<img src="data:image/png;base64,iVBORw..." alt="Signature">'
264 // Expected: Same output (valid)
265 ```
266
267 ## Integration with Pro Plans
268
269 ### Adding New HTML Field Types
270
271 1. **Backend**: Create entry hooks in `{Plan}/Entries/{FieldType}EntryHooks.php`
272 2. **Frontend Hook**: Register in `plans/{plan}/hooks/entryHtmlFieldsHooks.ts`
273 3. **SafeHTML Config**: Add field-type case in `getDOMPurifyConfig()`
274
275 Example for a new "video" field:
276
277 ```typescript
278 // SafeHTML.vue
279 case 'video':
280 return {
281 ...baseConfig,
282 ALLOWED_TAGS: ['video', 'source'],
283 ALLOWED_ATTR: ['src', 'type', 'controls', 'width', 'height', 'poster'],
284 ALLOWED_URI_REGEXP: /^https:\/\//i, // Only HTTPS videos
285 }
286 ```
287
288 ## Benefits Over v-html
289
290 | Feature | `v-html` | `SafeHTML` |
291 | --------------------- | ----------------- | ------------------- |
292 | Backend Sanitization | �
293 | �
294 |
295 | Frontend Sanitization | ❌ | �
296 |
297 | Field-Specific Rules | ❌ | �
298 |
299 | XSS Protection | ⚠️ (Backend only) | �
300 �
301 (Double layer) |
302 | Custom Configurations | ❌ | �
303 |
304 | Error Handling | ❌ | �
305 |
306 | Bundle Size | 0KB | +32KB |
307
308 ## Migration from v-html
309
310 ```vue
311 <!-- Before: Simple v-html -->
312 <td v-html="getFieldDisplayValue(field)"></td>
313
314 <!-- After: SafeHTML component -->
315 <td>
316 <SafeHTML
317 :html="getFieldDisplayValue(field)"
318 :field-type="getFormField(field)?.type"
319 />
320 </td>
321 ```
322
323 ## Dependencies
324
325 ```json
326 {
327 "dependencies": {
328 "dompurify": "^3.0.0"
329 },
330 "devDependencies": {
331 "@types/dompurify": "^3.0.0"
332 }
333 }
334 ```
335
336 ## Files
337
338 - **Component**: `ivyforms/frontend/src/views/_components/SafeHTML.vue`
339 - **Usage**: `ivyforms/frontend/src/views/admin/entries/entry-page/section/EntryPageSectionContent.vue`
340 - **Backend**: `ivyforms-pro/backend/src/{Plan}/Entries/*EntryHooks.php`
341 - **Frontend Hooks**: `ivyforms-pro/frontend/src/plans/{plan}/hooks/entryHtmlFieldsHooks.ts`
342
343 ## Conclusion
344
345 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.
346