authz source pure
Package authz provides flexible authorization control for privileged actions.
Package authz provides flexible authorization control for privileged actions.
Authorization Strategies
The package supports multiple authorization strategies:
- Member-based: Single user or team of users
- Contract-based: Async authorization (e.g., via DAO)
- Auto-accept: Allow all actions
- Drop: Deny all actions
Core Components
- Authority interface: Base interface implemented by all authorities
- Authorizer: Main wrapper object for authority management
- MemberAuthority: Manages authorized addresses
- ContractAuthority: Delegates to another contract
- AutoAcceptAuthority: Accepts all actions
- DroppedAuthority: Denies all actions
Quick Start
Example
1// Initialize with contract deployer as authority
2var member address(...)
3var auth = authz.NewWithMembers(member)
4
5// Create functions that require authorization
6func UpdateConfig(cur realm, newValue string) error {
7 return auth.DoByPrevious(0, cur, "update_config", func() error {
8 config = newValue
9 return nil
10 })
11}
See example_test.gno for more usage examples.
7
func NewRestrictedContractAuthority
1func NewRestrictedContractAuthority(path string, handler PrivilegedActionHandler, proposer Authority) AuthorityNewRestrictedContractAuthority creates a new contract authority with a proposer restriction.
SECURITY:
- `handler` is the same Class-4 captured-callback risk as NewContractAuthority — runs synchronously inside Authorize with the consumer's authority. Register only trusted handler functions.
- `proposer` is an open-interface input (Class-3 impl-substitution). A hostile proposer Authority can always-approve creation of any proposal, defeating the restriction. Pass canonical impls only.
func NewWithAuthority
NewWithAuthority creates a new Authorizer with a specific authority.
SECURITY: `authority` is an open-interface input — any value satisfying Authority is accepted. A malicious impl can always-approve (privilege escalation) or always-deny (DoS). Prefer canonical impls from this package (NewMemberAuthority, NewContractAuthority, NewAutoAcceptAuthority, NewDroppedAuthority) unless you specifically need a foreign impl.
func NewWithMembers
NewWithMembers creates a new Authorizer whose authority is a MemberAuthority containing the given addresses. Callers express authority intent at the call site:
Example
1// "auth realm is the authority"
2a := authz.NewWithMembers(cur.Address())
3
4// "previous realm is the authority" (from a crossing function)
5a := authz.NewWithMembers(cur.Previous().Address())
6
7// "EOA caller is the authority" (from init(cur realm))
8if !cur.Previous().IsUserCall() {
9 panic("realm must be initialized by EOA")
10}
11a := authz.NewWithMembers(cur.Previous().Address())
This replaces the previous NewWithCurrent / NewWithPrevious / NewWithOrigin sugar — those baked runtime.{Current,Previous,Origin} reads into the constructor, which (a) prevented use from package- level var initializers, (b) made the EOA-origin check inside NewWithOrigin an indirect address comparison rather than the straightforward IsUserCall predicate, and (c) coupled the constructor to the runtime walks the rest of the migration is moving away from.
func NewAutoAcceptAuthority
func NewContractAuthority
NewContractAuthority creates a new contract-based authority.
SECURITY (Class-4 captured callback): `handler` is a caller-supplied closure that runs SYNCHRONOUSLY inside Authorize, with the consumer's authority. A hostile handler can swallow actions, log the caller, or execute arbitrary code under the consumer's frame. The package-internal wrappedAction guards "execute action only from contractAddr" but the handler can call wrappedAction however it likes (multiple times, never, out of order). Register only trusted handler functions; treat handler registration as the trust boundary.
func NewMemberAuthority
7
type Authority
interface1type Authority interface {
2 // Authorize executes a privileged action if the caller is authorized
3 // Additional args can be provided for context (e.g., for proposal creation)
4 Authorize(caller address, title string, action PrivilegedAction, args ...any) error
5
6 // String returns a human-readable description of the authority
7 String() string
8}Authority represents an entity that can authorize privileged actions. It is implemented by MemberAuthority, ContractAuthority, AutoAcceptAuthority, and DroppedAuthority.
Authority is the canonical safe shape for cross-package authority interfaces: methods are address-typed (no realm/cur crosses the interface boundary), and consumers correctly derive `caller` from `cur.Previous().Address()` under `rlm.IsCurrent()` before invoking Authorize. No cur-leak (class 1) is possible through this interface.
However, two RESIDUAL RISKS apply:
-
Class-3 impl-substitution: NewWithAuthority and Authorizer.Transfer accept any Authority impl. A malicious Authority can always-approve (silent privilege escalation) or always-deny (denial-of-service). Consumers should pass canonical impls from this package (MemberAuthority, ContractAuthority, AutoAcceptAuthority, DroppedAuthority) unless they have explicit reason to register a foreign impl. We do not expose an IsCanonicalAuthority allowlist because the package is intentionally extensible — third-party impls are the design intent.
-
Class-4 closed-over-authority: NewContractAuthority and NewRestrictedContractAuthority capture a caller-supplied PrivilegedActionHandler closure. The handler runs synchronously inside Authorize with the consumer's authority. A hostile handler can swallow actions, log the caller, or execute arbitrary code under the consumer's frame. Register only trusted handler functions. See r/gnops/valopers/init.gno for the realistic registration shape.
We do NOT seal Authority via an unexported marker method — that pattern is bypassable via embedding in Gno; see p/test/seal/filetests/z_seal_*_filetest.gno for the four bypass tests.
type Authorizer
structAuthorizer is the main wrapper object that handles authority management. It is configured with a replaceable Authority implementation.
Methods on Authorizer
func Authority
method on AuthorizerAuthority returns the auth authority implementation
func DoByCurrent
method on Authorizer1func (a *Authorizer) DoByCurrent(_ int, rlm realm, title string, action PrivilegedAction, args ...any) errorDoByCurrent executes a privileged action authorized as `rlm`. `rlm` must be the caller's own live cur (asserted via rlm.IsCurrent()); the authorized principal is `rlm.Address()`. To authorize as the realm that called your function, use `DoByPrevious`.
Example
1auth.DoByCurrent(0, cur, "update_config", func() error { ... }) // current realm authorizes
2auth.DoByPrevious(0, cur, "update_config", func() error { ... }) // calling realm authorizes
The `_ int` first parameter is a deliberate sentinel that pushes `rlm realm` past the first-arg position so DoByCurrent stays a non-crossing method — otherwise it would be a crossing method and rlm.Previous() inside would resolve one realm deeper than the caller intended.
SECURITY: the IsCurrent guard closes Class-2 designation forgery (see docs/resources/gno-security.md). A realm value's .Address() is set when the value is minted at a crossing frame; the value can in principle be stored and replayed. Without IsCurrent, a hostile realm could capture a high-privilege realm's cur.Previous() (e.g., when that realm called into it) and later pass the stored value here to authorize actions as that realm. IsCurrent rejects stale captures by requiring the value to match the topmost live crossing frame's cur.
func DoByPrevious
method on Authorizer1func (a *Authorizer) DoByPrevious(_ int, rlm realm, title string, action PrivilegedAction, args ...any) errorDoByPrevious executes a privileged action authorized as the realm that called the function invoking DoByPrevious. `rlm` must be the caller's own live cur; the principal is derived as `rlm.Previous().Address()`. Mirrors the Transfer/AddMember pattern: always take live cur, derive the caller-of-caller internally rather than accepting a stored/forwarded realm value.
func String
method on AuthorizerString returns a string representation of the auth authority
func Transfer
method on AuthorizerTransfer changes the auth authority after validation. rlm must be the caller's own captured cur (asserted via rlm.IsCurrent()); the principal is rlm.Previous().Address(). Closes the address-parameter forgery: an external realm cannot supply Owner() as `caller` to bypass the underlying Authority's check.
SECURITY (runtime substitution): once the current authority approves a Transfer, the new authority is installed and effective on the next call. If an attacker ever becomes the authority — even briefly — they can install a permanent DroppedAuthority (DoS) or an AutoAcceptAuthority (privilege escalation). Consumers concerned about this should wrap Transfer with a one-shot guard or a quorum/cooldown check.
`newAuthority` is also an open-interface input — see NewWithAuthority's Class-3 caveat. Pass canonical impls.
type AutoAcceptAuthority
structAutoAcceptAuthority implements an authority that accepts all actions AutoAcceptAuthority is a simple authority that automatically accepts all actions. It can be used as a proposer authority to allow anyone to create proposals.
type ContractAuthority
structContractAuthority implements async contract-based authority
type MemberAuthority
structMemberAuthority is the default implementation using addrset for member management.
Methods on MemberAuthority
func AddMember
method on MemberAuthorityAddMember adds a new member to the authority. rlm must be the caller's own captured cur; the principal is rlm.Previous().Address() and must already be a member. The IsCurrent guard closes the forgery where an external realm passes Owner() as caller to bypass members.Has(caller).
func AddMembers
method on MemberAuthorityAddMembers adds a list of members to the authority. Same rlm contract as AddMember.
func Authorize
method on MemberAuthorityfunc Has
method on MemberAuthorityHas checks if the given address is a member of the authority
func RemoveMember
method on MemberAuthorityRemoveMember removes a member from the authority. Same rlm contract as AddMember.
func String
method on MemberAuthorityfunc Tree
method on MemberAuthorityTree returns a read-only view of the members tree
type PrivilegedAction
funcPrivilegedAction defines a function that performs a privileged action.
type PrivilegedActionHandler
funcPrivilegedActionHandler is called by contract-based authorities to handle privileged actions.
8
- chain stdlib
- errors stdlib
- gno.land/p/moul/addrset package
- gno.land/p/moul/once package
- gno.land/p/nt/avl/v0 package
- gno.land/p/nt/avl/v0/rotree package
- gno.land/p/nt/ufmt/v0 package
- strings stdlib