Update Content Security Policy with specific hashes for inline scripts and styles
	
		
			
	
		
	
	
		
			
				
	
				ci/woodpecker/push/woodpecker Pipeline failed
				
					Details
				
			
		
	
				
					
				
			
				
	
				ci/woodpecker/push/woodpecker Pipeline failed
				
					Details
				
			
		
	This commit is contained in:
		
							parent
							
								
									d2c70ee746
								
							
						
					
					
						commit
						02f1c33b71
					
				|  | @ -0,0 +1,140 @@ | ||||||
|  | #!/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();  | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | #!/usr/bin/env node
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * CSP Hash Calculator | ||||||
|  |  *  | ||||||
|  |  * This script calculates SHA-256 hashes for inline scripts and styles | ||||||
|  |  * to be used in Content Security Policy headers. | ||||||
|  |  *  | ||||||
|  |  * Usage: | ||||||
|  |  *   node csp-hash-calculator.js "<inline-code>" | ||||||
|  |  *  | ||||||
|  |  * Example: | ||||||
|  |  *   node csp-hash-calculator.js "document.addEventListener('DOMContentLoaded', function() { console.log('Hello'); });" | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | 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}'`; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get content from command line argument or stdin
 | ||||||
|  | const content = process.argv[2] || ''; | ||||||
|  | 
 | ||||||
|  | if (!content) { | ||||||
|  |   console.error('Error: No content provided. Please provide content as a command line argument.'); | ||||||
|  |   process.exit(1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Calculate and display the hash
 | ||||||
|  | const hash = calculateHash(content); | ||||||
|  | console.log('CSP Hash:'); | ||||||
|  | console.log(hash); | ||||||
|  | console.log('\nAdd to your CSP header:'); | ||||||
|  | console.log('For script: script-src ' + hash); | ||||||
|  | console.log('For style: style-src ' + hash);  | ||||||
|  | @ -48,8 +48,8 @@ | ||||||
|         # Frame Options (prevents clickjacking) |         # Frame Options (prevents clickjacking) | ||||||
|         X-Frame-Options "SAMEORIGIN" |         X-Frame-Options "SAMEORIGIN" | ||||||
|          |          | ||||||
|         # Update CSP to allow media content, scripts, and blob URLs |         # Update CSP to allow media content, scripts, and blob URLs with hashes | ||||||
|         Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; worker-src 'self' blob:" |         Content-Security-Policy "default-src 'self'; script-src 'self' blob: 'sha256-ahmIukdQI/ax+3q3Lrjh0Nuqv1/WxkBIGp+vaji6n8w=' 'sha256-qXRIcycfl2OwZL/s1bd83TFw2OcgcMsv1efLB/P3oOs=' 'sha256-SBn8uB66KTUeApEMuYlK6vZG0XcFpXLKlsbXswTKang=' 'sha256-/nvt7GhhWJsKGTVATnlAsNH54uy+pwbcjfx9Z9CT/u0=' 'sha256-rEjWap8xDw9tc9ULmaSD7VQycQasVvSd1OUiH9xKMTM=' 'sha256-9YtzahjQAT4luPVKC0lfwKhhBxWtN3zkQm99EHsc1bk=' 'sha256-PdtHVmWDPYQUs6SFGLloIwo3P4rG5A7ACmYWE1W4Gmk=' 'sha256-ALpx63KUUcf6ky/Teq3GLd+LlD+t+TpXN+bv/1++prU=' 'sha256-llDQiboC1dyoUHsUebHmXSwCs/k0znV6kWogS1Govvs=' 'sha256-zhuCqwglnTqPZ3YumUUbXlmgy3fN4NGHmK+wQzsoQic=' 'sha256-aCakwry3g1c1frt10sPVerFht/3JKT8i7ij3Aoxtsqw=' 'sha256-WE9M5TeJ2Xj1O9eh+0bg7XLyucO5+HCMccMznmiyocw=' 'sha256-FcjCj8HX/odDguAR0bldjsSdXOQMOLnCBKvlLHMZPZI=' 'sha256-tz6nsCI6ZDRK9g0tLDGMU5j9DBRx74XOe8xqaag7D3E=' 'sha256-IsinOLsxFzlWG2kdQIgMjg7l2ebbAaMbWWNSComW7EE=' 'sha256-p92qjinn1HJIBQCKu3QBxLsdKRh4NTdjvCax1ifSpw4=' 'sha256-17JNXqVQbWEbcxlPw9O3wCCa8PEFW9lwv6rOxRzkmXI=' 'sha256-uRkRZZ6nSw2qypQ46ShF3X/DRaPwWezfixlC4pkDuwo=' 'sha256-7bYe3kxYZPs9D4vqScBDsNEjqOw+n8pUFwyFObBKIjw=' 'sha256-IQIGMyVnkPj80HHZ8/Z8ZyxRC5ZPSFiGtTKsUdDqqOs='; style-src 'self' 'sha256-BBl1Pb4QBQZyj2HmRgFr/OhuPRYwV0zoE6G+08FM5TM=' 'sha256-DPggA6+WHJsxuaWoYLnB8XoTcBjKTnq+AmEhXZ2wJfw=' 'sha256-VyDqCue31iv/ickZ+WUp5RF3wMLAGo01mUL0VdbSTc8=' 'sha256-0ZDDv9ptap3zxZW4gGFrmDP9Y5osppDLJj0gRhecFN8=' 'sha256-c9m3RGxNzIy6ShTOIsmAgY77OyuTfgYCG3B2secjHc4=' 'sha256-rweYv4ZmpQ37GLZ2aJrWCpv486xCBOtOb6ngN4dBn8s=' 'sha256-dE50whpmj5sYr02WC5zh9QQNj6tVUQz1eTMmzJh6OU8=' 'sha256-3av5Wckr9yfHOVSXT8j0+EhuI9xI0Jld43e2jilZsro='; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; worker-src 'self' blob:" | ||||||
|          |          | ||||||
|         # Remove Server header |         # Remove Server header | ||||||
|         -Server |         -Server | ||||||
|  |  | ||||||
|  | @ -0,0 +1,119 @@ | ||||||
|  | #!/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 <html-file-path> | ||||||
|  |  *  | ||||||
|  |  * 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 = /<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; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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();  | ||||||
		Loading…
	
		Reference in New Issue