Skip to content

ci(defi): check endpoints exported in a canister's WASM against its Candid specification#10147

Open
gregorydemay wants to merge 12 commits into
masterfrom
gdemay/check-endpoints-ic-canisters
Open

ci(defi): check endpoints exported in a canister's WASM against its Candid specification#10147
gregorydemay wants to merge 12 commits into
masterfrom
gdemay/check-endpoints-ic-canisters

Conversation

@gregorydemay
Copy link
Copy Markdown
Contributor

@gregorydemay gregorydemay commented May 8, 2026

Summary

Use ic-wasm to check the endpoints of a canister’s WASM exports against its Candid interface. This is to ensure that all exported endpoints are intentional and match the Candid specification, helping to detect any accidental, unexpected or potentially malicious exports.

Wraps the existing ic-wasm invocation in bazel/canisters.bzl's finalize_wasm rule with an optional check-endpoints step, then opts the five DeFi-team canisters in dfinity/ic into it. The check fails the build if a canister WASM exports a method that is neither in the candid service declaration nor explicitly allowlisted in a per-canister hidden_endpoints.conf — catching accidental public exports (a dropped #[query(hidden = true)], a leaked FFI symbol, etc.) before they ship.

Closes DEFI-2501.
Closes DEFI-2502.
Closes DEFI-2503.
Closes DEFI-2504.
Closes DEFI-2511.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in ic-wasm check-endpoints validation step to the Bazel canister finalization pipeline and enables it for selected DeFi-team canisters, using per-canister allowlists to prevent accidental public exports from shipping.

Changes:

  • Extend bazel/canisters.bzl finalize_wasm to optionally run ic-wasm check-endpoints using a hidden_endpoints.conf allowlist.
  • Add hidden_endpoints.conf allowlists and Bazel wiring for the ICP/ICRC1 ledgers and DeFi canisters (ckBTC minter, ckETH minter, ledger-suite orchestrator, BTC checker).
  • Bump ic-wasm (and Bazel crate metadata/locks) to a version that supports the check-endpoints feature.

Reviewed changes

Copilot reviewed 18 out of 20 changed files in this pull request and generated no comments.

Show a summary per file
File Description
rs/ledger_suite/icrc1/ledger/hidden_endpoints.conf Adds allowlist of lifecycle/hidden exports for ic-wasm check-endpoints.
rs/ledger_suite/icrc1/ledger/BUILD.bazel Enables hidden_endpoints for production variants; opts test-only variants out.
rs/ledger_suite/icp/ledger/ledger_canisters.bzl Adds hidden_endpoints parameter pass-through to rust_canister.
rs/ledger_suite/icp/ledger/hidden_endpoints.conf Adds allowlist for ICP ledger hidden/system/legacy exports.
rs/ledger_suite/icp/ledger/hidden_endpoints_allowance_getter.conf Adds allowlist variant for the allowance-getter build that exports allowance.
rs/ledger_suite/icp/ledger/BUILD.bazel Opts ICP ledger wasm targets into endpoint checking with the appropriate allowlist.
rs/ethereum/ledger-suite-orchestrator/hidden_endpoints.conf Adds allowlist for orchestrator hidden/system exports.
rs/ethereum/ledger-suite-orchestrator/BUILD.bazel Opts orchestrator canister into endpoint checking.
rs/ethereum/cketh/minter/hidden_endpoints.conf Adds allowlist for ckETH minter hidden/system exports (timers, metrics, lifecycle).
rs/ethereum/cketh/minter/hidden_endpoints_debug.conf Adds debug-variant allowlist for extra debug-only exported endpoints.
rs/ethereum/cketh/minter/BUILD.bazel Wires allowlists into both prod and debug ckETH minter builds.
rs/bitcoin/ckbtc/minter/hidden_endpoints.conf Adds allowlist for ckBTC minter hidden/system exports.
rs/bitcoin/ckbtc/minter/hidden_endpoints_debug.conf Adds debug-variant allowlist for self-check/debug-only endpoints.
rs/bitcoin/ckbtc/minter/BUILD.bazel Wires allowlists into both prod and debug ckBTC minter builds.
rs/bitcoin/checker/hidden_endpoints.conf Adds allowlist for BTC checker hidden/system exports (metrics, transform, lifecycle).
rs/bitcoin/checker/BUILD.bazel Opts BTC checker canister into endpoint checking.
Cargo.Bazel.toml.lock Updates locked Rust dependencies for the ic-wasm bump (incl. new transitive deps).
Cargo.Bazel.json.lock Updates Bazel crate universe lock for the ic-wasm bump and new dependencies.
bazel/rust.MODULE.bazel Enables ic-wasm check-endpoints feature and bumps the allowed ic-wasm version range.
bazel/canisters.bzl Adds hidden_endpoints plumbing and optional check-endpoints genrule step.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

