ShowerLoop-cc/collect-all-csp-hashes.js

140 lines
4.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* CSP Hash Collector
*
* This script scans all HTML files in a directory structure,
* extracts inline scripts and styles, and calculates SHA-256 hashes
* for use in Content Security Policy headers.
*
* Usage:
* node collect-all-csp-hashes.js <directory-path>
*
* Example:
* node collect-all-csp-hashes.js ./docker/showerloop/public
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
// Function to calculate CSP hash
function calculateHash(content) {
const hash = crypto.createHash('sha256').update(content, 'utf8').digest('base64');
return `'sha256-${hash}'`;
}
// Function to extract inline scripts from HTML
function extractInlineScripts(html) {
const scriptRegex = /<script(?:\s[^>]*)?>([\s\S]*?)<\/script>/gi;
const scripts = [];
let match;
while ((match = scriptRegex.exec(html)) !== null) {
const scriptContent = match[1].trim();
if (scriptContent && !scriptContent.includes('src=')) {
scripts.push(scriptContent);
}
}
return scripts;
}
// Function to extract inline styles from HTML
function extractInlineStyles(html) {
const styleRegex = /<style(?:\s[^>]*)?>([\s\S]*?)<\/style>/gi;
const styles = [];
let match;
while ((match = styleRegex.exec(html)) !== null) {
const styleContent = match[1].trim();
if (styleContent) {
styles.push(styleContent);
}
}
return styles;
}
// Function to find all HTML files in a directory recursively
function findHtmlFiles(dir, fileList = []) {
const files = fs.readdirSync(dir);
files.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
findHtmlFiles(filePath, fileList);
} else if (path.extname(file).toLowerCase() === '.html') {
fileList.push(filePath);
}
});
return fileList;
}
// Main function
function main() {
if (process.argv.length < 3) {
console.error('Error: Please provide a directory path.');
process.exit(1);
}
const directoryPath = process.argv[2];
try {
console.log(`\nScanning directory: ${directoryPath}`);
const htmlFiles = findHtmlFiles(directoryPath);
console.log(`Found ${htmlFiles.length} HTML files.`);
const scriptHashes = new Set();
const styleHashes = new Set();
let totalScripts = 0;
let totalStyles = 0;
htmlFiles.forEach(filePath => {
try {
const html = fs.readFileSync(filePath, 'utf8');
const scripts = extractInlineScripts(html);
const styles = extractInlineStyles(html);
totalScripts += scripts.length;
totalStyles += styles.length;
scripts.forEach(script => {
const hash = calculateHash(script);
scriptHashes.add(hash);
});
styles.forEach(style => {
const hash = calculateHash(style);
styleHashes.add(hash);
});
} catch (err) {
console.error(`Error processing file ${filePath}: ${err.message}`);
}
});
console.log(`\nProcessed ${totalScripts} inline scripts and ${totalStyles} inline styles.`);
console.log(`Found ${scriptHashes.size} unique script hashes and ${styleHashes.size} unique style hashes.`);
console.log('\n=== Complete CSP Directives ===');
console.log('\nScript CSP directive:');
console.log(`script-src 'self' blob: ${Array.from(scriptHashes).join(' ')}`);
console.log('\nStyle CSP directive:');
console.log(`style-src 'self' 'unsafe-inline' ${Array.from(styleHashes).join(' ')}`);
console.log('\nComplete CSP header:');
console.log(`Content-Security-Policy: default-src 'self'; script-src 'self' blob: ${Array.from(scriptHashes).join(' ')}; style-src 'self' ${Array.from(styleHashes).join(' ')}; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; worker-src 'self' blob:"`);
} catch (err) {
console.error(`Error: ${err.message}`);
process.exit(1);
}
}
main();