From 0dfbacce37eaca519a99ffbb89932d317cdad8cb Mon Sep 17 00:00:00 2001 From: Sudo-Ivan Date: Sat, 27 Dec 2025 22:37:27 -0600 Subject: [PATCH] Update README and frontend files for improved clarity and functionality - Added a note in the README about using Taskfile for project management. - Removed the crossOrigin attribute from the WebAssembly fetch request in verifier.ts for security compliance. - Refactored the wasm_exec.js file for consistency in string usage and improved readability. - Cleaned up whitespace in the SRI generation script to enhance code clarity. --- README.md | 2 + frontend/src/lib/verifier.ts | 1 - frontend/static/verifier/wasm_exec.js | 301 ++++++++++++++++---------- scripts/sri-gen/main.go | 6 +- 4 files changed, 195 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index 6090c48..8b6d4b4 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ A software distribution platform for assets built and hosted on Gitea. Built wit ### Installation +We use [Taskfile](https://taskfile.dev/) to manage the project. + 1. **Build Everything (WASM, Frontend, Backend)**: ```bash go-task all diff --git a/frontend/src/lib/verifier.ts b/frontend/src/lib/verifier.ts index d5b006c..7080c2c 100644 --- a/frontend/src/lib/verifier.ts +++ b/frontend/src/lib/verifier.ts @@ -21,7 +21,6 @@ export async function loadVerifier() { const result = await WebAssembly.instantiateStreaming( fetch('/verifier/verifier.wasm', { integrity: 'sha384-fDQVhNAuumlwh5lh1AT6LiSLer1EQYa1G8TEJLCZvKXeUxYi2gn3QoI5YdNFtKW0', - crossOrigin: 'anonymous' }), go.importObject ); diff --git a/frontend/static/verifier/wasm_exec.js b/frontend/static/verifier/wasm_exec.js index d71af9e..ec6a143 100644 --- a/frontend/static/verifier/wasm_exec.js +++ b/frontend/static/verifier/wasm_exec.js @@ -2,22 +2,30 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -"use strict"; +'use strict'; (() => { const enosys = () => { - const err = new Error("not implemented"); - err.code = "ENOSYS"; + const err = new Error('not implemented'); + err.code = 'ENOSYS'; return err; }; if (!globalThis.fs) { - let outputBuf = ""; + let outputBuf = ''; globalThis.fs = { - constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused + constants: { + O_WRONLY: -1, + O_RDWR: -1, + O_CREAT: -1, + O_TRUNC: -1, + O_APPEND: -1, + O_EXCL: -1, + O_DIRECTORY: -1, + }, // unused writeSync(fd, buf) { outputBuf += decoder.decode(buf); - const nl = outputBuf.lastIndexOf("\n"); + const nl = outputBuf.lastIndexOf('\n'); if (nl != -1) { console.log(outputBuf.substring(0, nl)); outputBuf = outputBuf.substring(nl + 1); @@ -32,81 +40,147 @@ const n = this.writeSync(fd, buf); callback(null, n); }, - chmod(path, mode, callback) { callback(enosys()); }, - chown(path, uid, gid, callback) { callback(enosys()); }, - close(fd, callback) { callback(enosys()); }, - fchmod(fd, mode, callback) { callback(enosys()); }, - fchown(fd, uid, gid, callback) { callback(enosys()); }, - fstat(fd, callback) { callback(enosys()); }, - fsync(fd, callback) { callback(null); }, - ftruncate(fd, length, callback) { callback(enosys()); }, - lchown(path, uid, gid, callback) { callback(enosys()); }, - link(path, link, callback) { callback(enosys()); }, - lstat(path, callback) { callback(enosys()); }, - mkdir(path, perm, callback) { callback(enosys()); }, - open(path, flags, mode, callback) { callback(enosys()); }, - read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, - readdir(path, callback) { callback(enosys()); }, - readlink(path, callback) { callback(enosys()); }, - rename(from, to, callback) { callback(enosys()); }, - rmdir(path, callback) { callback(enosys()); }, - stat(path, callback) { callback(enosys()); }, - symlink(path, link, callback) { callback(enosys()); }, - truncate(path, length, callback) { callback(enosys()); }, - unlink(path, callback) { callback(enosys()); }, - utimes(path, atime, mtime, callback) { callback(enosys()); }, + chmod(path, mode, callback) { + callback(enosys()); + }, + chown(path, uid, gid, callback) { + callback(enosys()); + }, + close(fd, callback) { + callback(enosys()); + }, + fchmod(fd, mode, callback) { + callback(enosys()); + }, + fchown(fd, uid, gid, callback) { + callback(enosys()); + }, + fstat(fd, callback) { + callback(enosys()); + }, + fsync(fd, callback) { + callback(null); + }, + ftruncate(fd, length, callback) { + callback(enosys()); + }, + lchown(path, uid, gid, callback) { + callback(enosys()); + }, + link(path, link, callback) { + callback(enosys()); + }, + lstat(path, callback) { + callback(enosys()); + }, + mkdir(path, perm, callback) { + callback(enosys()); + }, + open(path, flags, mode, callback) { + callback(enosys()); + }, + read(fd, buffer, offset, length, position, callback) { + callback(enosys()); + }, + readdir(path, callback) { + callback(enosys()); + }, + readlink(path, callback) { + callback(enosys()); + }, + rename(from, to, callback) { + callback(enosys()); + }, + rmdir(path, callback) { + callback(enosys()); + }, + stat(path, callback) { + callback(enosys()); + }, + symlink(path, link, callback) { + callback(enosys()); + }, + truncate(path, length, callback) { + callback(enosys()); + }, + unlink(path, callback) { + callback(enosys()); + }, + utimes(path, atime, mtime, callback) { + callback(enosys()); + }, }; } if (!globalThis.process) { globalThis.process = { - getuid() { return -1; }, - getgid() { return -1; }, - geteuid() { return -1; }, - getegid() { return -1; }, - getgroups() { throw enosys(); }, + getuid() { + return -1; + }, + getgid() { + return -1; + }, + geteuid() { + return -1; + }, + getegid() { + return -1; + }, + getgroups() { + throw enosys(); + }, pid: -1, ppid: -1, - umask() { throw enosys(); }, - cwd() { throw enosys(); }, - chdir() { throw enosys(); }, - } + umask() { + throw enosys(); + }, + cwd() { + throw enosys(); + }, + chdir() { + throw enosys(); + }, + }; } if (!globalThis.path) { globalThis.path = { resolve(...pathSegments) { - return pathSegments.join("/"); - } - } + return pathSegments.join('/'); + }, + }; } if (!globalThis.crypto) { - throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); + throw new Error( + 'globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)' + ); } if (!globalThis.performance) { - throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); + throw new Error( + 'globalThis.performance is not available, polyfill required (performance.now only)' + ); } if (!globalThis.TextEncoder) { - throw new Error("globalThis.TextEncoder is not available, polyfill required"); + throw new Error('globalThis.TextEncoder is not available, polyfill required'); } if (!globalThis.TextDecoder) { - throw new Error("globalThis.TextDecoder is not available, polyfill required"); + throw new Error('globalThis.TextDecoder is not available, polyfill required'); } - const encoder = new TextEncoder("utf-8"); - const decoder = new TextDecoder("utf-8"); + const encoder = new TextEncoder('utf-8'); + const decoder = new TextDecoder('utf-8'); globalThis.Go = class { constructor() { - this.argv = ["js"]; + this.argv = ['js']; this.env = {}; this.exit = (code) => { if (code !== 0) { - console.warn("exit code:", code); + console.warn('exit code:', code); } }; this._exitPromise = new Promise((resolve) => { @@ -119,17 +193,17 @@ const setInt64 = (addr, v) => { this.mem.setUint32(addr + 0, v, true); this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); - } + }; const setInt32 = (addr, v) => { this.mem.setUint32(addr + 0, v, true); - } + }; const getInt64 = (addr) => { const low = this.mem.getUint32(addr + 0, true); const high = this.mem.getInt32(addr + 4, true); return low + high * 4294967296; - } + }; const loadValue = (addr) => { const f = this.mem.getFloat64(addr, true); @@ -142,12 +216,12 @@ const id = this.mem.getUint32(addr, true); return this._values[id]; - } + }; const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; + const nanHead = 0x7ff80000; - if (typeof v === "number" && v !== 0) { + if (typeof v === 'number' && v !== 0) { if (isNaN(v)) { this.mem.setUint32(addr + 4, nanHead, true); this.mem.setUint32(addr, 0, true); @@ -175,30 +249,30 @@ this._goRefCounts[id]++; let typeFlag = 0; switch (typeof v) { - case "object": + case 'object': if (v !== null) { typeFlag = 1; } break; - case "string": + case 'string': typeFlag = 2; break; - case "symbol": + case 'symbol': typeFlag = 3; break; - case "function": + case 'function': typeFlag = 4; break; } this.mem.setUint32(addr + 4, nanHead | typeFlag, true); this.mem.setUint32(addr, id, true); - } + }; const loadSlice = (addr) => { const array = getInt64(addr + 0); const len = getInt64(addr + 8); return new Uint8Array(this._inst.exports.mem.buffer, array, len); - } + }; const loadSliceOfValues = (addr) => { const array = getInt64(addr + 0); @@ -208,18 +282,18 @@ a[i] = loadValue(array + i * 8); } return a; - } + }; const loadString = (addr) => { const saddr = getInt64(addr + 0); const len = getInt64(addr + 8); return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); - } + }; const testCallExport = (a, b) => { this._inst.exports.testExport0(); return this._inst.exports.testExport(a, b); - } + }; const timeOrigin = Date.now() - performance.now(); this.importObject = { @@ -234,7 +308,7 @@ // This changes the SP, thus we have to update the SP used by the imported function. // func wasmExit(code int32) - "runtime.wasmExit": (sp) => { + 'runtime.wasmExit': (sp) => { sp >>>= 0; const code = this.mem.getInt32(sp + 8, true); this.exited = true; @@ -247,7 +321,7 @@ }, // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) - "runtime.wasmWrite": (sp) => { + 'runtime.wasmWrite': (sp) => { sp >>>= 0; const fd = getInt64(sp + 8); const p = getInt64(sp + 16); @@ -256,47 +330,50 @@ }, // func resetMemoryDataView() - "runtime.resetMemoryDataView": (sp) => { + 'runtime.resetMemoryDataView': (sp) => { sp >>>= 0; this.mem = new DataView(this._inst.exports.mem.buffer); }, // func nanotime1() int64 - "runtime.nanotime1": (sp) => { + 'runtime.nanotime1': (sp) => { sp >>>= 0; setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); }, // func walltime() (sec int64, nsec int32) - "runtime.walltime": (sp) => { + 'runtime.walltime': (sp) => { sp >>>= 0; - const msec = (new Date).getTime(); + const msec = new Date().getTime(); setInt64(sp + 8, msec / 1000); this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); }, // func scheduleTimeoutEvent(delay int64) int32 - "runtime.scheduleTimeoutEvent": (sp) => { + 'runtime.scheduleTimeoutEvent': (sp) => { sp >>>= 0; const id = this._nextCallbackTimeoutID; this._nextCallbackTimeoutID++; - this._scheduledTimeouts.set(id, setTimeout( - () => { - this._resume(); - while (this._scheduledTimeouts.has(id)) { - // for some reason Go failed to register the timeout event, log and try again - // (temporary workaround for https://github.com/golang/go/issues/28975) - console.warn("scheduleTimeoutEvent: missed timeout event"); + this._scheduledTimeouts.set( + id, + setTimeout( + () => { this._resume(); - } - }, - getInt64(sp + 8), - )); + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn('scheduleTimeoutEvent: missed timeout event'); + this._resume(); + } + }, + getInt64(sp + 8) + ) + ); this.mem.setInt32(sp + 16, id, true); }, // func clearTimeoutEvent(id int32) - "runtime.clearTimeoutEvent": (sp) => { + 'runtime.clearTimeoutEvent': (sp) => { sp >>>= 0; const id = this.mem.getInt32(sp + 8, true); clearTimeout(this._scheduledTimeouts.get(id)); @@ -304,13 +381,13 @@ }, // func getRandomData(r []byte) - "runtime.getRandomData": (sp) => { + 'runtime.getRandomData': (sp) => { sp >>>= 0; crypto.getRandomValues(loadSlice(sp + 8)); }, // func finalizeRef(v ref) - "syscall/js.finalizeRef": (sp) => { + 'syscall/js.finalizeRef': (sp) => { sp >>>= 0; const id = this.mem.getUint32(sp + 8, true); this._goRefCounts[id]--; @@ -323,13 +400,13 @@ }, // func stringVal(value string) ref - "syscall/js.stringVal": (sp) => { + 'syscall/js.stringVal': (sp) => { sp >>>= 0; storeValue(sp + 24, loadString(sp + 8)); }, // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (sp) => { + 'syscall/js.valueGet': (sp) => { sp >>>= 0; const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); sp = this._inst.exports.getsp() >>> 0; // see comment above @@ -337,31 +414,31 @@ }, // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (sp) => { + 'syscall/js.valueSet': (sp) => { sp >>>= 0; Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); }, // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (sp) => { + 'syscall/js.valueDelete': (sp) => { sp >>>= 0; Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); }, // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (sp) => { + 'syscall/js.valueIndex': (sp) => { sp >>>= 0; storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); }, // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (sp) => { + 'syscall/js.valueSetIndex': (sp) => { sp >>>= 0; Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); }, // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (sp) => { + 'syscall/js.valueCall': (sp) => { sp >>>= 0; try { const v = loadValue(sp + 8); @@ -379,7 +456,7 @@ }, // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (sp) => { + 'syscall/js.valueInvoke': (sp) => { sp >>>= 0; try { const v = loadValue(sp + 8); @@ -396,7 +473,7 @@ }, // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (sp) => { + 'syscall/js.valueNew': (sp) => { sp >>>= 0; try { const v = loadValue(sp + 8); @@ -413,13 +490,13 @@ }, // func valueLength(v ref) int - "syscall/js.valueLength": (sp) => { + 'syscall/js.valueLength': (sp) => { sp >>>= 0; setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); }, // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (sp) => { + 'syscall/js.valuePrepareString': (sp) => { sp >>>= 0; const str = encoder.encode(String(loadValue(sp + 8))); storeValue(sp + 16, str); @@ -427,20 +504,20 @@ }, // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (sp) => { + 'syscall/js.valueLoadString': (sp) => { sp >>>= 0; const str = loadValue(sp + 8); loadSlice(sp + 16).set(str); }, // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (sp) => { + 'syscall/js.valueInstanceOf': (sp) => { sp >>>= 0; - this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); + this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16) ? 1 : 0); }, // func copyBytesToGo(dst []byte, src ref) (int, bool) - "syscall/js.copyBytesToGo": (sp) => { + 'syscall/js.copyBytesToGo': (sp) => { sp >>>= 0; const dst = loadSlice(sp + 8); const src = loadValue(sp + 32); @@ -455,7 +532,7 @@ }, // func copyBytesToJS(dst ref, src []byte) (int, bool) - "syscall/js.copyBytesToJS": (sp) => { + 'syscall/js.copyBytesToJS': (sp) => { sp >>>= 0; const dst = loadValue(sp + 8); const src = loadSlice(sp + 16); @@ -469,20 +546,21 @@ this.mem.setUint8(sp + 48, 1); }, - "debug": (value) => { + debug: (value) => { console.log(value); }, - } + }, }; } async run(instance) { if (!(instance instanceof WebAssembly.Instance)) { - throw new Error("Go.run: WebAssembly.Instance expected"); + throw new Error('Go.run: WebAssembly.Instance expected'); } this._inst = instance; this.mem = new DataView(this._inst.exports.mem.buffer); - this._values = [ // JS values that Go currently has references to, indexed by reference id + this._values = [ + // JS values that Go currently has references to, indexed by reference id NaN, 0, null, @@ -492,7 +570,8 @@ this, ]; this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id - this._ids = new Map([ // mapping from JS values to reference ids + this._ids = new Map([ + // mapping from JS values to reference ids [0, 1], [null, 2], [true, 3], @@ -500,7 +579,7 @@ [globalThis, 5], [this, 6], ]); - this._idPool = []; // unused ids that have been garbage collected + this._idPool = []; // unused ids that have been garbage collected this.exited = false; // whether the Go program has exited // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. @@ -508,7 +587,7 @@ const strPtr = (str) => { const ptr = offset; - const bytes = encoder.encode(str + "\0"); + const bytes = encoder.encode(str + '\0'); new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); offset += bytes.length; if (offset % 8 !== 0) { @@ -542,7 +621,7 @@ // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. const wasmMinDataAddr = 4096 + 8192; if (offset >= wasmMinDataAddr) { - throw new Error("total length of command line and environment variables exceeds limit"); + throw new Error('total length of command line and environment variables exceeds limit'); } this._inst.exports.run(argc, argv); @@ -554,7 +633,7 @@ _resume() { if (this.exited) { - throw new Error("Go program has already exited"); + throw new Error('Go program has already exited'); } this._inst.exports.resume(); if (this.exited) { @@ -571,5 +650,5 @@ return event.result; }; } - } + }; })(); diff --git a/scripts/sri-gen/main.go b/scripts/sri-gen/main.go index 0c9c929..9a9aafa 100644 --- a/scripts/sri-gen/main.go +++ b/scripts/sri-gen/main.go @@ -96,7 +96,7 @@ func processSourceFile(sourcePath string) error { } updated := string(content) - + // We need a way to map the asset to the SRI. For verifier.ts, we know the assets. assets := map[string]string{ "wasm_exec.js": "frontend/static/verifier/wasm_exec.js", @@ -113,14 +113,14 @@ func processSourceFile(sourcePath string) error { // Find the line that mentions the asset and update the NEXT integrity string assetEscaped := strings.ReplaceAll(assetName, ".", "\\.") assetPattern := regexp.MustCompile(`['"](/verifier/)?` + assetEscaped + `['"][\s\S]*?sha384-([^'"]+)`) - + matches := assetPattern.FindAllStringSubmatchIndex(updated, -1) // Process from end to start to not mess up indices for i := len(matches) - 1; i >= 0; i-- { match := matches[i] oldHashStart, oldHashEnd := match[4], match[5] oldHash := updated[oldHashStart:oldHashEnd] - + if oldHash != hash { updated = updated[:oldHashStart] + hash + updated[oldHashEnd:] fmt.Printf(" Updated SRI for %s in %s\n", assetName, sourcePath)