Security and Content Security Policy

1. Prototype Pollution Prevention in Polyfills

Vulnerability Attack Vector Prevention Impact
Prototype Pollution Modify Object.prototype or __proto__ Validate keys, use Object.create(null) High - Can bypass security
Constructor Pollution Modify constructor.prototype Check for reserved keys High - Code execution
Property Injection Add malicious properties Use hasOwnProperty checks Medium - Data corruption
__proto__ Access Direct prototype manipulation Blacklist __proto__, constructor High - Security bypass
Deep Merge Exploits Nested object pollution Recursive key validation High - Full prototype chain

Example: Prototype pollution prevention

// Unsafe Object.assign polyfill (vulnerable)
if (!Object.assign) {
    Object.assign = function(target) {
        // VULNERABLE: No key validation
        for (var i = 1; i < arguments.length; i++) {
            var source = arguments[i];
            
            for (var key in source) {
                target[key] = source[key]; // DANGEROUS!
            }
        }
        return target;
    };
}

// SAFE Object.assign polyfill
if (!Object.assign) {
    Object.assign = function(target) {
        'use strict';
        
        if (target == null) {
            throw new TypeError('Cannot convert undefined or null to object');
        }
        
        var to = Object(target);
        
        for (var index = 1; index < arguments.length; index++) {
            var nextSource = arguments[index];
            
            if (nextSource != null) {
                for (var nextKey in nextSource) {
                    // SAFE: Check hasOwnProperty
                    if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                        // SAFE: Check for dangerous keys
                        if (nextKey !== '__proto__' && 
                            nextKey !== 'constructor' && 
                            nextKey !== 'prototype') {
                            to[nextKey] = nextSource[nextKey];
                        }
                    }
                }
            }
        }
        
        return to;
    };
}

// Utility function to check for dangerous keys
function isSafeKey(key) {
    var dangerousKeys = ['__proto__', 'constructor', 'prototype'];
    return dangerousKeys.indexOf(key) === -1;
}

// Safe deep merge implementation
function safeMerge(target, source) {
    if (typeof target !== 'object' || typeof source !== 'object') {
        return source;
    }
    
    for (var key in source) {
        // Validate key is safe
        if (!Object.prototype.hasOwnProperty.call(source, key)) {
            continue;
        }
        
        if (!isSafeKey(key)) {
            console.warn('Dangerous key blocked:', key);
            continue;
        }
        
        var sourceValue = source[key];
        
        if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue)) {
            // Recursive merge for nested objects
            target[key] = safeMerge(target[key] || {}, sourceValue);
        } else {
            target[key] = sourceValue;
        }
    }
    
    return target;
}

// Usage - blocks prototype pollution
var malicious = JSON.parse('{"__proto__": {"polluted": true}}');
var obj = {};

safeMerge(obj, malicious);
console.log(obj.polluted); // undefined (blocked)
console.log({}.polluted); // undefined (prototype not polluted)

Example: Creating pollution-proof objects

// Create objects without prototype chain
var SafeObject = {
    create: function() {
        // Creates object with no prototype
        return Object.create(null);
    },
    
    set: function(obj, key, value) {
        // Safe property setter
        if (!isSafeKey(key)) {
            throw new Error('Unsafe key: ' + key);
        }
        
        obj[key] = value;
        return obj;
    },
    
    get: function(obj, key) {
        // Safe property getter
        if (!Object.prototype.hasOwnProperty.call(obj, key)) {
            return undefined;
        }
        return obj[key];
    },
    
    freeze: function(obj) {
        // Prevent modifications
        return Object.freeze(obj);
    }
};

// Usage
var safeConfig = SafeObject.create();
SafeObject.set(safeConfig, 'apiKey', 'secret123');
console.log(SafeObject.get(safeConfig, 'apiKey')); // 'secret123'

// Attempting pollution fails
try {
    SafeObject.set(safeConfig, '__proto__', { polluted: true });
} catch(e) {
    console.log('Pollution prevented:', e.message);
}

