Skip to content

fix(contract): propagate with_actor/with_scope to bound function#361

Open
wolfy-j wants to merge 5 commits into
mainfrom
fix/contract-actor-propagation
Open

fix(contract): propagate with_actor/with_scope to bound function#361
wolfy-j wants to merge 5 commits into
mainfrom
fix/contract-actor-propagation

Conversation

@wolfy-j

@wolfy-j wolfy-j commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Problem

A contract framed with :with_actor() / :with_scope() silently dropped the actor. The Lua contract module sets Actor/HasActor/SecurityScope/HasScope on OpenCmd, but the dispatcher never applied them:

  • system/contract/dispatcher.go handleOpen passed only openCmd.Scope (the binding value bag) to Instantiate and forked the ambient frame context.
  • instanceImpl.Call built the runtime.Task with value pairs only, no security pairs.

Net effect: a bound function always ran under the caller's ambient security.actor(). Framing a specific owner (e.g. a background job registering a component on behalf of a user) was ignored, surfacing as No authenticated actor found. By contrast funcs works because it appends secapi.ActorPair/ScopePair to Task.Context.

Fix

Mirror the funcs mechanism: carry the open-time actor/scope onto instanceImpl (set in handleOpen from OpenCmd — same package, no interface change), and append ActorPair/ScopePair to task.Context in Call only when explicitly framed. Non-framed calls keep inheriting the ambient context unchanged.

Tests

  • TestIntegration_ActorPropagation — opened :with_actor("framed-user"), the bound function must observe that actor (was no-actor before the fix).
  • TestIntegration_ActorPropagation_PersistsAcrossCalls — the framed actor persists across multiple calls on the same instance.

go build ./..., go vet, and the system/contract / runtime/lua/modules/contract / funcs suites all pass.

wolfy-j added 5 commits June 17, 2026 12:12
A contract framed with :with_actor()/:with_scope() shipped the actor and
security scope in OpenCmd, but the dispatcher dropped them: handleOpen only
passed the binding scope to Instantiate, and instanceImpl.Call built the
task with no security pairs. So a bound function always ran under the
caller's ambient actor, and framing a specific owner was silently ignored.

Carry the open-time actor/scope onto the instance and append ActorPair/
ScopePair to task.Context on every call (the same mechanism funcs uses for
its with_actor/with_scope), so the bound function runs under the framed
security context. Only applied when explicitly framed, so non-framed calls
keep inheriting the ambient context unchanged.

Adds integration tests asserting the framed actor reaches the bound
function and persists across multiple calls on the same instance.
Add a securityFramer capability interface so handleOpen frames instances
through an explicit port rather than a concrete-type assertion; a custom
Instantiator can now opt into security framing.

Tests:
- scope propagation via with_scope reaches the bound function
- with_actor overrides an ambient actor; no framing inherits it
- instance-level proof that a framed scope carries its policies
…ags, dedup test handler

- remove redundant instance != nil guard (comma-ok assertion handles nil)
- group hasActor/hasScope flags after the value fields
- extract shared actorReportFunc; collapse four duplicate inline handlers
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.

1 participant