Files
michaelschiemer/tests/e2e/livecomponents/SECURITY-TESTS.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

20 KiB

LiveComponents Security E2E Tests

Comprehensive security testing suite for LiveComponents framework covering CSRF protection, rate limiting, idempotency, input sanitization, authorization, and session security.

Overview

This test suite validates critical security features:

  • CSRF Protection: Token validation for state-changing actions
  • Rate Limiting: Protection against abuse and DoS attacks
  • Idempotency: Preventing duplicate action execution
  • Input Sanitization: XSS and injection prevention
  • Authorization: Access control and permissions
  • Session Security: Session management and hijacking prevention
  • Content Security Policy: CSP header enforcement

Quick Start

Prerequisites

# Ensure Playwright is installed
npm install

# Install browser binaries
npx playwright install chromium

# Ensure development server is running
make up

Running Security Tests

# Run all security tests
npm run test:security

# Run specific security test category
npx playwright test security.spec.js --grep "CSRF Protection"
npx playwright test security.spec.js --grep "Rate Limiting"
npx playwright test security.spec.js --grep "Idempotency"

# Run with visible browser (for debugging)
npm run test:security:headed

# Run with debug mode
npm run test:security:debug

Test Categories

1. CSRF Protection (4 tests)

Purpose: Validate Cross-Site Request Forgery protection for all state-changing actions.

Test: CSRF token included in requests

Validates:

  • CSRF token present in action requests
  • Token included in headers (X-CSRF-Token) or POST data (_csrf_token)
  • Framework automatically handles token management

Expected Behavior:

// Request includes CSRF token
headers: {
    'X-CSRF-Token': 'generated-token-value'
}

// OR in POST data
{
    _csrf_token: 'generated-token-value',
    action: 'increment',
    params: { ... }
}

Test: Reject action without CSRF token

Validates:

  • Requests without CSRF token are rejected
  • Appropriate error message returned
  • No state changes occur

Expected Response:

Status: 403 Forbidden
Error: "CSRF token validation failed"

Test: Reject action with invalid CSRF token

Validates:

  • Invalid/expired tokens are rejected
  • Token tampering detected
  • Security event logged

Test Approach:

// Replace token with invalid value
headers: { 'X-CSRF-Token': 'invalid-token-12345' }

// Expected: 403 Forbidden

Test: CSRF token rotation

Validates:

  • Tokens rotate after usage (optional, framework-dependent)
  • New token provided in response
  • Old token invalidated

Rotation Strategy:

  • Per-request rotation: New token after each action
  • Time-based rotation: New token after TTL
  • Session-based: Token tied to session lifetime

2. Rate Limiting (5 tests)

Purpose: Prevent abuse through excessive action calls and DoS attacks.

Test: Enforce rate limit on rapid calls

Configuration:

  • Default Limit: 10 requests per minute
  • Window: 60 seconds sliding window
  • Action: Block excess requests

Test Approach:

// Trigger 20 rapid action calls
for (let i = 0; i < 20; i++) {
    await triggerAction();
}

// Expected: First 10 succeed, remaining 10 blocked
// Success rate: 50% (10/20)

Expected Error:

Status: 429 Too Many Requests
Error: "Rate limit exceeded"
Retry-After: 45  // seconds

Test: Retry-After header

Validates:

  • Retry-After header present in 429 responses
  • Header contains remaining cooldown time
  • Client can use header for backoff

Response Format:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{
    "error": "Rate limit exceeded",
    "retryAfter": 60,
    "limit": 10,
    "window": 60
}

Test: Rate limit reset after cooldown

Validates:

  • Rate limit resets after window expires
  • Actions allowed again after cooldown
  • No lingering restrictions

Timeline:

00:00 - First 10 requests succeed
00:01 - Next 10 requests blocked (rate limited)
00:01 - Retry-After: 59 seconds
01:00 - Window expires, limit resets
01:01 - New requests succeed

Test: Separate limits per action type

Validates:

  • Different actions have independent rate limits
  • Hitting limit on action A doesn't affect action B
  • Granular control per action

Configuration:

const RATE_LIMITS = {
    'increment': { limit: 10, window: 60 },
    'delete': { limit: 5, window: 300 },
    'search': { limit: 30, window: 60 }
};

Test: IP-based rate limiting

Validates:

  • Rate limits applied per IP address
  • Multiple users from same IP share limit
  • Different IPs have independent limits

Scenario:

