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…
Reference in a new issue