Skip to content

Home > Documentation > Security Guide

Security Guide

Parley-js implements security-first design for cross-window communication. This document outlines security features, best practices, and testing strategies.

Overview

Cross-window communication via postMessage has inherent security risks. Parley-js is designed with security as a primary concern, implementing multiple layers of protection:

  • Origin Validation - Strict origin whitelist enforcement
  • Payload Sanitization - XSS/injection prevention
  • DoS Prevention - Size limits, rate limiting, deep nesting protection
  • Message Validation - Protocol structure validation
  • Error Safety - No sensitive information disclosure

Table of Contents

  1. Threat Model
  2. Core Security Features
  3. Security Best Practices
  4. Security Testing
  5. Attack Prevention
  6. Reporting Security Issues

Threat Model

Potential Attack Vectors

  1. Origin Spoofing - Malicious sites attempting to send messages
  2. Message Injection - Injecting malicious payloads (XSS, code execution)
  3. Data Exfiltration - Stealing sensitive data via cross-origin messages
  4. Clickjacking - Embedding your app in a malicious iframe
  5. Replay Attacks - Re-sending captured messages
  6. Denial of Service - Flooding with messages, resource exhaustion
  7. Information Disclosure - Leaking sensitive data through error messages

Core Security Features

For secure coding patterns, see Code Patterns. For security testing, see Testing Patterns.

1. Origin Validation

Always configure allowed origins:

typescript
const parley = Parley.create({
    // Good: Explicit allowlist
    allowedOrigins: ['https://app.example.com', 'https://admin.example.com'],
});

Never use wildcards in production:

typescript
// DANGEROUS: Allows any origin
allowedOrigins: ['*'];

// DANGEROUS: Too broad
allowedOrigins: ['*.com'];

// CAREFUL: Only if you trust all subdomains
allowedOrigins: ['*.example.com'];