User A (IP: 192.168.1.1) - 10 requests
User B (IP: 192.168.1.1) - 10 requests
Result: Both users share 10-request limit for that IP

3. Idempotency (4 tests)

Purpose: Ensure critical operations execute exactly once, even with duplicate requests.

Test: Handle duplicate calls idempotently

Validates:

  • Duplicate action calls with same idempotency key are ignored
  • Operation executes exactly once
  • State changes applied only once

Test Approach:

// First call with idempotency key
await callAction('increment', { idempotencyKey: 'key-123' });
// Counter: 0 → 1

// Duplicate call (same key)
await callAction('increment', { idempotencyKey: 'key-123' });
// Counter: 1 (unchanged, duplicate ignored)

Test: Allow different idempotency keys

Validates:

  • Different idempotency keys allow separate executions
  • Each unique key represents distinct operation
  • No interference between different keys

Test Approach:

await callAction('increment', { idempotencyKey: 'key-1' });  // Executes
await callAction('increment', { idempotencyKey: 'key-2' });  // Executes
await callAction('increment', { idempotencyKey: 'key-1' });  // Ignored (duplicate)

Test: Idempotency key expiration

Validates:

  • Keys expire after TTL (default: 24 hours)
  • Expired keys allow re-execution
  • Storage cleanup for old keys

Timeline:

00:00 - Action with key-123 executes
00:00 - Duplicate with key-123 ignored
24:00 - Key-123 expires
24:01 - Action with key-123 executes again (new operation)

Test: Cached result for duplicate action

Validates:

  • Duplicate calls return cached result
  • No server-side re-execution
  • Consistent response for same key

Expected Behavior:

const result1 = await callAction('createOrder', {
    idempotencyKey: 'order-123'
});
// Returns: { orderId: 'abc', total: 100 }

const result2 = await callAction('createOrder', {
    idempotencyKey: 'order-123'
});
// Returns: { orderId: 'abc', total: 100 } (cached, same result)

4. Input Sanitization & XSS Prevention (5 tests)

Purpose: Prevent XSS, injection attacks, and malicious input processing.

Test: Sanitize HTML in action parameters

Validates:

  • HTML tags stripped or escaped
  • Script tags prevented from execution
  • Safe rendering of user input

Attack Vectors Tested:

// XSS attempts
'<script>alert("XSS")</script>'
'<img src=x onerror=alert("XSS")>'
'<svg onload=alert("XSS")>'
'<iframe src="javascript:alert(\'XSS\')"></iframe>'

Expected Output:

<!-- Input: <script>alert("XSS")</script> -->
<!-- Output: &lt;script&gt;alert("XSS")&lt;/script&gt; -->
<!-- Displayed as text, not executed -->

Test: Escape HTML entities

Validates:

  • Special characters properly escaped
  • No raw HTML injection
  • Content-Type headers correct

Entity Escaping:

< → &lt;
> → &gt;
& → &amp;
" → &quot;
' → &#x27;

Test: Prevent JavaScript injection

Validates:

  • javascript: protocol blocked in URLs
  • Event handlers stripped
  • No inline script execution

Blocked Patterns:

javascript:alert('XSS')
data:text/html,<script>alert('XSS')</script>
vbscript:msgbox("XSS")

Test: Validate file paths

Validates:

  • Path traversal attacks prevented
  • Only allowed directories accessible
  • Absolute paths rejected

Attack Attempts:

../../../etc/passwd
..\..\windows\system32\config\sam
/etc/passwd
C:\Windows\System32\config\SAM

Expected: All rejected with "Invalid path" error

Test: Prevent SQL injection

Validates:

  • Parameterized queries used
  • SQL keywords escaped
  • No raw query concatenation

Attack Vectors:

'; DROP TABLE users; --
' OR '1'='1
admin'--
' UNION SELECT * FROM users--

Expected: Treated as literal search strings, no SQL execution

5. Authorization (3 tests)

Purpose: Enforce access control and permission checks.

Test: Reject unauthorized action calls

Validates:

  • Actions requiring authentication are protected
  • Anonymous users blocked
  • Clear error message

Protected Action:

#[Action]
#[RequiresAuth]
public function deleteAllData(): array
{
    // Only authenticated users
}

Expected Response:

Status: 401 Unauthorized
Error: "Authentication required"

Test: Allow authorized action calls

Validates:

  • Authenticated users can access protected actions
  • Valid session/token accepted
  • Actions execute successfully

Flow:

1. User logs in → Session created
2. Call protected action → Success
3. Action executes → Result returned