// Freeze object to prevent any changes
SafeObject.freeze(safeConfig);
Warning: Always validate keys in polyfills that merge or assign properties. Block __proto__, constructor, and prototype to prevent prototype pollution attacks.

2. CSP-Compatible Polyfill Implementation

CSP Directive Impact on Polyfills Workaround Support
script-src 'unsafe-eval' Blocks eval(), Function() Pre-compile or avoid eval Required for some polyfills
script-src 'unsafe-inline' Blocks inline scripts External scripts only Use nonce or hash
style-src 'unsafe-inline' Blocks inline styles External stylesheets CSS polyfills affected
script-src 'nonce-*' Requires nonce attribute Add nonce to polyfill script Secure alternative
strict-dynamic Propagates trust Dynamically loaded scripts inherit Modern CSP

Example: CSP-compatible polyfill patterns

// BAD: Uses eval (blocked by CSP)
function badPolyfill(code) {
    eval(code); // Blocked by CSP
}

// BAD: Uses Function constructor (blocked by CSP)
function badDynamicFunction(param, body) {
    return new Function(param, body); // Blocked by CSP
}

// GOOD: Pre-compiled function
function cspSafePolyfill() {
    // All code is pre-defined, no eval needed
    if (!Array.prototype.includes) {
        Array.prototype.includes = function(searchElement, fromIndex) {
            'use strict';
            
            var O = Object(this);
            var len = parseInt(O.length) || 0;
            
            if (len === 0) {
                return false;
            }
            
            var n = parseInt(fromIndex) || 0;
            var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
            
            while (k < len) {
                if (O[k] === searchElement) {
                    return true;
                }
                k++;
            }
            
            return false;
        };
    }
}

// CSP-compatible dynamic injection
function injectPolyfillScript(src, nonce) {
    var script = document.createElement('script');
    script.src = src;
    script.async = true;
    
    // Add nonce for CSP compliance
    if (nonce) {
        script.setAttribute('nonce', nonce);
    }
    
    // Add integrity hash for subresource integrity
    if (src.indexOf('cdn') >= 0) {
        script.integrity = 'sha384-...'; // SRI hash
        script.crossOrigin = 'anonymous';
    }
    
    document.head.appendChild(script);
}

// Get nonce from existing script
function getCurrentNonce() {
    var scripts = document.getElementsByTagName('script');
    
    for (var i = 0; i < scripts.length; i++) {
        var nonce = scripts[i].getAttribute('nonce');
        if (nonce) {
            return nonce;
        }
    }
    
    return null;
}

// Usage
var nonce = getCurrentNonce();
injectPolyfillScript('https://cdn.polyfill.io/v3/polyfill.min.js', nonce);

Example: CSP-safe CSS injection

// CSP-safe style injection
var CSSInjector = {
    nonce: null,
    
    init: function() {
        this.nonce = this.getNonce();
    },
    
    getNonce: function() {
        // Try to get nonce from existing style tags
        var styles = document.getElementsByTagName('style');
        
        for (var i = 0; i < styles.length; i++) {
            var nonce = styles[i].getAttribute('nonce');
            if (nonce) return nonce;
        }
        
        // Try to get from script tags
        var scripts = document.getElementsByTagName('script');
        
        for (var i = 0; i < scripts.length; i++) {
            var nonce = scripts[i].getAttribute('nonce');
            if (nonce) return nonce;
        }
        
        return null;
    },
    
    inject: function(css, id) {
        // Check if already injected
        if (id && document.getElementById(id)) {
            return;
        }
        
        var style = document.createElement('style');
        
        if (id) {
            style.id = id;
        }
        
        // Add nonce for CSP compliance
        if (this.nonce) {
            style.setAttribute('nonce', this.nonce);
        }
        
        style.textContent = css;
        document.head.appendChild(style);
        
        return style;
    },
    
    injectExternal: function(href) {
        var link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = href;
        
        // Add integrity for CDN resources
        if (href.indexOf('cdn') >= 0) {
            link.integrity = 'sha384-...';
            link.crossOrigin = 'anonymous';
        }
        
        document.head.appendChild(link);
    }
};

// Initialize
CSSInjector.init();

// Inject styles with nonce
CSSInjector.inject(`
    .polyfilled-class {
        display: flex;
        flex-direction: column;
    }
`, 'flexbox-polyfill-styles');
Note: For strict CSP compliance, use nonce-based scripts and avoid eval/Function. Pre-compile polyfills at build time or use external scripts with SRI hashes.

3. Safe eval() Alternatives and Function Constructors

Unsafe Pattern Risk Safe Alternative Use Case
eval(code) Arbitrary code execution JSON.parse() for data Parsing trusted data
new Function(body) Dynamic code creation Pre-defined functions Fixed logic only
setTimeout(string) Eval-like behavior setTimeout(function) Delayed execution
setInterval(string) Eval-like behavior setInterval(function) Repeated execution
innerHTML with scripts Script injection textContent or DOMPurify Content sanitization

Example: Safe alternatives to eval()

// UNSAFE: Using eval
function unsafeCalculate(expression) {
    return eval(expression); // DANGEROUS!
}

// SAFE: Limited expression evaluator
function safeCalculate(expression) {
    // Whitelist approach - only allow safe operations
    var sanitized = expression.replace(/[^0-9+\-*/(). ]/g, '');
    
    if (sanitized !== expression) {
        throw new Error('Invalid expression');
    }
    
    // Use Function constructor with validation (still requires CSP 'unsafe-eval')
    try {
        var fn = new Function('return ' + sanitized);
        return fn();
    } catch(e) {
        throw new Error('Calculation error: ' + e.message);
    }
}

// SAFER: Parser-based approach (no eval)
function safeMathEvaluator(expression) {
    var tokens = tokenize(expression);
    return evaluate(tokens);
    
    function tokenize(expr) {
        var regex = /(\d+\.?\d*|[+\-*/()])/g;
        return expr.match(regex) || [];
    }
    
    function evaluate(tokens) {
        var index = 0;
        
        function parseExpression() {
            var left = parseTerm();
            
            while (index < tokens.length && 
                   (tokens[index] === '+' || tokens[index] === '-')) {
                var op = tokens[index++];
                var right = parseTerm();
                left = op === '+' ? left + right : left - right;
            }
            
            return left;
        }
        
        function parseTerm() {
            var left = parseFactor();
            
            while (index < tokens.length && 
                   (tokens[index] === '*' || tokens[index] === '/')) {
                var op = tokens[index++];
                var right = parseFactor();
                left = op === '*' ? left * right : left / right;
            }
            
            return left;
        }
        
        function parseFactor() {
            if (tokens[index] === '(') {
                index++;
                var result = parseExpression();
                index++; // skip ')'
                return result;
            }
            
            return parseFloat(tokens[index++]);
        }
        
        return parseExpression();
    }
}

// Usage
console.log(safeMathEvaluator('2 + 3 * 4')); // 14
console.log(safeMathEvaluator('(2 + 3) * 4')); // 20

// SAFE: JSON parsing instead of eval
function parseData(jsonString) {
    // NEVER use eval for JSON
    try {
        return JSON.parse(jsonString);
    } catch(e) {
        console.error('Invalid JSON:', e);
        return null;
    }
}

Example: Safe setTimeout/setInterval usage

// BAD: String-based setTimeout (eval-like)
setTimeout('console.log("hello")', 1000); // DANGEROUS!

// GOOD: Function-based setTimeout
setTimeout(function() {
    console.log('hello');
}, 1000);

// Safe timer utility
var SafeTimer = {
    setTimeout: function(callback, delay) {
        if (typeof callback !== 'function') {
            throw new TypeError('Callback must be a function');
        }
        
        return window.setTimeout(callback, delay);
    },
    
    setInterval: function(callback, delay) {
        if (typeof callback !== 'function') {
            throw new TypeError('Callback must be a function');
        }
        
        return window.setInterval(callback, delay);
    },
    
    clearTimeout: function(id) {
        return window.clearTimeout(id);
    },
    
    clearInterval: function(id) {
        return window.clearInterval(id);
    }
};

