From 820d3d136418395d88be7293da962ce4bdb12cc3 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sat, 13 Jun 2026 21:40:26 +0200 Subject: [PATCH 1/2] fix(wasmsign2_wrapper): pass wasmtime + wasm paths from rules, not runfiles (#501) wasmsign2_wrapper located wasmtime and wasmsign2.wasm via hardcoded canonical runfiles Rlocations (`+wasmtime+wasmtime_toolchain/wasmtime`, `+_repo_rules+wasmsign2_cli_wasm/file/wasmsign2.wasm`). Those canonical names are correct only when rules_wasm_component is the root module; consumed as a dependency they gain a `rules_wasm_component+` prefix, so the lookups fail at runtime and wasm_sign/verify/keygen break for downstream consumers. This is the same bug confirmed and fixed for loom_wrapper in #490/#497. Fix (mirrors #497): the wasm_sign/wasm_verify/wasm_keygen rules now pass the wasmtime binary and the wasmsign2 component as `--bazel-wasmtime=` / `--bazel-wasm-component=` arguments (and stage them as action inputs via the wasmtime toolchain + a `_wasmsign2_wasm` attr). The wrapper consumes those and no longer touches runfiles, so it is root/dependency-agnostic. Verified in-repo: keygen + sign + verify build end-to-end and //examples/wasm_signing:verify_embedded reports "Signature is valid." Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/wasmsign2_wrapper/BUILD.bazel | 12 ++++----- tools/wasmsign2_wrapper/main.go | 38 +++++++++++++------------- wasm/private/wasm_signing.bzl | 41 ++++++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/tools/wasmsign2_wrapper/BUILD.bazel b/tools/wasmsign2_wrapper/BUILD.bazel index 6d5d9d72..54e74bb6 100644 --- a/tools/wasmsign2_wrapper/BUILD.bazel +++ b/tools/wasmsign2_wrapper/BUILD.bazel @@ -9,17 +9,17 @@ load("@rules_go//go:def.bzl", "go_binary") package(default_visibility = ["//visibility:public"]) -# Wrapper binary that executes wasmsign2.wasm with path resolution +# Wrapper binary that executes wasmsign2.wasm with path resolution. +# The wasmtime binary and wasmsign2.wasm are passed by the calling rule as +# --bazel-* arguments (and staged as action inputs); wasmtime opens both +# natively. They are NOT resolved via runfiles, which would embed a canonical +# repo name that breaks when rules_wasm_component is consumed as a dependency +# (issue #501). go_binary( name = "wasmsign2_wrapper", srcs = ["main.go"], - data = [ - "@wasmsign2_cli_wasm//file", - "@wasmtime_toolchain//:wasmtime", - ], pure = "on", # Pure Go for cross-platform compatibility visibility = ["//visibility:public"], - deps = ["@rules_go//go/runfiles"], ) # Export for easy access in toolchains diff --git a/tools/wasmsign2_wrapper/main.go b/tools/wasmsign2_wrapper/main.go index e342993c..dd8bd9a3 100644 --- a/tools/wasmsign2_wrapper/main.go +++ b/tools/wasmsign2_wrapper/main.go @@ -7,8 +7,6 @@ import ( "os/exec" "path/filepath" "strings" - - "github.com/bazelbuild/rules_go/go/runfiles" ) // Wrapper for wasmsign2 WASM component @@ -19,6 +17,8 @@ func main() { } // Internal-only Bazel coordination flags. These never reach wsc. + // --bazel-wasmtime=PATH Path to the wasmtime binary to exec. + // --bazel-wasm-component=PATH Path to the wasmsign2 WASM component. // --bazel-marker-file=PATH Write "Verification passed\n" on success. // --bazel-stage-source=PATH Copy PATH to the --output-file location // before running wsc. Lets rules pass the @@ -29,12 +29,25 @@ func main() { // inheriting this process's stdout. Used // by show-chain to produce a Bazel output // artifact. + // + // wasmtime and the wasm component are passed by the calling rule (and staged + // as action inputs) rather than located via runfiles: a hardcoded runfiles + // Rlocation embeds the canonical repo name, which differs when + // rules_wasm_component is consumed as a dependency (it gains a + // `rules_wasm_component+` prefix), breaking downstream signing (issue #501, + // same fix as #490/#497). wasmtime opens both files natively. var markerFile string var stageSource string var captureStdout string + var wasmtimeBinary string + var wasmsign2Wasm string filteredArgs := make([]string, 0, len(os.Args)) for i, arg := range os.Args { switch { + case strings.HasPrefix(arg, "--bazel-wasmtime="): + wasmtimeBinary = strings.TrimPrefix(arg, "--bazel-wasmtime=") + case strings.HasPrefix(arg, "--bazel-wasm-component="): + wasmsign2Wasm = strings.TrimPrefix(arg, "--bazel-wasm-component=") case strings.HasPrefix(arg, "--bazel-marker-file="): markerFile = strings.TrimPrefix(arg, "--bazel-marker-file=") case strings.HasPrefix(arg, "--bazel-stage-source="): @@ -48,28 +61,15 @@ func main() { } } - // Initialize Bazel runfiles - r, err := runfiles.New() - if err != nil { - log.Fatalf("Failed to initialize runfiles: %v", err) + if wasmtimeBinary == "" { + log.Fatal("Missing required --bazel-wasmtime=PATH") } - - // Locate wasmtime binary - wasmtimeBinary, err := r.Rlocation("+wasmtime+wasmtime_toolchain/wasmtime") - if err != nil { - log.Fatalf("Failed to locate wasmtime: %v", err) + if wasmsign2Wasm == "" { + log.Fatal("Missing required --bazel-wasm-component=PATH") } - if _, err := os.Stat(wasmtimeBinary); err != nil { log.Fatalf("Wasmtime binary not found at %s: %v", wasmtimeBinary, err) } - - // Locate wasmsign2 WASM component - wasmsign2Wasm, err := r.Rlocation("+_repo_rules+wasmsign2_cli_wasm/file/wasmsign2.wasm") - if err != nil { - log.Fatalf("Failed to locate wasmsign2.wasm: %v", err) - } - if _, err := os.Stat(wasmsign2Wasm); err != nil { log.Fatalf("wasmsign2.wasm not found at %s: %v", wasmsign2Wasm, err) } diff --git a/wasm/private/wasm_signing.bzl b/wasm/private/wasm_signing.bzl index 7d14650a..e7795a4d 100644 --- a/wasm/private/wasm_signing.bzl +++ b/wasm/private/wasm_signing.bzl @@ -2,6 +2,34 @@ load("//providers:providers.bzl", "WasmComponentInfo", "WasmKeyInfo", "WasmSignatureInfo") +# wasmtime toolchain that runs the wasmsign2 component. +_WASMTIME_TOOLCHAIN = "@rules_wasm_component//toolchains:wasmtime_toolchain_type" + +# The wasmsign2 WASM component, passed to the wrapper as an action input. +_WASMSIGN2_WASM_ATTR = { + "_wasmsign2_wasm": attr.label( + default = "@wasmsign2_cli_wasm//file:file", + allow_single_file = True, + doc = "wasmsign2 WASM component, passed to the wrapper as an action input", + ), +} + +def _add_wrapper_tools(ctx, args): + """Add the wasmtime binary and wasmsign2.wasm to a wrapper invocation. + + They are passed as action inputs + `--bazel-*` arguments rather than located + via the wrapper's runfiles. A hardcoded runfiles Rlocation embeds the + canonical repo name, which differs when rules_wasm_component is consumed as a + dependency (it gains a `rules_wasm_component+` prefix), breaking downstream + signing — the same bug fixed for loom_wrapper in #490/#497 (issue #501). + Returns the extra action inputs to add. + """ + wasmtime = ctx.toolchains[_WASMTIME_TOOLCHAIN].wasmtime + wasm = ctx.file._wasmsign2_wasm + args.add(wasmtime, format = "--bazel-wasmtime=%s") + args.add(wasm, format = "--bazel-wasm-component=%s") + return [wasmtime, wasm] + def _wasm_keygen_impl(ctx): """Implementation of wasm_keygen rule""" @@ -17,12 +45,14 @@ def _wasm_keygen_impl(ctx): args.add("keygen") args.add("--public-key", public_key) args.add("--secret-key", secret_key) + tool_inputs = _add_wrapper_tools(ctx, args) # Run key generation via Go wrapper # The wrapper handles symlink resolution and WASI directory mapping ctx.actions.run( executable = wasmsign2_wrapper, arguments = [args], + inputs = tool_inputs, outputs = [public_key, secret_key], mnemonic = "WasmKeyGen", progress_message = "Generating WASM signing keys %s" % ctx.label, @@ -69,7 +99,8 @@ wasm_keygen = rule( executable = True, cfg = "exec", ), - }, + } | _WASMSIGN2_WASM_ATTR, + toolchains = [_WASMTIME_TOOLCHAIN], doc = """ Generates a key pair for signing WebAssembly components in compact format. @@ -150,6 +181,7 @@ def _wasm_sign_impl(ctx): inputs = [input_wasm, secret_key] if public_key: inputs.append(public_key) + inputs += _add_wrapper_tools(ctx, args) # Prepare outputs outputs = [signed_wasm] @@ -238,7 +270,8 @@ wasm_sign = rule( executable = True, cfg = "exec", ), - }, + } | _WASMSIGN2_WASM_ATTR, + toolchains = [_WASMTIME_TOOLCHAIN], doc = """ Signs a WebAssembly component with a cryptographic signature. @@ -323,6 +356,7 @@ def _wasm_verify_impl(ctx): inputs.append(public_key) if signature_file: inputs.append(signature_file) + inputs += _add_wrapper_tools(ctx, args) # Run verification via Go wrapper (no shell scripts!) # The wrapper will create the marker file on success @@ -394,7 +428,8 @@ wasm_verify = rule( executable = True, cfg = "exec", ), - }, + } | _WASMSIGN2_WASM_ATTR, + toolchains = [_WASMTIME_TOOLCHAIN], doc = """ Verifies the cryptographic signature of a WebAssembly component. From f192ff092dea42fd2253272438e996c5889994b6 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sat, 13 Jun 2026 22:37:06 +0200 Subject: [PATCH 2/2] fix(#501): also fix wsc_attestation wrapper callers; share helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI caught that wsc_attestation.bzl has three more wasmsign2_wrapper callers (wasm_attest / wasm_verify_chain / wasm_show_chain) that the first commit missed — making --bazel-wasmtime/--bazel-wasm-component mandatory broke them (e.g. //examples/wasm_signing:signed_component_chain_json, WasmShowChain). Extract the helper into wasm/private/wasmsign2_tools.bzl (WASMTIME_TOOLCHAIN, WASMSIGN2_WASM_ATTR, add_wrapper_tools) and load it from both wasm_signing.bzl and wsc_attestation.bzl. All six wrapper callers now pass wasmtime + the wasm component as action inputs/args; none use runfiles. Verified: `bazel build //examples/wasm_signing/...` (28 targets, incl. the show-chain target that failed CI) builds; verify targets report "Signature is valid." Co-Authored-By: Claude Opus 4.8 (1M context) --- wasm/private/BUILD.bazel | 19 ++++++++++-- wasm/private/wasm_signing.bzl | 52 +++++++++----------------------- wasm/private/wasmsign2_tools.bzl | 36 ++++++++++++++++++++++ wasm/private/wsc_attestation.bzl | 22 +++++++++++--- 4 files changed, 85 insertions(+), 44 deletions(-) create mode 100644 wasm/private/wasmsign2_tools.bzl diff --git a/wasm/private/BUILD.bazel b/wasm/private/BUILD.bazel index 8f812a11..3a09f646 100644 --- a/wasm/private/BUILD.bazel +++ b/wasm/private/BUILD.bazel @@ -68,6 +68,15 @@ bzl_library( deps = ["//providers"], ) +bzl_library( + name = "wasmsign2_tools", + srcs = ["wasmsign2_tools.bzl"], + visibility = [ + "//docs:__pkg__", + "//wasm:__pkg__", + ], +) + bzl_library( name = "wasm_signing", srcs = ["wasm_signing.bzl"], @@ -75,7 +84,10 @@ bzl_library( "//docs:__pkg__", "//wasm:__pkg__", ], - deps = ["//providers"], + deps = [ + ":wasmsign2_tools", + "//providers", + ], ) bzl_library( @@ -173,5 +185,8 @@ bzl_library( "//docs:__pkg__", "//wasm:__pkg__", ], - deps = ["//providers"], + deps = [ + ":wasmsign2_tools", + "//providers", + ], ) diff --git a/wasm/private/wasm_signing.bzl b/wasm/private/wasm_signing.bzl index e7795a4d..3d77594a 100644 --- a/wasm/private/wasm_signing.bzl +++ b/wasm/private/wasm_signing.bzl @@ -1,34 +1,12 @@ """WebAssembly signing rules using wasmsign2""" load("//providers:providers.bzl", "WasmComponentInfo", "WasmKeyInfo", "WasmSignatureInfo") - -# wasmtime toolchain that runs the wasmsign2 component. -_WASMTIME_TOOLCHAIN = "@rules_wasm_component//toolchains:wasmtime_toolchain_type" - -# The wasmsign2 WASM component, passed to the wrapper as an action input. -_WASMSIGN2_WASM_ATTR = { - "_wasmsign2_wasm": attr.label( - default = "@wasmsign2_cli_wasm//file:file", - allow_single_file = True, - doc = "wasmsign2 WASM component, passed to the wrapper as an action input", - ), -} - -def _add_wrapper_tools(ctx, args): - """Add the wasmtime binary and wasmsign2.wasm to a wrapper invocation. - - They are passed as action inputs + `--bazel-*` arguments rather than located - via the wrapper's runfiles. A hardcoded runfiles Rlocation embeds the - canonical repo name, which differs when rules_wasm_component is consumed as a - dependency (it gains a `rules_wasm_component+` prefix), breaking downstream - signing — the same bug fixed for loom_wrapper in #490/#497 (issue #501). - Returns the extra action inputs to add. - """ - wasmtime = ctx.toolchains[_WASMTIME_TOOLCHAIN].wasmtime - wasm = ctx.file._wasmsign2_wasm - args.add(wasmtime, format = "--bazel-wasmtime=%s") - args.add(wasm, format = "--bazel-wasm-component=%s") - return [wasmtime, wasm] +load( + ":wasmsign2_tools.bzl", + "WASMSIGN2_WASM_ATTR", + "WASMTIME_TOOLCHAIN", + "add_wrapper_tools", +) def _wasm_keygen_impl(ctx): """Implementation of wasm_keygen rule""" @@ -45,7 +23,7 @@ def _wasm_keygen_impl(ctx): args.add("keygen") args.add("--public-key", public_key) args.add("--secret-key", secret_key) - tool_inputs = _add_wrapper_tools(ctx, args) + tool_inputs = add_wrapper_tools(ctx, args) # Run key generation via Go wrapper # The wrapper handles symlink resolution and WASI directory mapping @@ -99,8 +77,8 @@ wasm_keygen = rule( executable = True, cfg = "exec", ), - } | _WASMSIGN2_WASM_ATTR, - toolchains = [_WASMTIME_TOOLCHAIN], + } | WASMSIGN2_WASM_ATTR, + toolchains = [WASMTIME_TOOLCHAIN], doc = """ Generates a key pair for signing WebAssembly components in compact format. @@ -181,7 +159,7 @@ def _wasm_sign_impl(ctx): inputs = [input_wasm, secret_key] if public_key: inputs.append(public_key) - inputs += _add_wrapper_tools(ctx, args) + inputs += add_wrapper_tools(ctx, args) # Prepare outputs outputs = [signed_wasm] @@ -270,8 +248,8 @@ wasm_sign = rule( executable = True, cfg = "exec", ), - } | _WASMSIGN2_WASM_ATTR, - toolchains = [_WASMTIME_TOOLCHAIN], + } | WASMSIGN2_WASM_ATTR, + toolchains = [WASMTIME_TOOLCHAIN], doc = """ Signs a WebAssembly component with a cryptographic signature. @@ -356,7 +334,7 @@ def _wasm_verify_impl(ctx): inputs.append(public_key) if signature_file: inputs.append(signature_file) - inputs += _add_wrapper_tools(ctx, args) + inputs += add_wrapper_tools(ctx, args) # Run verification via Go wrapper (no shell scripts!) # The wrapper will create the marker file on success @@ -428,8 +406,8 @@ wasm_verify = rule( executable = True, cfg = "exec", ), - } | _WASMSIGN2_WASM_ATTR, - toolchains = [_WASMTIME_TOOLCHAIN], + } | WASMSIGN2_WASM_ATTR, + toolchains = [WASMTIME_TOOLCHAIN], doc = """ Verifies the cryptographic signature of a WebAssembly component. diff --git a/wasm/private/wasmsign2_tools.bzl b/wasm/private/wasmsign2_tools.bzl new file mode 100644 index 00000000..55c88db2 --- /dev/null +++ b/wasm/private/wasmsign2_tools.bzl @@ -0,0 +1,36 @@ +"""Shared helper for invoking the wasmsign2 Go wrapper. + +The wrapper (`//tools/wasmsign2_wrapper`) runs the wasmsign2 WASM component under +wasmtime. Both the wasmtime binary and the component are passed to it as +`--bazel-*` arguments (and staged as action inputs) rather than located via the +wrapper's runfiles: a hardcoded runfiles Rlocation embeds the canonical repo +name, which differs when rules_wasm_component is consumed as a dependency (it +gains a `rules_wasm_component+` prefix), breaking downstream signing/attestation +(issue #501, same fix as #490/#497). +""" + +# wasmtime toolchain that runs the wasmsign2 component. +WASMTIME_TOOLCHAIN = "@rules_wasm_component//toolchains:wasmtime_toolchain_type" + +# The wasmsign2 WASM component, passed to the wrapper as an action input. +# Merge into a rule's attrs; the rule must also set +# `toolchains = [WASMTIME_TOOLCHAIN]`. +WASMSIGN2_WASM_ATTR = { + "_wasmsign2_wasm": attr.label( + default = "@wasmsign2_cli_wasm//file:file", + allow_single_file = True, + doc = "wasmsign2 WASM component, passed to the wrapper as an action input", + ), +} + +def add_wrapper_tools(ctx, args): + """Add `--bazel-wasmtime=`/`--bazel-wasm-component=` to a wrapper invocation. + + Returns the extra action inputs (the wasmtime binary and the component) to + include in `ctx.actions.run(inputs = ...)`. + """ + wasmtime = ctx.toolchains[WASMTIME_TOOLCHAIN].wasmtime + wasm = ctx.file._wasmsign2_wasm + args.add(wasmtime, format = "--bazel-wasmtime=%s") + args.add(wasm, format = "--bazel-wasm-component=%s") + return [wasmtime, wasm] diff --git a/wasm/private/wsc_attestation.bzl b/wasm/private/wsc_attestation.bzl index 18d2a18a..4b1090d2 100644 --- a/wasm/private/wsc_attestation.bzl +++ b/wasm/private/wsc_attestation.bzl @@ -18,6 +18,12 @@ useful for CI diagnostics. """ load("//providers:providers.bzl", "WasmComponentInfo") +load( + ":wasmsign2_tools.bzl", + "WASMSIGN2_WASM_ATTR", + "WASMTIME_TOOLCHAIN", + "add_wrapper_tools", +) _TRANSFORMATION_TYPES = [ "optimization", @@ -69,11 +75,12 @@ def _wasm_attest_impl(ctx): args.add("--tool-name", ctx.attr.tool_name) args.add("--tool-version", ctx.attr.tool_version) args.add("--type", ctx.attr.transformation_type) + tool_inputs = add_wrapper_tools(ctx, args) ctx.actions.run( executable = wrapper, arguments = [args], - inputs = [input_wasm, output_input], + inputs = [input_wasm, output_input] + tool_inputs, outputs = [attested_wasm], mnemonic = "WasmAttest", progress_message = "Recording transformation attestation %s on %s" % ( @@ -141,7 +148,8 @@ wasm_attest = rule( executable = True, cfg = "exec", ), - }, + } | WASMSIGN2_WASM_ATTR, + toolchains = [WASMTIME_TOOLCHAIN], doc = """Record a transformation attestation on a WebAssembly module. Use this when you run a transformation outside of the built-in pipeline rules @@ -197,6 +205,7 @@ def _wasm_verify_chain_impl(ctx): args.add("--report-only") args.add("--bazel-marker-file=" + marker.path) + inputs += add_wrapper_tools(ctx, args) ctx.actions.run( executable = wrapper, @@ -254,7 +263,8 @@ wasm_verify_chain = rule( executable = True, cfg = "exec", ), - }, + } | WASMSIGN2_WASM_ATTR, + toolchains = [WASMTIME_TOOLCHAIN], doc = """Verify a WebAssembly module's transformation attestation chain. Emits a marker file on success; the build fails if verification fails (unless @@ -306,11 +316,12 @@ def _wasm_show_chain_impl(ctx): args.add("--input-file", input_wasm) if ctx.attr.as_json: args.add("--json") + tool_inputs = add_wrapper_tools(ctx, args) ctx.actions.run( executable = wrapper, arguments = [args], - inputs = [input_wasm], + inputs = [input_wasm] + tool_inputs, outputs = [out], mnemonic = "WasmShowChain", progress_message = "Extracting attestation chain from %s" % input_wasm.short_path, @@ -339,7 +350,8 @@ wasm_show_chain = rule( executable = True, cfg = "exec", ), - }, + } | WASMSIGN2_WASM_ATTR, + toolchains = [WASMTIME_TOOLCHAIN], doc = """Extract a WebAssembly module's transformation attestation chain to a file. Produces a JSON (default) or text rendering of the transformation chain stored