Origin validation features:

  • Protocol matching (https:// vs http://)
  • Port number validation
  • Subdomain bypass prevention
  • Null origin rejection

2. Payload Sanitization

Parley automatically sanitizes all payloads:

typescript
// Removes dangerous content:
// - Functions
// - Symbols
// - Circular references
// - __proto__ and constructor properties

const sanitized = sanitizePayload({
    data: 'safe',
    dangerous: function () {
        /* removed */
    },
    circular: obj, // handled safely
});

3. Protocol Enforcement

By default, Parley validates message structure:

typescript
// Valid message structure (enforced internally)
{
    _v: '1.0.0',
    _id: 'uuid-string',
    _type: 'message-type',
    _timestamp: 1234567890,
    _origin: 'https://trusted.com',
    _expectsResponse: true,
    payload: { /* user data */ }
}

Any message not matching this structure is rejected.

4. DoS Prevention

Size Limits:

typescript
const parley = Parley.create({
    allowedOrigins: ['https://trusted.com'],
    maxPayloadSize: 1024 * 1024, // 1MB limit
});

Deep Nesting Protection:

typescript
// Automatically rejects deeply nested objects
const rejected = {
    a: {
        b: {
            c: {
                d: {
                    e: {
                        f: {
                            /* too deep */
                        },
                    },
                },
            },
        },
    },
};

Listener Limits:

typescript
// Prevents memory leaks from excessive listeners
const MAX_LISTENERS = 100;
emitter.setMaxListeners(MAX_LISTENERS);

5. Timestamp Validation

Parley can reject stale messages:

typescript
const parley = Parley.create({
    allowedOrigins: ['https://trusted.com'],
    // Reject messages older than 30 seconds
    maxMessageAge: 30000,
});

6. Schema Validation

Validate payloads against JSON Schema:

typescript
parley.register('sensitive-action', handler, {
    requestSchema: {
        type: 'object',
        properties: {
            userId: { type: 'string', pattern: '^[a-z0-9]{24}$' },
            action: { type: 'string', enum: ['read', 'write', 'delete'] },
        },
        required: ['userId', 'action'],
        additionalProperties: false, // Reject unknown properties
    },
});

7. Error Information Disclosure Prevention

Parley sanitizes error messages to prevent information leakage:

typescript
// Production mode: Generic error messages
try {
    await parley.send('action', data);
} catch (error) {
    // User sees: "Validation failed"
    // Logs contain: Full details for debugging
}

Security Best Practices

1. Always Use HTTPS

typescript
// Always use https:// in production
allowedOrigins: ['https://secure.example.com'];

// Never use http:// in production
allowedOrigins: ['http://insecure.example.com'];

2. Minimal Origin List

Only allow origins that absolutely need to communicate:

typescript
// Good: Minimal, explicit list
allowedOrigins: ['https://app.mycompany.com', 'https://widget.mycompany.com'];

// Bad: Overly permissive
allowedOrigins: [
    'https://app.mycompany.com',
    'https://widget.mycompany.com',
    'https://dev.mycompany.com', // Should be separate config
    'https://staging.mycompany.com', // Should be separate config
    'https://partner1.com', // Are you sure?
    'https://partner2.com', // Are you sure?
];

3. Validate All Incoming Data

Never trust incoming payloads:

typescript
parley.register('update-user', async (payload, metadata) => {
    // Always validate
    if (!isValidUserId(payload.userId)) {
        throw new ValidationError('Invalid user ID');
    }

    // Sanitize input
    const sanitizedName = sanitizeString(payload.name);

    // Check permissions
    if (!canUserUpdate(metadata.origin, payload.userId)) {
        throw new SecurityError('Unauthorized');
    }

    // Now safe to process
    return updateUser(payload.userId, sanitizedName);
});

4. Limit Sensitive Operations

Don't expose dangerous operations via postMessage:

typescript
// DANGEROUS: Direct database access
parley.register('execute-query', (payload) => {
    return db.query(payload.sql); // SQL injection risk!
});

// SAFE: Predefined operations only
parley.register('get-user', (payload) => {
    return db.users.findById(payload.userId);
});

5. Rate Limiting

Implement rate limiting for sensitive operations:

typescript
const rateLimiter = new RateLimiter({
    windowMs: 60000, // 1 minute
    maxRequests: 100,
});

parley.register('api-call', async (payload, metadata) => {
    if (!rateLimiter.allow(metadata.origin)) {
        throw new Error('Rate limit exceeded');
    }
    return makeApiCall(payload);
});

6. Logging and Monitoring

Log security-relevant events:

typescript
parley.onSystem('error:security', (event) => {
    securityLog.warn('Security violation', {
        origin: event.origin,
        reason: event.reason,
        timestamp: Date.now(),
    });

    // Alert on repeated violations
    if (getViolationCount(event.origin) > 10) {
        alertSecurityTeam(event);
    }
});

parley.onAnalytics((event) => {
    // Track all cross-origin communication
    auditLog.info('Cross-origin message', event);
});

Security Checklist

Before deploying to production:

  • [ ] HTTPS only - All allowed origins use HTTPS
  • [ ] Explicit origins - No wildcards or *
  • [ ] Schema validation - All handlers validate input
  • [ ] Error handling - Errors don't leak sensitive info
  • [ ] Rate limiting - Implemented for sensitive operations
  • [ ] Logging - Security events are logged
  • [ ] Monitoring - Alerts for suspicious activity
  • [ ] CSP headers - Content Security Policy configured
  • [ ] X-Frame-Options - Clickjacking protection
  • [ ] Testing - Security tested with various attack payloads

Security Testing

Parley-js includes comprehensive security tests to validate protection mechanisms. See TESTING.md for complete testing documentation.

Security Test Suite

Located in tests/security/, these tests verify:

  1. Origin Validation (origin-validation.test.ts)

    • Unauthorized origin rejection
    • Protocol matching enforcement
    • Subdomain bypass prevention
    • Null origin handling
  2. Payload Sanitization (payload-sanitization.test.ts)

    • XSS prevention
    • Code injection prevention
    • Circular reference handling
    • Function/symbol removal
  3. PostMessage Security (postmessage-security.test.ts)

    • Wildcard prevention in targetOrigin
    • Specific origin enforcement
    • Null origin handling
  4. Schema Validation DoS (schema-validation-dos.test.ts)

    • Deep nesting protection
    • Regex DoS prevention
    • Resource exhaustion prevention
  5. Payload Size Limits (payload-size-limits.test.ts)

    • Size limit enforcement
    • Nested payload calculation
    • Clear error messaging
  6. Listener Limits (listener-limits.test.ts)

    • Memory leak prevention
    • Per-event listener limits
    • Cleanup on removal
  7. Message Validation (message-validation.test.ts)

    • Required field validation
    • Field type validation
    • Timestamp validation
  8. Error Info Disclosure (error-info-disclosure.test.ts)

    • Stack trace sanitization
    • Error code usage
    • Sensitive information filtering

Running Security Tests

bash
# Run all security tests
npm test tests/security/

# Run specific security test
npm test tests/security/origin-validation.test.ts

# Run with coverage
npm run test:coverage -- tests/security/

Coverage Requirements

Security layer tests maintain 95%+ coverage to ensure comprehensive protection.


Attack Prevention

Tested Attack Scenarios

Parley-js has been tested against:

  1. XSS Attacks

    typescript
    // Automatically sanitized
    const payload = {
        xss: '<script>alert("XSS")</script>',
        dangerous: 'javascript:void(0)',
    };
  2. Code Injection

    typescript
    // Functions are removed
    const payload = {
        callback: function () {
            maliciousCode();
        },
    };
  3. Prototype Pollution

    typescript
    // __proto__ and constructor are blocked
    const payload = {
        __proto__: { admin: true },
        constructor: { prototype: { isAdmin: true } },
    };
  4. DoS via Deep Nesting

    typescript
    // Rejected if exceeds max depth
    const payload = createDeeplyNested(1000);
  5. DoS via Large Payloads

    typescript
    // Rejected if exceeds size limit
    const payload = { data: 'x'.repeat(10_000_000) };
  6. Origin Spoofing

    typescript
    // Origin validation prevents spoofing
    event.origin; // validated against allowedOrigins

Content Security Policy (CSP)

Configure CSP to restrict iframe sources:

http
Content-Security-Policy: frame-ancestors 'self' https://trusted-parent.com
html
<!-- Or via meta tag -->
<meta
    http-equiv="Content-Security-Policy"
    content="frame-ancestors 'self' https://trusted-parent.com"
/>

Clickjacking Protection

If your app should not be embedded:

http
X-Frame-Options: DENY

If only specific parents are allowed:

http
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'

Custom Security Layer

For advanced use cases, implement a custom security layer:

typescript
import { SecurityLayer, Parley } from 'parley-js';

class EnterpriseSecurityLayer implements SecurityLayer {
    private allowedOrigins: Set<string>;
    private blockedOrigins: Set<string>;

    constructor(config: SecurityConfig) {
        this.allowedOrigins = new Set(config.allowedOrigins);
        this.blockedOrigins = new Set(config.blockedOrigins || []);
    }

    validateOrigin(origin: string): boolean {
        // Check blocklist first
        if (this.blockedOrigins.has(origin)) {
            this.logViolation('blocked_origin', origin);
            return false;
        }

        // Check allowlist
        if (!this.allowedOrigins.has(origin)) {
            this.logViolation('unknown_origin', origin);
            return false;
        }

        return true;
    }

    validateMessage(message: unknown, origin: string): boolean {
        // Custom validation logic
        if (!this.isValidStructure(message)) {
            return false;
        }

        // Check for suspicious patterns
        if (this.containsSuspiciousContent(message)) {
            this.logViolation('suspicious_content', origin);
            return false;
        }

        return true;
    }

    private logViolation(type: string, origin: string): void {
        // Log to security monitoring
    }
}

const parley = Parley.create({
    allowedOrigins: ['https://trusted.com'],
    securityLayer: new EnterpriseSecurityLayer({
        allowedOrigins: ['https://trusted.com'],
        blockedOrigins: ['https://known-bad.com'],
    }),
});

Common Vulnerabilities and Mitigations

1. Prototype Pollution

typescript
// Vulnerable: Direct property access
function processPayload(payload: any) {
    for (const key in payload) {
        this.config[key] = payload[key]; // Prototype pollution!
    }
}

// Safe: Validate properties
function processPayload(payload: unknown) {
    if (!isPlainObject(payload)) return;

    const allowedKeys = ['name', 'value'];
    for (const key of allowedKeys) {
        if (key in payload) {
            this.config[key] = (payload as any)[key];
        }
    }
}

Reporting Security Issues

Do not report security vulnerabilities through public GitHub issues.

Instead, please email security issues to: security@igniteworks.com

Include:

  • Description of the vulnerability
  • Steps to reproduce
  • Potential impact
  • Any suggested fixes

We will respond within 48 hours and work with you to address the issue.


Security Checklist

Before deploying to production:

  • [ ] Origin validation configured with explicit allowlist
  • [ ] HTTPS only for all communication
  • [ ] Schema validation for sensitive message types
  • [ ] Size limits configured appropriately
  • [ ] Error handling does not expose sensitive information
  • [ ] CSP headers configured to restrict iframe sources
  • [ ] X-Frame-Options set if embedding not allowed
  • [ ] Security tests passing with 95%+ coverage
  • [ ] Logging/monitoring configured for security events
  • [ ] Regular updates to latest Parley-js version

Resources


Questions? Open an issue or contact us at security@igniteworks.com

2. eval() and Function()

typescript
// NEVER: Execute code from messages
parley.register('execute', (payload) => {
    eval(payload.code); // Remote code execution!
});

// NEVER: Dynamic function creation
parley.register('run', (payload) => {
    new Function(payload.body)(); // Remote code execution!
});

3. innerHTML

typescript
// Vulnerable: XSS
parley.register('update-ui', (payload) => {
    document.getElementById('output').innerHTML = payload.html;
});

// Safe: Use textContent or sanitize
parley.register('update-ui', (payload) => {
    document.getElementById('output').textContent = payload.text;
});

// Safe: Sanitize HTML
parley.register('update-ui', (payload) => {
    const sanitized = DOMPurify.sanitize(payload.html);
    document.getElementById('output').innerHTML = sanitized;
});

Incident Response

If you detect a security incident:

  1. Log the details - Capture origin, payload, timestamp
  2. Block the origin - Add to blocklist immediately
  3. Alert the team - Notify security team
  4. Investigate - Determine the scope
  5. Remediate - Fix vulnerabilities
  6. Review - Update security policies
typescript
class IncidentHandler {
    onSecurityViolation(event: SecurityEvent) {
        // 1. Log
        this.logger.error('Security violation', event);

        // 2. Block
        this.securityLayer.blockOrigin(event.origin);

        // 3. Alert
        this.alertService.notify('security-team', {
            type: 'cross-origin-attack',
            details: event,
        });

        // 4. Disconnect
        parley.disconnect(event.targetId);
    }
}

Further Reading


See Also

Back to: Documentation Home

Released under the MIT License.