forked from MapComplete/MapComplete
		
	Fix: add script integrity, add check to validate that script integrity is always in place
This commit is contained in:
		
							parent
							
								
									3f0ca80117
								
							
						
					
					
						commit
						08bbbcabc4
					
				
					 6 changed files with 85 additions and 15 deletions
				
			
		
							
								
								
									
										2
									
								
								404.html
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								404.html
									
										
									
									
									
								
							|  | @ -50,7 +50,7 @@ | |||
| </div> | ||||
| 
 | ||||
| <script type="module" src="./src/notfound.ts"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -51,7 +51,8 @@ | |||
| 
 | ||||
| <div id="main"></div> | ||||
| <script type="module" src="./src/all_themes_index.ts"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" | ||||
|         integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> | ||||
| 
 | ||||
| <script> | ||||
|     window.addEventListener('load', () => { | ||||
|  |  | |||
							
								
								
									
										32
									
								
								scripts/generateRedirectFiles.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								scripts/generateRedirectFiles.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| import Script from "./Script" | ||||
| import ScriptUtils from "./ScriptUtils" | ||||
| import { writeFileSync } from "fs" | ||||
| import { AllKnownLayouts } from "../src/Customizations/AllKnownLayouts" | ||||
| 
 | ||||
| class CreateRedirectFiles extends Script { | ||||
|     constructor() { | ||||
|         super( | ||||
|             "Creates a redirect html-file in the 'mapcomplete-osm-be' repository for every .html file and every known theme" | ||||
|         ) | ||||
|     } | ||||
|     async main(args: string[]): Promise<void> { | ||||
|         const htmlFiles = ScriptUtils.readDirRecSync(".", 1) | ||||
|             .filter((f) => f.endsWith(".html")) | ||||
|             .map((s) => s.substring(2, s.length - 5)) | ||||
|         const themes = Array.from(AllKnownLayouts.allKnownLayouts.keys()) | ||||
|         htmlFiles.push(...themes) | ||||
|         console.log("HTML files are:", htmlFiles) | ||||
|         for (const htmlFile of htmlFiles) { | ||||
|             let path = "" | ||||
|             if (htmlFile !== "index") { | ||||
|                 path = htmlFile | ||||
|             } | ||||
|             writeFileSync( | ||||
|                 "../mapcomplete-osm-be/" + htmlFile + ".html", | ||||
|                 `<meta http-equiv="Refresh" content="0; url='https://mapcomplete.org/${path}'" />` | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| new CreateRedirectFiles().run() | ||||
|  | @ -14,7 +14,7 @@ | |||
| <body> | ||||
| <div id="main">Loading statistics...</div> | ||||
| <script src="./src/UI/StatisticsGUI.ts" type="module"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -1,6 +1,10 @@ | |||
| import { exec } from "child_process" | ||||
| import { describe, it } from "vitest" | ||||
| 
 | ||||
| import { parse as parse_html } from "node-html-parser" | ||||
| import { readFileSync } from "fs" | ||||
| import ScriptUtils from "../scripts/ScriptUtils" | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  * @param forbidden: a GREP-regex. This means that '.' is a wildcard and should be escaped to match a literal dot | ||||
|  | @ -64,6 +68,32 @@ function itAsync(name: string, promise: Promise<void>) { | |||
|     it(name, wrap(promise)) | ||||
| } | ||||
| 
 | ||||
| function validateScriptIntegrityOf(path: string) { | ||||
|     const htmlContents = readFileSync(path, "utf8") | ||||
|     const doc = parse_html(htmlContents) | ||||
|     // @ts-ignore
 | ||||
|     const scripts = Array.from(doc.getElementsByTagName("script")) | ||||
|     for (const script of scripts) { | ||||
|         const src = script.getAttribute("src") | ||||
|         if (src === undefined) { | ||||
|             continue | ||||
|         } | ||||
|         if (src.startsWith("./")) { | ||||
|             // Local script - no check needed
 | ||||
|             continue | ||||
|         } | ||||
|         const integrity = script.getAttribute("integrity") | ||||
|         const ctx = "Script with source " + src + " in file " + path | ||||
|         if (integrity === undefined) { | ||||
|             throw new Error(ctx + " has no integrity value") | ||||
|         } | ||||
|         const crossorigin = script.getAttribute("crossorigin") | ||||
|         if (crossorigin !== "anonymous") { | ||||
|             throw new Error(ctx + " has crossorigin missing or not set to 'anonymous'") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| describe("Code quality", () => { | ||||
|     itAsync( | ||||
|         "should not contain reverse", | ||||
|  | @ -85,6 +115,13 @@ describe("Code quality", () => { | |||
|             "innerText is not allowed as it is not testable with fakeDom. Use 'textContent' instead." | ||||
|         ) | ||||
|     ) | ||||
| 
 | ||||
|     it("scripts with external sources should have an integrity hash", () => { | ||||
|         const htmlFiles = ScriptUtils.readDirRecSync(".", 1).filter((f) => f.endsWith(".html")) | ||||
|         for (const htmlFile of htmlFiles) { | ||||
|             validateScriptIntegrityOf(htmlFile) | ||||
|         } | ||||
|     }) | ||||
|     /* | ||||
|   itAsync( | ||||
|       "should not contain 'import * as name from \"xyz.json\"'", | ||||
|  |  | |||
|  | @ -97,7 +97,7 @@ | |||
| 
 | ||||
| 
 | ||||
| <script src="./src/index.ts" type="module"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> | ||||
| 
 | ||||
| <script> | ||||
|     window.addEventListener('load', () => { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue