Security/fix: update SHA-hashes of goatcounter script, add test to check that they are still up-to-date

This commit is contained in:
Pieter Vander Vennet 2024-01-07 17:32:14 +01:00
parent b8a631f368
commit 3cbedf7cf2
8 changed files with 35 additions and 14 deletions

View file

@ -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>

View file

@ -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', () => {

View file

@ -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', () => {

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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)
} }
}) })
/* /*

View file

@ -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>