Test: Role-based authorization

Validates:

  • Different roles have different permissions
  • Role checks enforced
  • Insufficient permissions rejected

Role Matrix:

Action: deleteAllData
- admin: ✅ Allowed
- moderator: ❌ Forbidden
- user: ❌ Forbidden

Action: editOwnProfile
- admin: ✅ Allowed
- moderator: ✅ Allowed
- user: ✅ Allowed

6. Session Security (3 tests)

Purpose: Secure session management and hijacking prevention.

Test: Invalidate session after logout

Validates:

  • Session properly destroyed on logout
  • Session cookie removed
  • Subsequent requests rejected

Flow:

1. Login → Session active
2. Call protected action → Success
3. Logout → Session destroyed
4. Call protected action → 401 Unauthorized

Test: Detect session hijacking

Validates:

  • Session binding to IP/User-Agent
  • Suspicious activity detected
  • Stolen sessions rejected

Detection Criteria:

  • IP address change
  • User-Agent change
  • Geo-location anomaly
  • Concurrent sessions from different locations

Response:

Status: 403 Forbidden
Error: "Session invalid - security violation detected"
Action: Session terminated, user notified

Test: Enforce session timeout

Validates:

  • Sessions expire after inactivity
  • Timeout configurable (default: 30 minutes)
  • Expired sessions rejected

Timeline:

00:00 - Login
00:15 - Action call (session refreshed)
00:45 - No activity for 30 minutes
00:46 - Action call → 401 Session Expired

7. Content Security Policy (2 tests)

Purpose: Enforce CSP headers to prevent injection attacks.

Test: CSP headers present

Validates:

  • Content-Security-Policy header set
  • Proper directives configured
  • No unsafe configurations

Expected Header:

Content-Security-Policy:
    default-src 'self';
    script-src 'self' 'nonce-xyz123';
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https:;
    connect-src 'self' wss://localhost;
    font-src 'self' https://fonts.gstatic.com;

Test: Block inline scripts via CSP

Validates:

  • Inline scripts blocked by CSP
  • Console errors for violations
  • CSP reports generated

Violation Detection:

// Attempt to inject inline script
const script = document.createElement('script');
script.textContent = 'alert("XSS")';
document.body.appendChild(script);

// Expected Console Error:
// "Refused to execute inline script because it violates
//  Content Security Policy directive: 'script-src self'"

Test Page Requirements

Tests assume the following test page at https://localhost/livecomponents/test/security:

Required HTML Elements

<div data-component-id="counter:test">
    <div id="counter-value">0</div>
    <button id="trigger-action">Trigger Action</button>
    <div class="action-success" style="display:none">Success</div>
    <div class="error-message" style="display:none"></div>
    <div class="rate-limit-error" style="display:none" data-retry-after=""></div>
</div>

<div data-component-id="text:test">
    <div id="text-display"></div>
</div>

<div data-component-id="admin:test">
    <div class="authorization-error" style="display:none"></div>
</div>

<!-- Login Form -->
<form id="login-form">
    <input type="text" id="username" name="username" />
    <input type="password" id="password" name="password" />
    <button type="submit" id="login-btn">Login</button>
</form>

<div class="logged-in-indicator" style="display:none"></div>
<button id="logout-btn" style="display:none">Logout</button>

<!-- CSRF Token -->
<meta name="csrf-token" content="generated-token-value">

Required Component Actions

final readonly class SecurityTestComponent extends LiveComponent
{
    #[Action]
    #[RateLimit(requests: 10, window: 60)]
    public function triggerAction(): array
    {
        return ['success' => true];
    }

    #[Action]
    public function increment(string $idempotencyKey = null): array
    {
        // Idempotent increment
        if ($idempotencyKey && $this->hasExecuted($idempotencyKey)) {
            return $this->getCachedResult($idempotencyKey);
        }

        $newValue = $this->state->get('counter') + 1;
        $this->state->set('counter', $newValue);

        $result = ['value' => $newValue];

        if ($idempotencyKey) {
            $this->cacheResult($idempotencyKey, $result);
        }

        return $result;
    }

    #[Action]
    public function setText(string $text): array
    {
        // Sanitize HTML
        $sanitizedText = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
        $this->state->set('text', $sanitizedText);

        return ['text' => $sanitizedText];
    }

    #[Action]
    #[RequiresAuth]
    #[RequiresRole('admin')]
    public function performAdminAction(): array
    {
        return ['success' => true];
    }
}

Configuration

Environment Variables

# CSRF Protection
CSRF_TOKEN_TTL=7200                  # 2 hours
CSRF_ROTATE_ON_ACTION=false          # Rotate token after each action

# Rate Limiting
RATE_LIMIT_ENABLED=true
RATE_LIMIT_DEFAULT=60                # Requests per window
RATE_LIMIT_WINDOW=60                 # Window in seconds
RATE_LIMIT_STORAGE=redis             # Storage backend

# Idempotency
IDEMPOTENCY_ENABLED=true
IDEMPOTENCY_TTL=86400                # 24 hours
IDEMPOTENCY_STORAGE=redis

# Session Security
SESSION_LIFETIME=1800                # 30 minutes
SESSION_SECURE=true                  # HTTPS only
SESSION_HTTP_ONLY=true               # No JavaScript access
SESSION_SAME_SITE=strict             # SameSite cookie attribute
SESSION_BIND_IP=true                 # Bind to IP address
SESSION_BIND_USER_AGENT=true         # Bind to User-Agent

# Content Security Policy
CSP_ENABLED=true
CSP_REPORT_ONLY=false                # Enforce CSP (not just report)
CSP_REPORT_URI=/csp-report           # CSP violation reporting endpoint

Troubleshooting

CSRF Tests Failing

Symptoms:

  • CSRF token not found in requests
  • All CSRF tests failing

Solutions:

  1. Verify CSRF middleware enabled:
// In middleware stack
new CsrfMiddleware($csrfTokenGenerator)
  1. Check meta tag presence:
<meta name="csrf-token" content="...">
  1. Verify JavaScript includes token:
const token = document.querySelector('meta[name="csrf-token"]').content;
// Include in requests

Rate Limiting Not Working

Symptoms:

  • All rapid requests succeed
  • No 429 responses

Solutions:

  1. Check rate limit configuration:
# Verify environment variables
docker exec php php -r "echo getenv('RATE_LIMIT_ENABLED');"
  1. Verify rate limiter initialized:
// Check DI container
$rateLimiter = $container->get(RateLimiter::class);
  1. Check storage backend:
# For Redis backend
docker exec redis redis-cli KEYS "rate_limit:*"

Idempotency Issues

Symptoms:

  • Duplicate requests execute
  • Idempotency keys not working

Solutions:

  1. Check idempotency key format:
// Must be unique per operation
idempotencyKey: `${userId}-${operationId}-${timestamp}`
  1. Verify storage:
# For Redis
docker exec redis redis-cli KEYS "idempotency:*"
  1. Check TTL:
# Verify keys not expiring too quickly
docker exec redis redis-cli TTL "idempotency:key-123"

Authorization Failures

Symptoms:

  • Authorized users rejected
  • Role checks failing

Solutions:

  1. Verify session active:
const sessionActive = await page.evaluate(() => {
    return window.__sessionActive === true;
});
  1. Check role assignment:
// Verify user roles
$user->hasRole('admin'); // Should return true
  1. Review authorization middleware:
// Ensure middleware in correct order
[AuthMiddleware, RoleMiddleware, ...]

CI/CD Integration

GitHub Actions

name: Security Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * *'  # Daily

jobs:
  security-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright
        run: npx playwright install --with-deps chromium

      - name: Start dev server
        run: make up

      - name: Run security tests
        run: npm run test:security

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: security-test-results
          path: test-results/

      - name: Security alert on failure
        if: failure()
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: '🚨 Security Tests Failed',
              labels: ['security', 'urgent'],
              body: 'Security tests failed. Immediate review required.'
            });

Best Practices

1. Regular Security Testing

  • Run security tests before every deployment
  • Include in CI/CD pipeline
  • Monitor for new vulnerabilities
  • Update tests for new attack vectors

2. Defense in Depth

  • Multiple security layers (CSRF + Rate Limit + Auth)
  • Fail securely (block by default, allow explicitly)
  • Log all security events
  • Alert on suspicious patterns

3. Security Monitoring

  • Track failed authentication attempts
  • Monitor rate limit violations
  • Alert on XSS attempts
  • Log authorization failures

4. Incident Response

  • Defined escalation procedures
  • Security event playbooks
  • Regular security drills
  • Post-incident analysis

Resources

Support

For security issues or questions:

  1. Review this documentation
  2. Check framework security documentation
  3. Consult OWASP guidelines
  4. Report security vulnerabilities privately to security@example.com
  5. Create GitHub issue for non-security test failures