Resource Auditing Techniques
Resource Auditing Techniques
Comprehensive resource auditing forms the foundation of effective CSP implementation. Use automated tools and manual inspection to create a complete inventory of your application's resource requirements.
Browser-based audit script:
// Comprehensive Resource Auditor
class ResourceAuditor {
constructor() {
this.resources = {
scripts: new Map(),
styles: new Map(),
images: new Map(),
fonts: new Map(),
media: new Map(),
frames: new Map(),
connects: new Map(),
inlineScripts: [],
inlineStyles: []
};
this.startAudit();
}
startAudit() {
// Monitor resource loading
this.observeResources();
// Scan existing resources
this.scanExistingResources();
// Monitor dynamic resource loading
this.monitorDynamicLoading();
// Check for inline content
this.scanInlineContent();
}
observeResources() {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.categorizeResource(entry);
}
});
observer.observe({ entryTypes: ['resource'] });
}
categorizeResource(entry) {
const url = new URL(entry.name);
const origin = url.origin;
if (entry.initiatorType === 'script') {
this.resources.scripts.set(entry.name, {
origin: origin,
loadTime: entry.responseEnd - entry.startTime,
size: entry.transferSize
});
} else if (entry.initiatorType === 'css' || entry.initiatorType === 'link') {
this.resources.styles.set(entry.name, {
origin: origin,
loadTime: entry.responseEnd - entry.startTime
});
} else if (entry.initiatorType === 'img') {
this.resources.images.set(entry.name, {
origin: origin,
type: this.getImageType(entry.name)
});
}
}
scanExistingResources() {
// Scripts
document.querySelectorAll('script').forEach(script => {
if (script.src) {
const url = new URL(script.src, window.location.origin);
this.resources.scripts.set(url.href, {
origin: url.origin,
async: script.async,
defer: script.defer
});
} else if (script.textContent.trim()) {
this.resources.inlineScripts.push({
content: script.textContent.substring(0, 100) + '...',
hash: this.calculateHash(script.textContent)
});
}
});
// Stylesheets
document.querySelectorAll('link[rel="stylesheet"]').forEach(link => {
const url = new URL(link.href, window.location.origin);
this.resources.styles.set(url.href, {
origin: url.origin,
media: link.media
});
});
// Inline styles
document.querySelectorAll('style').forEach(style => {
this.resources.inlineStyles.push({
content: style.textContent.substring(0, 100) + '...',
hash: this.calculateHash(style.textContent)
});
});
}
monitorDynamicLoading() {
// Override fetch to monitor API calls
const originalFetch = window.fetch;
window.fetch = (...args) => {
const url = args[0];
if (typeof url === 'string') {
const urlObj = new URL(url, window.location.origin);
this.resources.connects.set(urlObj.origin, {
type: 'fetch',
count: (this.resources.connects.get(urlObj.origin)?.count || 0) + 1
});
}
return originalFetch.apply(this, args);
};
// Monitor XMLHttpRequest
const originalXHR = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function(method, url) {
const urlObj = new URL(url, window.location.origin);
this.resources.connects.set(urlObj.origin, {
type: 'xhr',
count: (this.resources.connects.get(urlObj.origin)?.count || 0) + 1
});
return originalXHR.apply(this, arguments);
};
}
calculateHash(content) {
// Simple hash calculation for demo - use crypto.subtle in production
return 'sha256-' + btoa(content).substring(0, 20) + '...';
}
generateReport() {
const report = {
summary: {
totalScripts: this.resources.scripts.size,
totalStyles: this.resources.styles.size,
inlineScripts: this.resources.inlineScripts.length,
inlineStyles: this.resources.inlineStyles.length,
uniqueOrigins: this.getUniqueOrigins().size
},
suggestedPolicy: this.generateSuggestedPolicy(),
details: this.resources
};
return report;
}
generateSuggestedPolicy() {
const policy = {
'default-src': ["'self'"],
'script-src': ["'self'"],
'style-src': ["'self'"],
'img-src': ["'self'"],
'font-src': ["'self'"],
'connect-src': ["'self'"]
};
// Add discovered origins
this.resources.scripts.forEach((info, url) => {
if (info.origin !== window.location.origin) {
policy['script-src'].push(info.origin);
}
});
// Handle inline content
if (this.resources.inlineScripts.length > 0) {
policy['script-src'].push("'unsafe-inline'"); // To be replaced with nonces
}
if (this.resources.inlineStyles.length > 0) {
policy['style-src'].push("'unsafe-inline'"); // To be replaced with nonces
}
return this.formatPolicy(policy);
}
formatPolicy(policy) {
return Object.entries(policy)
.map(([directive, sources]) => `${directive} ${sources.join(' ')}`)
.join('; ');
}
}
// Usage
const auditor = new ResourceAuditor();
setTimeout(() => {
console.log('CSP Audit Report:', auditor.generateReport());
}, 5000);