Skip to content

feat: gate input-note script execution behind a trust policy#2136

Open
JereSalo wants to merge 21 commits into
nextfrom
note-script-trust-policy
Open

feat: gate input-note script execution behind a trust policy#2136
JereSalo wants to merge 21 commits into
nextfrom
note-script-trust-policy

Conversation

@JereSalo

@JereSalo JereSalo commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator

Adds a per-request NoteScriptTrustPolicy that controls whether the client will execute the script of an input note. The new default, StandardScriptsOnly, rejects any input note whose script root is not a recognized standard (P2ID, P2IDE, SWAP, MINT, BURN). Previously, non-standard scripts could be silently fetched from the node and executed.

Callers that need to consume notes with custom scripts must opt in explicitly, either by listing the script roots they trust via TransactionRequestBuilder::trusted_input_note_script_roots(...), or by acknowledging that they have already approved the unlisted scripts via ::allow_unlisted_note_scripts_after_user_approval(). The preflight check runs before the network-transaction script registration submits its own transaction, and again before direct execute_transaction callers, so a rejected request never reaches execution.

The CLI gains an --allow-unlisted-note-scripts flag on consume-notes for the equivalent opt-in.

Closes #2133

The check now lives in `GrpcClient::get_note_script_by_root` and returns
`RpcError::InvalidResponse` on a root mismatch, with the invariant
documented on the `NodeRpcClient` trait. Removes the duplicate check from
`ClientDataStore::get_note_script` and ensures every caller of the RPC
method (including `ensure_ntx_scripts_registered`) gets the verification.
Introduces NoteScriptTrustPolicy on TransactionRequest so the SDK no
longer silently fetches and executes unknown input-note scripts. The
policy is checked at the user-facing transaction boundary
(execute_transaction and submit_new_transaction_with_prover) before any
side-effectful work, and is also applied by the note screener so
discovery does not run unverified bytecode.

Defaults to StandardScriptsOnly (P2ID, P2IDE, SWAP, MINT, BURN). Custom
scripts are opted in via TransactionRequestBuilder::trusted_input_note_
script_roots, or the request can opt out of root-based gating with
allow_unlisted_note_scripts_after_user_approval for clients that have
their own approval flow. Failures surface as
ClientError::UntrustedNoteScript.

The policy is part of TransactionRequest's serialized form, so a
deserialized request carries its own trust decision through the same
preflight as a freshly built one.
@JereSalo JereSalo added the maintainer PRs that come from internal contributors or integration partners. They should be given priority. label Apr 28, 2026
Adds a `--allow-unlisted-note-scripts` flag to the `consume-notes` CLI
and updates the integration tests that consume custom-script notes to
opt in via `trusted_input_note_script_roots`, so they pass under the
new default `StandardScriptsOnly` policy.
@JereSalo JereSalo changed the base branch from next to jere/verify-fetched-note-script-root April 29, 2026 14:14
… opt-in

The CLI's --allow-unlisted-note-scripts flag was collecting every input
note's script root and passing them as TrustedScriptRoots, which is
equivalent to AllowUnlistedAfterApproval here since the notes are fixed
at that point. Call the unlisted-policy method directly instead.

Drop the _after_user_approval suffix on the builder method: calling the
method is the approval, and the suffix is doc copy in name form.
Distinguish gated user-authorized transaction execution from speculative
consumability probes (NoteScreener during sync, consume-notes auto-discovery)
that may run scripts but discard their effects and stay outside the policy.
@JereSalo JereSalo marked this pull request as ready for review April 30, 2026 16:36
Base automatically changed from jere/verify-fetched-note-script-root to next April 30, 2026 21:42

@TomasArrachea TomasArrachea left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good! This feature seems like worth mentioning in the docs.

JereSalo added 4 commits May 6, 2026 16:54
# Conflicts:
#	CHANGELOG.md
#	crates/rust-client/src/rpc/tonic_client/mod.rs
After merging next, NoteScript::root() and StandardNote::script_root()
now return NoteScriptRoot instead of Word. Convert at boundaries so the
policy API and call sites continue to use Word.
@JereSalo

JereSalo commented May 7, 2026

Copy link
Copy Markdown
Collaborator Author

Looks good! This feature seems like worth mentioning in the docs.

Yeah I've now added mention to it, I don't know if I chose the right place to do so but I documented it both in the cli and in the rust-client library docs. LMK if you were expecting it to be somewhere else.
839d45e

Comment thread docs/external/src/rust-client/library.md Outdated
Comment thread crates/rust-client/src/errors.rs
Comment thread crates/rust-client/src/transaction/request/builder.rs
Comment thread crates/rust-client/src/transaction/mod.rs Outdated
JereSalo added 2 commits May 14, 2026 11:48
# Conflicts:
#	CHANGELOG.md
#	bin/miden-cli/src/commands/new_transactions.rs
#	crates/rust-client/src/errors.rs
#	crates/rust-client/src/transaction/mod.rs
#	crates/testing/miden-client-tests/src/tests.rs
#	docs/external/src/rust-client/library.md
- collect all rejected note-script roots before erroring (was: first only)
- add error hint with remediation for UntrustedNoteScript
- display the active trust policy in the error message
- note in builder rustdoc that the two policy setters overwrite rather than append
- link StandardNote docs.rs in the library guide and polish wording
- add multi-root regression test
@JereSalo JereSalo requested a review from SantiagoPittella May 14, 2026 20:31
# Conflicts:
#	crates/rust-client/src/transaction/mod.rs
JereSalo added 2 commits June 16, 2026 11:28
After merging next, Felt::new is fallible and setup_two_wallets_and_faucet
takes AccountType instead of AccountStorageMode. Update the trust-policy
tests (Word::from integer arrays, AccountType::Private) so the test build
compiles.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer PRs that come from internal contributors or integration partners. They should be given priority.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a trust policy for lazy-fetched note scripts

3 participants