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