Skip to content

Fix: AuthControlled faucet leaves Authority setters unauthenticated#2958

Merged
mmagician merged 15 commits into
nextfrom
fix-h1-unauthenticated-authority
May 22, 2026
Merged

Fix: AuthControlled faucet leaves Authority setters unauthenticated#2958
mmagician merged 15 commits into
nextfrom
fix-h1-unauthenticated-authority

Conversation

@onurinanc
Copy link
Copy Markdown
Collaborator

@onurinanc onurinanc commented May 20, 2026

This PR closes #2943.

The root cause was that create_fungible_faucet configured AuthSingleSigAcl with only mint_and_send in its trigger list, leaving every other authority-gated setter (set_max_supply, the token metadata setters, and the policy-manager set_*_policy procedures) permissionless under AccessControl::AuthControlled. The fix has two parts:

Trigger list expansion: under AccessControl::AuthControlled + AuthMethod::SingleSig, the AuthSingleSigAcl trigger list now contains every authority-gated setter exported by the faucet, so each one requires a signature. Other access-control variants keep the original [mint_and_send] trigger list because their setter gate is enforced in-procedure.

Invalid combinations are rejected: (AuthControlled, NoAuth) would have left setters permissionless even with the expanded trigger list.

  • Added the private all_authority_gated_setter_roots() helper as the single source of truth for the trigger list under AuthControlled, keeps the security invariant in one place and prevents possible mistakes if more setters are added.

Copy link
Copy Markdown
Contributor

@bobbinth bobbinth left a comment

Choose a reason for hiding this comment

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

Looks good! Thank you! I left one comment inline about tightening up valid combinations of AccessControl and AuthMethod enums.

Comment on lines +599 to +603
let trigger_procedures = if is_auth_controlled {
all_authority_gated_setter_roots()
} else {
vec![FungibleFaucet::mint_and_send_root()]
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is is_auth_controlled == false a valid value here? I think of the valid combinations as follows:

  • If AccessControl == AuthControlled, we are dealing with a user account and then valid AuthMethods would be SingleSig and NoAuth (mostly for testing purposes).
  • If AccessControl is anything else, we are dealing with a network account, and then the valid AuthMethod would be NetworkAccount (maybe NoAuth too - but I'm actually not sure about this).

Copy link
Copy Markdown
Collaborator Author

@onurinanc onurinanc May 21, 2026

Choose a reason for hiding this comment

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

I think this problem will be better solved once we've done the refactor here: #2944

I have temporarily added a build_auth_component method, which I think it is a weak solution, we should consider removing it in the merging AuthMethod with AccessControl PR.

And, the other solution will be to this once we have separate create_network_fungible_faucet and create_user_fungible_faucet specified here: #2944 (comment)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, agreed. What we have has issues but works for now. We should definitely refactor this though to make the structure more intuitive, and ideally, make invalid combinations impossible by construction. #2944 would be a good place to do it.

Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

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

Looks good to me!

Comment on lines +555 to +571
/// Every authority-gated procedure root that must require a signature when
/// [`AccessControl::AuthControlled`] is paired with [`AuthMethod::SingleSig`]. Includes
/// `mint_and_send` so that minting always requires a signature regardless of the access
/// control variant.
fn all_authority_gated_setter_roots() -> Vec<AccountProcedureRoot> {
vec![
FungibleFaucet::mint_and_send_root(),
FungibleFaucet::set_max_supply_root(),
FungibleFaucet::set_description_root(),
FungibleFaucet::set_logo_uri_root(),
FungibleFaucet::set_external_link_root(),
TokenPolicyManager::set_mint_policy_root(),
TokenPolicyManager::set_burn_policy_root(),
TokenPolicyManager::set_send_policy_root(),
TokenPolicyManager::set_receive_policy_root(),
]
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not for this PR, but I think a safer design for AuthSinglesigAcl might be something similar as what we recently did for AuthMultisig, where if a procedure is called that is not configured, the threshold is set to at least default_threshold.

Similarly for the ACL, we could flip the current logic and say that only configured procedures are exempt from a signature. This is the safer default. E.g. you could say receive_asset can run without signature, but anything else requires a signature.

It would also make the configuration less cumbersome, e.g. here we need to carefully list all procedures exhaustively and if we forget just one, we might have a security hole.

I'm not yet sure how to marry this concept together with with_allow_unauthorized_input_notes and the with_allow_unauthorized_output_notes, but maybe we'd have to remove this and require users to be more explicit (e.g. explicitly configure receive_and_burn as a no-signature procedure).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think we can resolve this as described in the second approach here: #2964 (comment)

Copy link
Copy Markdown
Contributor

@bobbinth bobbinth left a comment

Choose a reason for hiding this comment

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

Looks good! Thank you! I left one comment inline - once it is addressed, we can merge (but would be good for @PhilippGackstatter to take another look).

Comment on lines +635 to +637
// AuthControlled + NetworkAccount: accepted; allowed_script_roots is the caller's
// responsibility (must not include scripts that can invoke authority-gated setters).
(AccessControl::AuthControlled, AuthMethod::NetworkAccount { allowed_script_roots }) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think this is a valid combination: a network account needs either Ownable2Step or Rbac access control approaches.

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.

The comment explicitly states that figuring out which notes are safe "is the caller's responsibility".
I'm not fully convinced this is invalid

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I've fine with keeping it for now - but I do think this is a weird combination. Maybe naming is off here - but AuthControlled really means "user-controlled" to me, and "user-controlled network account" doesn't make too much sense.

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.

So I think it is generally a valid combination, but that said: disallowing it is a good safety net.
Usually, we expect the operator of the network account to explicitly only allow consumption of scripts they have audited and vetted as safe.
But, if for some reason, they fail to assert the script's safety:

  • they missed the suspicious call during the audit
  • they accidentally / under pressure updated the allowlist to include the malicious script

then there are no guards against the script invoking e.g. set_mint_policy(AllowAll), whereas with Ownable2Step we still have the owner checks on the critical procedures

@mmagician mmagician added this pull request to the merge queue May 22, 2026
Merged via the queue into next with commit 1bc27e5 May 22, 2026
20 checks passed
@mmagician mmagician deleted the fix-h1-unauthenticated-authority branch May 22, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: AuthControlled faucet leaves Authority setters unauthenticated

4 participants