gregorydemay and others added 10 commits May 11, 2026 12:59
Extends rust_canister and finalize_wasm in bazel/canisters.bzl with
an optional hidden_endpoints kwarg pointing at a hidden_endpoints.conf
file. When provided, ic-wasm check-endpoints --hidden <conf> runs
between the metadata-stamping and gzip steps of the existing
finalize_wasm cmd_bash chain, failing the build if the wasm exports
a method that is neither in the candid service nor allowlisted in
the conf.

The kwarg defaults to None, so existing canisters that don't opt in
see no behavior change. The rust_ledger_canister wrapper in
rs/ledger_suite/icp/ledger/ledger_canisters.bzl threads the kwarg
through to rust_canister.

Mirrors the pattern already shipped in dfinity/bitcoin-canister and
dfinity/dogecoin-canister, where hidden_endpoints.conf lists
legitimate non-candid exports (lifecycle hooks, http_request,
canister_global_timer, ic_cdk_timers' timer_executor, vendored FFI
symbols, and Rust's main).

Per-canister opt-in lands in subsequent commits (DEFI-2501, 2502,
2503, 2504, 2511).
…eature

ic-wasm 0.9.x introduces a `check-endpoints` subcommand that fails
the canister build if the wasm exports a method that is neither in
the candid `service` nor allowlisted in a `hidden_endpoints.conf`
file. The IC repo previously pinned ^0.8.4 (predates the
subcommand) with `default_features = False` and only the `exe`
feature enabled — `check-endpoints` lives behind its own cargo
feature, so opting in requires both bumping the version and adding
the feature flag.

Verified the existing `shrink` and `metadata` invocations in
`bazel/canisters.bzl`'s `finalize_wasm` rule still work unchanged
(the BTC checker rebuild succeeds end-to-end on 0.9.11 with the
existing pipeline).

Subsequent commits opt individual canisters into the new check
(DEFI-2501, 2502, 2503, 2504, 2511).
Adds rs/bitcoin/checker/hidden_endpoints.conf and threads it into
the rust_canister rule via the new optional `hidden_endpoints` kwarg.
The conf lists the legitimate non-candid exports of the BTC checker:
the metrics http_request, the HTTP-outcall transform query, the two
lifecycle hooks, and Rust's `main` symbol.

`bazel build //rs/bitcoin/checker:btc_checker_canister.wasm.gz`
now reports `Canister WASM and Candid interface match!` as part of
finalization and fails the build if a future change exports an
unlisted method.
Both variants (`ledger_suite_orchestrator_canister` and
`..._getblocksdisabled`) now go through `ic-wasm check-endpoints`
during finalization. The shared hidden_endpoints.conf lists
canister_global_timer, the metrics http_request, lifecycle hooks,
and Rust's main symbol.
Two confs because the ckbtc_minter_debug variant compiles in three
extra endpoints behind the `self_check` cargo feature
(canister_query:self_check, canister_update:refresh_fee_percentiles,
canister_update:upload_events) that are not present in the
production ckbtc_minter wasm. ic-wasm check-endpoints is strict in
both directions (extra entries in --hidden are also flagged), so a
single permissive conf can't satisfy both variants.

Both confs share the timer, http_request metrics endpoint, the
__get_candid_interface_tmp_hack candid-extraction helper (declared
#[query(hidden = true)] in main.rs), the lifecycle hooks, and the
Rust main symbol.
Two confs because the cketh_minter_debug variant exports an extra
`check_audit_log` query gated behind the `debug_checks` cargo
feature, which is absent from cketh_minter.did.

Both confs share the IC system-level `canister_global_timer` plus
the ic-cdk-timers dispatcher `<ic-cdk internal> timer_executor`,
the metrics http_request, the lifecycle hooks (init, pre_upgrade,
post_upgrade), and the Rust main symbol. No FFI symbols from
ic_secp256k1 / ic_sha3 leaked into the WASM exports — the IC's
ic_secp256k1 wrapper appears to keep them internal.
Wires the production-shape variants (`ledger_canister`,
`_u256`, `_nextledgerversion`, `_u256_nextledgerversion`) of the
ICRC1 ledger through ic-wasm check-endpoints, against a single
hidden_endpoints.conf listing the lifecycle hooks, the
#[query(hidden = true)] http_request metrics endpoint, the
__get_candid_interface_tmp_hack helper (declared #[query] but
absent from ledger.did), and Rust's main symbol.

The `_getblocksdisabled` and `_canbench` variants are deliberately
opted out of the check (hidden_endpoints = None) because they
ship with a wasm shape that diverges from `ledger.did`: the former
disables `get_blocks` in code while the .did still declares it,
and the latter compiles in canbench-rs's `__canbench__*` /
`__tracing__*` benchmark exports and replaces `canister_init`
with the canbench harness's own. Both are test-only variants;
gating them on check-endpoints would either require a separate
.did or upstream changes to canbench-rs / the get-blocks-disabled
feature itself.
All 4 variants of the ICP ledger now go through ic-wasm
check-endpoints during finalization. The base hidden_endpoints.conf
covers the 12 legacy `_pb` protobuf endpoints (declared via
#[unsafe(export_name = "canister_query <name>")] /
#[unsafe(export_name = "canister_update <name>")]), the metrics
http_request, the candid-extraction __get_candid_interface_tmp_hack
helper (declared #[query] but absent from ledger.did), the
lifecycle hooks, and Rust's main symbol.

The `_allowance-getter` variant gets a separate
hidden_endpoints_allowance_getter.conf because the
`icp-allowance-getter` cargo feature compiles in an extra
`canister_query:allowance` endpoint not present in ledger.did.
Run `bazel run //pre-commit:buildifier-fix` and add the missing
`Args:` docstring section to `finalize_wasm` so that
`//pre-commit:buildifier-check` passes again on the autofix CI
gate.

No behavior change.
@gregorydemay gregorydemay force-pushed the gdemay/check-endpoints-ic-canisters branch from 7855207 to f48baff Compare May 11, 2026 10:59
pull Bot pushed a commit to mikeyhodl/ic that referenced this pull request May 11, 2026
## Summary
- Bumps `ic-wasm` from `^0.8.x` to `^0.9.11` in both `Cargo.toml` and
`bazel/rust.MODULE.bazel`.
- Split out from dfinity#10147 so the version bump lands independently of the
`check-endpoints` rollout.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: IDX GitHub Automation <infra+github-automation@dfinity.org>
gregorydemay and others added 2 commits May 11, 2026 14:38
The feature flag was lost during a merge conflict resolution
on top of master after ic-wasm was bumped to 0.9.11 there.
Without it the ic-wasm binary does not expose the
`check-endpoints` subcommand and canister finalize steps fail
with "unexpected argument 'check-endpoints' found".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gregorydemay gregorydemay changed the title ci(DEFI-2487): add ic-wasm check-endpoints to DeFi-team canisters ci: add ic-wasm check-endpoints to DeFi-team canisters May 11, 2026
@gregorydemay gregorydemay changed the title ci: add ic-wasm check-endpoints to DeFi-team canisters ci(defi): check endpoints exported in a canister's WASM against its Candid specification May 11, 2026
@gregorydemay gregorydemay marked this pull request as ready for review May 11, 2026 13:08
@gregorydemay gregorydemay requested review from a team as code owners May 11, 2026 13:08
@gregorydemay gregorydemay requested a review from mbjorkqvist May 11, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants