Skip to content

harden xcall: execute admitted cross-context calls under a least-privilege identity, not members.first() #3206

Description

@chefsale

Summary

Cross-context calls (#[app::xcall]) execute the target method under members.first() of the target context — a real owned/admin member with that context's full write authority. We should harden this to run xcalls under a least-privilege scoped identity, so an admitted xcall cannot exercise more authority in the target than the call actually needs.

Current state

crates/context/src/handlers/execute/mod.rs:1217:

// Find an owned member of the target context to execute as — one that has permissions there.
let members: Vec<_> = xcall_context_client
    .get_context_members(&target_context_id, Some(true))
    .try_collect()
    .await
    .unwrap_or_default();

let Some((target_executor, _is_owned)) = members.first() else { ... };

The target then executes via execute_with_origin(...) under target_executor's identity, with the source context tagged as env::xcall_origin().

What already mitigates this (why this is a follow-up, not a live hole)

Since this was first raised, the reachability of an xcall has been gated at the node level, so this is defense-in-depth, not an open door:

Residual risk

Once an xcall is admitted by the policy gate, it still runs under a real member's full authority in the target context. The target's in-handler env::xcall_origin() check is the last line of defense against a permissive-policy entry point being driven to do more than intended.

Proposed hardening

  • Execute admitted xcalls under a least-privilege synthetic/scoped identity rather than members.first(), so the effective write authority is bounded to what the entry point requires.
  • Document the origin-check requirement (env::xcall_origin()) as a first-class part of the #[app::xcall] contract, so app authors don't rely solely on the node gate.

References

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions