import crypto from "node:crypto"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { createRequire } from "node:module"; import { fileURLToPath, pathToFileURL } from "node:url"; import ts from "../../web/node_modules/typescript/lib/typescript.js"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const domain = process.argv[2]; const seed = Number.parseInt(process.argv[3] ?? "0", 10) >>> 0; const iteration = Number.parseInt(process.argv[4] ?? "0", 10); if (!["blend", "image", "font", "node", "manifest"].includes(domain) || !Number.isSafeInteger(iteration) || iteration < 0) process.exit(64); let state = (seed ^ Math.imul(iteration + 1, 0x9e3779b1)) >>> 0; const random = () => { state ^= state << 13; state ^= state >>> 17; state ^= state << 5; return state >>> 0; }; const mutate = (source) => { let bytes = Buffer.from(source); if (iteration % 7 === 0) bytes = bytes.subarray(0, Math.max(0, Math.min(bytes.length, random() % Math.max(1, bytes.length)))); else for (let index = 0; index < 1 + iteration % 8 && bytes.length > 0; index++) bytes[random() % bytes.length] ^= 1 << (random() % 8); return bytes; }; const digest = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex"); const temporary = fs.mkdtempSync(path.join(os.tmpdir(), `m13-fuzz-${domain}-`)); const transpile = (name, replacements = []) => { const sourcePath = path.join(root, "web/protocol", `${name}.ts`); const result = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true }); if (result.diagnostics?.length) throw new Error(`TRANSPILE_FAILED:${name}`); const output = replacements.reduce((value, [from, to]) => value.replaceAll(from, to), result.outputText); fs.writeFileSync(path.join(temporary, `${name}.mjs`), output); }; const result = (status, code) => process.stdout.write(`${JSON.stringify({ domain, seed, iteration, status, code })}\n`); try { if (domain === "blend") { const [{ default: factory }, wasmBinary, source] = await Promise.all([ import(pathToFileURL(path.join(root, "web/app/src/vendor/blender/web_engine.js")).href), fs.promises.readFile(path.join(root, "web/app/src/vendor/blender/web_engine.wasm")), fs.promises.readFile(path.join(root, "tests/files/web/basic_scene.blend")), ]); const bytes = mutate(source); const engine = await factory({ wasmBinary }); const handle = engine._web_engine_create(); let pointer = 0; try { if (bytes.length) { pointer = engine._malloc(bytes.length); engine.HEAPU8.set(bytes, pointer); } const code = engine._web_engine_open_blend(handle, pointer, bytes.length); result(code === 0 ? "ACCEPTED" : "REJECTED", code === 0 ? "OK" : "BLEND_OPEN_INVALID"); } finally { if (pointer) engine._free(pointer); engine._web_engine_destroy(handle); } } else if (domain === "image") { transpile("asset-preview"); transpile("asset-preview-decode", [['from "./asset-preview"', 'from "./asset-preview.mjs"']]); const identityProtocol = await import(pathToFileURL(path.join(temporary, "asset-preview.mjs")).href); const decode = await import(pathToFileURL(path.join(temporary, "asset-preview-decode.mjs")).href); const source = fs.readFileSync(path.join(root, "tests/golden/M12-02B/preview.png")); const bytes = mutate(source); const original = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-02B/identity.json"), "utf8")); const value = { ...original, content: { ...original.content, byteLength: bytes.length, sha256: digest(bytes) } }; delete value.identitySha256; const identity = await identityProtocol.createAssetPreviewIdentity(value); await decode.planAssetPreviewDecode(identity, bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)); result("ACCEPTED", "OK"); } else if (domain === "font") { transpile("asset-path"); transpile("external-vfont", [['from "./asset-path"', 'from "./asset-path.mjs"']]); const font = await import(pathToFileURL(path.join(temporary, "external-vfont.mjs")).href); const bytes = mutate(fs.readFileSync(path.join(root, "blender-5.2.0/release/datafiles/bfont.pfb"))); const data = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); await font.validateExternalVFontImport({ sourcePath: "//fonts/fuzz.pfb", mimeType: "application/x-font-type1", byteLength: bytes.length, sha256: digest(bytes), data }); result("ACCEPTED", "OK"); } else if (domain === "node") { transpile("shader-compiler"); const shader = await import(pathToFileURL(path.join(temporary, "shader-compiler.mjs")).href); const material = { id: "material:fuzz", name: "fuzz", baseColor: [0.2, 0.3, 0.4, 1], roughness: 0.5, metallic: 0, emissionColor: [0, 0, 0, 1], alpha: 1, ior: 1.45, shaderGraphHash: "a".repeat(64), nodes: [{ id: "principled", type: "PRINCIPLED", name: "Principled" }, { id: "output", type: "OUTPUT", name: "Output" }], links: [{ fromNodeId: "principled", fromSocket: "BSDF", toNodeId: "output", toSocket: "Surface" }] }; switch (iteration % 6) { case 0: material.nodes.push({ id: `unknown-${random()}`, type: "UNSUPPORTED", name: "Unknown" }); break; case 1: material.nodes.push({ ...material.nodes[0] }); break; case 2: material.links.push({ fromNodeId: "output", fromSocket: "Surface", toNodeId: "principled", toSocket: "Base Color" }); break; case 3: material.shaderGraphHash = digest(Buffer.from(String(random()))).slice(1); break; case 4: material.nodes[0].id = "x".repeat(4096); break; default: material.roughness = Number.NaN; } const compiled = shader.compileMaterialGraph(material); result(compiled.status === "BLOCKED" ? "REJECTED" : "ACCEPTED", compiled.issues?.[0]?.code ?? "OK"); } else { for (const name of ["asset-path", "capability-gates", "scripting-platform"]) transpile(name, [['from "./asset-path"', 'from "./asset-path.mjs"'], ['from "./capability-gates"', 'from "./capability-gates.mjs"']]); const protocol = await import(pathToFileURL(path.join(temporary, "scripting-platform.mjs")).href); const script = { id: "fuzz", name: "fuzz", entryPath: "scripts/fuzz.py", sourceByteLength: 128, sourceSha256: "a".repeat(64), publisher: "local", signature: "b".repeat(128), keyId: "key:local", permissions: ["READ_MAIN"], dependencies: [], module: false, cpuMs: 1000, memoryBytes: 1024 * 1024, wallMs: 5000, network: false, autorun: false, driverExpressions: false, addonInstall: false }; const manifest = { schemaVersion: 1, scripts: [script] }; switch (iteration % 6) { case 0: script.entryPath = `../${random()}.py`; break; case 1: script.sourceByteLength = Number.MAX_SAFE_INTEGER; break; case 2: script.permissions = ["UNKNOWN"]; break; case 3: script.dependencies = [{ id: "fuzz", sourceSha256: "x", sourcePath: "../dep.py" }]; break; case 4: manifest.schemaVersion = 2; break; default: script.module = true; } protocol.parseScriptingManifest(manifest); result("ACCEPTED", "OK"); } } catch (error) { const message = error instanceof Error ? error.message : String(error); const code = error?.code ?? message.match(/^([A-Z][A-Z0-9_]+):/u)?.[1] ?? "STRUCTURED_REJECTION"; result("REJECTED", code); } finally { fs.rmSync(temporary, { recursive: true, force: true }); }