#!/usr/bin/env node /** * CSP Hash Extractor and Calculator * * This script extracts inline scripts and styles from HTML files * and calculates SHA-256 hashes for use in Content Security Policy headers. * * Usage: * node extract-and-hash-csp.js * * Example: * node extract-and-hash-csp.js ./docker/showerloop/public/index.html */ 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 = /]*)?>([\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 = /]*)?>([\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; } // Main function function main() { if (process.argv.length < 3) { console.error('Error: Please provide an HTML file path.'); process.exit(1); } const filePath = process.argv[2]; try { const html = fs.readFileSync(filePath, 'utf8'); const scripts = extractInlineScripts(html); const styles = extractInlineStyles(html); console.log(`\nAnalyzing file: ${filePath}`); console.log('\n=== Inline Scripts ==='); if (scripts.length === 0) { console.log('No inline scripts found.'); } else { console.log(`Found ${scripts.length} inline scripts.`); const scriptHashes = new Set(); scripts.forEach((script, index) => { const hash = calculateHash(script); scriptHashes.add(hash); console.log(`\nScript #${index + 1}:`); console.log(`${script.substring(0, 100)}${script.length > 100 ? '...' : ''}`); console.log(`Hash: ${hash}`); }); console.log('\nScript CSP directive:'); console.log(`script-src 'self' ${Array.from(scriptHashes).join(' ')}`); } console.log('\n=== Inline Styles ==='); if (styles.length === 0) { console.log('No inline styles found.'); } else { console.log(`Found ${styles.length} inline styles.`); const styleHashes = new Set(); styles.forEach((style, index) => { const hash = calculateHash(style); styleHashes.add(hash); console.log(`\nStyle #${index + 1}:`); console.log(`${style.substring(0, 100)}${style.length > 100 ? '...' : ''}`); console.log(`Hash: ${hash}`); }); console.log('\nStyle CSP directive:'); console.log(`style-src 'self' ${Array.from(styleHashes).join(' ')}`); } } catch (err) { console.error(`Error: ${err.message}`); process.exit(1); } } main();