// Usage
var timerId = SafeTimer.setTimeout(function() {
    console.log('Safe timer executed');
}, 1000);

SafeTimer.clearTimeout(timerId);
Warning: Never use eval() or Function() constructor with user-supplied input. Always use JSON.parse() for data and pre-defined functions for logic.

4. Third-party Polyfill Security Assessment

Security Check What to Verify Risk Level Action
Source Verification Official repository, maintainer High Check npm, GitHub reputation
Code Audit Review polyfill implementation High Look for eval, XSS vectors
Dependencies Transitive dependencies count Medium npm audit, dependency tree
Maintenance Status Recent updates, issue response Medium Check last commit date
Bundle Size Unexpectedly large files Low Compare with alternatives
License Compatible with your project Legal Check package.json license

Example: Polyfill security checklist

// Security assessment checklist for polyfills
var PolyfillSecurityChecker = {
    checks: {
        hasEval: function(code) {
            // Check for eval usage
            return /\beval\s*\(/.test(code) || 
                   /new\s+Function\s*\(/.test(code);
        },
        
        hasPrototypePollution: function(code) {
            // Check for unsafe prototype access
            return /__proto__/.test(code) || 
                   /\[['"]constructor['"]\]/.test(code);
        },
        
        hasUnsafeHTMLInjection: function(code) {
            // Check for innerHTML without sanitization
            return /innerHTML\s*=/.test(code) && 
                   !/DOMPurify|sanitize/.test(code);
        },
        
        hasNetworkRequests: function(code) {
            // Check for unexpected network calls
            return /XMLHttpRequest|fetch\(|\.ajax\(/.test(code);
        },
        
        hasGlobalModification: function(code) {
            // Check for window/document modifications
            return /window\s*\[\s*['"]/.test(code) || 
                   /document\s*\[\s*['"]/.test(code);
        }
    },
    
    assess: function(polyfillCode, polyfillName) {
        console.log('Assessing polyfill:', polyfillName);
        
        var issues = [];
        
        for (var checkName in this.checks) {
            var checkFn = this.checks[checkName];
            
            if (checkFn(polyfillCode)) {
                issues.push({
                    check: checkName,
                    severity: this.getSeverity(checkName)
                });
            }
        }
        
        if (issues.length === 0) {
            console.log('✓ No security issues detected');
            return { safe: true, issues: [] };
        } else {
            console.warn('✗ Security issues detected:');
            issues.forEach(function(issue) {
                console.warn('  -', issue.check, '(' + issue.severity + ')');
            });
            return { safe: false, issues: issues };
        }
    },
    
    getSeverity: function(checkName) {
        var severities = {
            hasEval: 'CRITICAL',
            hasPrototypePollution: 'CRITICAL',
            hasUnsafeHTMLInjection: 'HIGH',
            hasNetworkRequests: 'MEDIUM',
            hasGlobalModification: 'LOW'
        };
        
        return severities[checkName] || 'UNKNOWN';
    }
};

// Usage - assess a polyfill before using
var polyfillCode = `
    if (!Array.prototype.includes) {
        Array.prototype.includes = function(searchElement) {
            return this.indexOf(searchElement) !== -1;
        };
    }
`;

var assessment = PolyfillSecurityChecker.assess(polyfillCode, 'Array.prototype.includes');

if (!assessment.safe) {
    console.error('Polyfill failed security assessment');
    assessment.issues.forEach(function(issue) {
        console.error('Issue:', issue.check, '- Severity:', issue.severity);
    });
}
Note: Always audit third-party polyfills before production use. Check for eval usage, prototype pollution, and unexpected network requests. Use npm audit regularly.

5. Supply Chain Security for Polyfill Dependencies

Attack Vector Description Mitigation Tools
Malicious Packages Typosquatting, backdoors Verify package names npm, Socket.dev
Compromised CDN Modified polyfill.io or CDN files Use SRI hashes Subresource Integrity
Dependency Confusion Internal package name collision Private registry, scoped packages npm config
Version Pinning Automatic updates to malicious versions Lock files, exact versions package-lock.json
Transitive Dependencies Vulnerable nested dependencies Audit regularly npm audit, Snyk

Example: Subresource Integrity (SRI) for CDN polyfills

// Load polyfill from CDN with SRI protection
function loadPolyfillWithSRI(url, integrity, callback) {
    var script = document.createElement('script');
    script.src = url;
    script.async = true;
    
    // Add integrity hash for SRI
    script.integrity = integrity;
    script.crossOrigin = 'anonymous';
    
    // Handle load success
    script.onload = function() {
        if (callback) callback(null);
    };
    
    // Handle load failure
    script.onerror = function() {
        console.error('Failed to load polyfill from CDN');
        
        // Fallback to local copy
        loadLocalPolyfill(callback);
    };
    
    document.head.appendChild(script);
}

function loadLocalPolyfill(callback) {
    var script = document.createElement('script');
    script.src = '/js/polyfills/fallback.js';
    script.async = true;
    
    script.onload = function() {
        if (callback) callback(null);
    };
    
    script.onerror = function() {
        console.error('Failed to load local polyfill');
        if (callback) callback(new Error('All polyfill sources failed'));
    };
    
    document.head.appendChild(script);
}

// Usage with SRI hash
loadPolyfillWithSRI(
    'https://cdn.jsdelivr.net/npm/core-js@3.27.2/minified.min.js',
    'sha384-qyF1Z4o7cH2u8gBWpQYY3U0KLTbVjP3xPmj9Qj5gD7Y8FpH4kW5NxZcB1qL9M4jR',
    function(err) {
        if (err) {
            console.error('Polyfill loading failed');
        } else {
            console.log('Polyfill loaded successfully');
        }
    }
);

// Generate SRI hash for local files
// Command: openssl dgst -sha384 -binary polyfill.js | openssl base64 -A

Example: Package verification and lock file usage

// package.json - Pin exact versions
{
  "name": "my-app",
  "dependencies": {
    "core-js": "3.27.2",  // Exact version, not ^3.27.2
    "regenerator-runtime": "0.13.11"
  },
  "scripts": {
    "audit": "npm audit",
    "audit-fix": "npm audit fix",
    "verify": "npm ci" // Use 'ci' for reproducible installs
  }
}

// Security verification script
var crypto = require('crypto');
var fs = require('fs');

function verifyPackageIntegrity(packagePath, expectedHash) {
    var fileBuffer = fs.readFileSync(packagePath);
    var hashSum = crypto.createHash('sha256');
    hashSum.update(fileBuffer);
    
    var hex = hashSum.digest('hex');
    
    if (hex === expectedHash) {
        console.log('✓ Package integrity verified');
        return true;
    } else {
        console.error('✗ Package integrity check FAILED');
        console.error('Expected:', expectedHash);
        console.error('Got:', hex);
        return false;
    }
}

// Verify critical polyfill files
var polyfills = [
    {
        path: './node_modules/core-js/index.js',
        hash: 'abc123...' // Expected SHA-256 hash
    },
    {
        path: './node_modules/whatwg-fetch/fetch.js',
        hash: 'def456...'
    }
];

polyfills.forEach(function(polyfill) {
    verifyPackageIntegrity(polyfill.path, polyfill.hash);
});

// Automated security checks
// Run: npm audit --production
// Run: npm outdated
// Run: snyk test (if using Snyk)
Warning: The polyfill.io CDN service was compromised in 2024. Always use SRI hashes for CDN resources and have local fallbacks. Self-host critical polyfills when possible.

Key Takeaways - Security & CSP

  • Prototype Pollution: Validate keys, block __proto__/constructor/prototype
  • CSP Compliance: Avoid eval/Function, use nonce-based scripts with SRI
  • Safe Alternatives: JSON.parse for data, pre-defined functions for logic
  • Third-party Audit: Review code for eval, XSS, prototype pollution
  • Supply Chain: Use SRI hashes, pin versions, audit dependencies regularly
  • Best Practice: Self-host critical polyfills, maintain local fallbacks