Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Workflow: Design Access Control

Use this workflow when you need to design who can call what zome functions, how remote signals are authorized, or how admin operations are gated.

Step 1: Identify Callers

Map every zome function to its caller type:

FunctionCallerNotes
create_postUI (same agent)No grant needed
recv_remote_signalAny remote agentNeeds Unrestricted grant
update_admin_statusAdmin agent onlyProgenitor check
get_shared_resourceSpecific partner agentAssigned grant

Questions to answer:

  • Is the caller the same agent as the cell owner? (No grant needed)
  • Can any agent call this function? (Unrestricted)
  • Can only a specific agent call this? (Assigned)
  • Can anyone with a token call this? (Transferable)

Step 2: Choose Pattern per Function

Caller scopePatternWhere
Same agent (UI)No grantN/A
Any agentCapAccess::Unrestricted in init()init() callback
Named agent(s)CapAccess::AssignedOn-demand grant creation
Token holderCapAccess::TransferableOn-demand grant creation
Admin-onlyProgenitor check in coordinatorCoordinator function body

Step 3: Design Cap Grants

For each function requiring a grant:

Function: recv_remote_signal
Grantor: self (init)
Grantee: all
Access: Unrestricted
Grant timing: init() on first run
Function: approve_member
Grantor: progenitor cell
Grantee: specific delegate agent
Access: Assigned { secret, assignees: [delegate_pubkey] }
Grant timing: progenitor creates grant on delegation
Secret distribution: progenitor sends via private entry to delegate

Step 4: Write the init() Function

For every Unrestricted grant, add to init():

#![allow(unused)]
fn main() {
#[hdk_extern]
pub fn init(_: ()) -> ExternResult<InitCallbackResult> {
    let mut functions = HashSet::new();

    // Add each function that needs an unrestricted grant:
    functions.insert((zome_info()?.name, "recv_remote_signal".into()));
    // functions.insert((zome_info()?.name, "another_open_fn".into()));

    create_cap_grant(ZomeCallCapGrant {
        tag: "open_functions".into(),
        access: CapAccess::Unrestricted,
        functions: GrantedFunctions::Listed(functions),
    })?;

    Ok(InitCallbackResult::Pass)
}
}

Step 5: Write Validation Constraints

For admin operations, the coordinator check is the enforcement point:

#![allow(unused)]
fn main() {
pub fn admin_only_function(input: AdminInput) -> ExternResult<ActionHash> {
    // Always check first — before any state mutation
    if !check_if_progenitor()? {
        return Err(wasm_error!(WasmErrorInner::Guest(
            "This function is restricted to the network progenitor.".into()
        )));
    }

    // Proceed with admin logic
}
}

For update/delete operations, also validate in the integrity zome using must_get_action():

#![allow(unused)]
fn main() {
// In integrity validate() for update ops:
let original = must_get_action(original_action_hash)?;
if action.author() != original.action().author() {
    return Ok(ValidateCallbackResult::Invalid("Not the original author".into()));
}
}

Reference

  • Cap grant patterns: AccessControl.md
  • Progenitor setup: Architecture.md § DNA Properties
  • must_get_* authorship checks: Patterns.md § must_get