forked from MapComplete/MapComplete
		
	Security/fix: update SHA-hashes of goatcounter script, add test to check that they are still up-to-date
This commit is contained in:
		
							parent
							
								
									b8a631f368
								
							
						
					
					
						commit
						3cbedf7cf2
					
				
					 8 changed files with 35 additions and 14 deletions
				
			
		
							
								
								
									
										2
									
								
								404.html
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								404.html
									
										
									
									
									
								
							| 
						 | 
					@ -51,7 +51,7 @@
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script type="module" src="./src/notfound.ts"></script>
 | 
					<script type="module" src="./src/notfound.ts"></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 async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,7 +53,7 @@
 | 
				
			||||||
<div id="main"></div>
 | 
					<div id="main"></div>
 | 
				
			||||||
<script type="module" src="./src/all_themes_index.ts"></script>
 | 
					<script type="module" src="./src/all_themes_index.ts"></script>
 | 
				
			||||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
 | 
					<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
 | 
				
			||||||
        integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script>
 | 
					        integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
    window.addEventListener('load', () => {
 | 
					    window.addEventListener('load', () => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,7 +53,7 @@
 | 
				
			||||||
<div id="main"></div>
 | 
					<div id="main"></div>
 | 
				
			||||||
<script type="module" src="./src/leaderboard.ts"></script>
 | 
					<script type="module" src="./src/leaderboard.ts"></script>
 | 
				
			||||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
 | 
					<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
 | 
				
			||||||
        integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script>
 | 
					        integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
    window.addEventListener('load', () => {
 | 
					    window.addEventListener('load', () => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,7 @@
 | 
				
			||||||
<div id="main"></div>
 | 
					<div id="main"></div>
 | 
				
			||||||
<script type="module" src="./src/privacy_index.ts"></script>
 | 
					<script type="module" src="./src/privacy_index.ts"></script>
 | 
				
			||||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
 | 
					<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
 | 
				
			||||||
        integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script>
 | 
					        integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
<div id="main">Loading statistics...</div>
 | 
					<div id="main">Loading statistics...</div>
 | 
				
			||||||
<script src="./src/UI/StatisticsGUI.ts" type="module"></script>
 | 
					<script src="./src/UI/StatisticsGUI.ts" type="module"></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 async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
<div id="main" class="h-full">Initing studio...</div>
 | 
					<div id="main" class="h-full">Initing studio...</div>
 | 
				
			||||||
<script src="./src/UI/StudioGui.ts" type="module"></script>
 | 
					<script src="./src/UI/StudioGui.ts" type="module"></script>
 | 
				
			||||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script>
 | 
					<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,10 @@
 | 
				
			||||||
import { exec } from "child_process"
 | 
					import { exec } from "child_process"
 | 
				
			||||||
import { describe, it } from "vitest"
 | 
					import { describe, expect, it, test } from "vitest"
 | 
				
			||||||
 | 
					import { webcrypto } from "node:crypto"
 | 
				
			||||||
import { parse as parse_html } from "node-html-parser"
 | 
					import { parse as parse_html } from "node-html-parser"
 | 
				
			||||||
import { readFileSync } from "fs"
 | 
					import { readFileSync } from "fs"
 | 
				
			||||||
import ScriptUtils from "../scripts/ScriptUtils"
 | 
					import ScriptUtils from "../scripts/ScriptUtils"
 | 
				
			||||||
 | 
					import hash from "svelte/types/compiler/compile/utils/hash"
 | 
				
			||||||
function detectInCode(forbidden: string, reason: string) {
 | 
					function detectInCode(forbidden: string, reason: string) {
 | 
				
			||||||
    return wrap(detectInCodeUnwrapped(forbidden, reason))
 | 
					    return wrap(detectInCodeUnwrapped(forbidden, reason))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -63,14 +64,22 @@ function wrap(promise: Promise<void>): (done: () => void) => void {
 | 
				
			||||||
        promise.then(done)
 | 
					        promise.then(done)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					function _arrayBufferToBase64(buffer) {
 | 
				
			||||||
function validateScriptIntegrityOf(path: string) {
 | 
					    var binary = ""
 | 
				
			||||||
 | 
					    var bytes = new Uint8Array(buffer)
 | 
				
			||||||
 | 
					    var len = bytes.byteLength
 | 
				
			||||||
 | 
					    for (var i = 0; i < len; i++) {
 | 
				
			||||||
 | 
					        binary += String.fromCharCode(bytes[i])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return btoa(binary)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					async function validateScriptIntegrityOf(path: string): Promise<void> {
 | 
				
			||||||
    const htmlContents = readFileSync(path, "utf8")
 | 
					    const htmlContents = readFileSync(path, "utf8")
 | 
				
			||||||
    const doc = parse_html(htmlContents)
 | 
					    const doc = parse_html(htmlContents)
 | 
				
			||||||
    // @ts-ignore
 | 
					    // @ts-ignore
 | 
				
			||||||
    const scripts = Array.from(doc.getElementsByTagName("script"))
 | 
					    const scripts = Array.from(doc.getElementsByTagName("script"))
 | 
				
			||||||
    for (const script of scripts) {
 | 
					    for (const script of scripts) {
 | 
				
			||||||
        const src = script.getAttribute("src")
 | 
					        let src = script.getAttribute("src")
 | 
				
			||||||
        if (src === undefined) {
 | 
					        if (src === undefined) {
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -87,6 +96,18 @@ function validateScriptIntegrityOf(path: string) {
 | 
				
			||||||
        if (crossorigin !== "anonymous") {
 | 
					        if (crossorigin !== "anonymous") {
 | 
				
			||||||
            throw new Error(ctx + " has crossorigin missing or not set to 'anonymous'")
 | 
					            throw new Error(ctx + " has crossorigin missing or not set to 'anonymous'")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (src.startsWith("//")) {
 | 
				
			||||||
 | 
					            src = "https:" + src
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const request = await fetch(src)
 | 
				
			||||||
 | 
					        const data: ArrayBuffer = await request.arrayBuffer()
 | 
				
			||||||
 | 
					        const hashed = await webcrypto.subtle.digest("SHA-384", data)
 | 
				
			||||||
 | 
					        const hashedStr = _arrayBufferToBase64(hashed)
 | 
				
			||||||
 | 
					        console.log(src, hashedStr, integrity)
 | 
				
			||||||
 | 
					        expect(integrity).to.equal(
 | 
				
			||||||
 | 
					            "sha384-" + hashedStr,
 | 
				
			||||||
 | 
					            "Loading a script from '" + src + "' in the file " + path + " has a mismatched checksum"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -112,10 +133,10 @@ describe("Code quality", () => {
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("scripts with external sources should have an integrity hash", () => {
 | 
					    test("scripts with external sources should have an integrity hash", async () => {
 | 
				
			||||||
        const htmlFiles = ScriptUtils.readDirRecSync(".", 1).filter((f) => f.endsWith(".html"))
 | 
					        const htmlFiles = ScriptUtils.readDirRecSync(".", 1).filter((f) => f.endsWith(".html"))
 | 
				
			||||||
        for (const htmlFile of htmlFiles) {
 | 
					        for (const htmlFile of htmlFiles) {
 | 
				
			||||||
            validateScriptIntegrityOf(htmlFile)
 | 
					            await validateScriptIntegrityOf(htmlFile)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    /*
 | 
					    /*
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -76,7 +76,7 @@
 | 
				
			||||||
<script src="./src/UI/RemoveOtherLanguages.js"></script>
 | 
					<script src="./src/UI/RemoveOtherLanguages.js"></script>
 | 
				
			||||||
<script async src="./src/InstallServiceWorker.ts" type="module"></script>
 | 
					<script async src="./src/InstallServiceWorker.ts" type="module"></script>
 | 
				
			||||||
<script defer src="./src/index.ts" type="module"></script>
 | 
					<script defer src="./src/index.ts" type="module"></script>
 | 
				
			||||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script>
 | 
					<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-nx5O+otcqJoqMhdDt8jUzmia6ng81Z5zZozYr69TzPkOLjVhLKMxu5zHCV9/0MPn"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue