Skip to content

Permissions — Three-Axis Access Control

TL;DR: Every host binding runs through WasmAccessManager.CheckAccess which bitwise-ANDs three flag masks: ObjectContext (is the current VM an Avatar/Prop/World?), OwnerContext (does the script run under the local player’s ownership?), and ScopeContext (is the wrapped object part of the script’s own content, or is it external?). A zero on any axis throws WasmAccessDeniedException. The “not generated = not callable” principle covers everything outside this check.

ObjectContext — what kind of content is the script attached to?

Section titled “ObjectContext — what kind of content is the script attached to?”

File: CVR-GameFiles/WasmScripting/CVRScriptObjectContext.cs

[Flags]
public enum CVRScriptObjectContext : byte {
None = 0,
Avatar = 1,
Prop = 2,
World = 4,
Any = 7,
}

Set once at VM construction by looking at the root content type (CVRAvatar, CVRSpawnable, or the world scene).

OwnerContext — who controls the content?

Section titled “OwnerContext — who controls the content?”

File: CVR-GameFiles/WasmScripting/CVRScriptOwnerContext.cs

[Flags]
public enum CVRScriptOwnerContext : byte {
None = 0,
Self = 1,
Other = 2,
Any = 3,
}
  • AvatarSelf iff the avatar is worn by the local player; otherwise Other.
  • PropSelf iff spawned by the local player; otherwise Other.
  • World — always Any (no per-user ownership).

ScopeContext — what object is the call touching?

Section titled “ScopeContext — what object is the call touching?”

File: CVR-GameFiles/WasmScripting/CVRScriptScopeContext.cs

[Flags]
public enum CVRScriptScopeContext : byte {
None = 0,
Self = 1,
ExternalContent = 2,
Any = 3,
}

Computed per-wrapped-object by WasmAccessManager.GetWrappedObjectScope:

  • If the target GameObject/Component is a child of the script’s root transform or (for worlds) lives in the scene (not DontDestroyOnLoad): Self.
  • If it’s a prefab or a CVR “internal” object: None (effectively closed).
  • Otherwise: ExternalContent.

File: CVR-GameFiles/WasmScripting/WasmAccessManager.cs

Two overloads:

public void CheckAccess(string memberName,
CVRScriptObjectContext objectContextMask,
CVRScriptOwnerContext ownerContextMask)
{
if ((ownerContextMask & ScriptContext.OwnerContext) == 0) throw new WasmAccessDeniedException(...);
if ((objectContextMask & ScriptContext.ObjectContext) == 0) throw new WasmAccessDeniedException(...);
}
public void CheckAccess(string memberName,
CVRScriptObjectContext objectContextMask,
CVRScriptOwnerContext ownerContextMask,
CVRScriptScopeContext scopeContextMask,
CVRScriptScopeContext selfScopeContext)
{
if ((scopeContextMask & selfScopeContext) == 0) throw new WasmAccessDeniedException(...);
if ((ownerContextMask & ScriptContext.OwnerContext) == 0) throw new WasmAccessDeniedException(...);
if ((objectContextMask & ScriptContext.ObjectContext) == 0) throw new WasmAccessDeniedException(...);
}

Every generated binding and every hand-written *Bindings.cs call either the 3-arg or the 5-arg overload. The 5-arg one is used whenever the binding targets a specific object handle (most of UnityEngine.*).

CVR-GameFiles/WasmBinder.Links.UnityEngine/TransformLink.cs

data.AccessManager.CheckAccess(
"UnityEngineTransform__get__position",
CVRScriptObjectContext.Any,
CVRScriptOwnerContext.Any,
CVRScriptScopeContext.Any,
scope);

Any VM context, any owner, any scope can read a Transform’s position — including external content.

data.AccessManager.CheckAccess(
"UnityEngineTransform__set__position",
CVRScriptObjectContext.Any,
CVRScriptOwnerContext.Any,
CVRScriptScopeContext.Self,
scope);

Any VM, any owner, but scope must be Self. You can read any transform in the scene, but you can only move transforms that live inside your own content root.

CVR-GameFiles/WasmScripting/LocalPlayerBindings.cs

data.AccessManager.CheckAccess("CVR_LocalPlayer_SetPosition",
CVRScriptObjectContext.World,
CVRScriptOwnerContext.Any);

Two axes only. Moving the local player teleports your own camera — but is allowed only from world scripts (ObjectContext.World). An avatar or prop script that tries it throws.

CVR-GameFiles/WasmScripting/PropBindings.cs

data.AccessManager.CheckAccess("CVR_Prop_Destroy",
CVRScriptObjectContext.World,
CVRScriptOwnerContext.Any);

Only world scripts can destroy props via the CVR API. (Players can still despawn their own props through the CVR UI regardless of scripts.)

CVR-GameFiles/WasmScripting/FileStorageLinks.cs

data.AccessManager.CheckAccess("FileStorage_WriteInternal_Full",
CVRScriptObjectContext.World,
CVRScriptOwnerContext.Any);

File storage is world-only.

WasmAccessDeniedException — plain C# exception raised inside the host binding. WasmBindingUtils.RaiseException marshals it back into the guest so you can try/catch in your script. Error messages explain which axis failed, e.g.:

  • Access to member UnityEngineTransform__set__position denied in a Avatar scope context. You may be trying to access objects outside of your script's scope.
  • Access to member CVR_LocalPlayer_SetPosition denied in a Avatar object context. You may be trying to do operations restricted to certain content types.

Beyond CheckAccess, the sandbox enforces an implicit rule: if a host function doesn’t have a linker.DefineFunction binding, and the guest module tries to import it, BindingManager.FillNonLinkedWithEmptyStubs replaces it with a no-op stub. Your call silently does nothing. See Not Exposed for the list.

These generalizations cover ~95% of the binder surface; individual bindings can still be stricter.

API categoryTypical object × owner × scope
Getters (reading a property)(Any, Any, Any) — available from every context, including on external content.
Setters / instance methods (mutating state)(Any, Any, Self) — you can only mutate objects inside your own content root.
Static methods on bound types (Vector3.Cross, Mathf.Sin, Quaternion.Lerp, etc.)(Any, Any, Any) — no scope check because there is no this object. Runs entirely inside the guest.
Method parameters that take wrapped objectsThe parameter object must also resolve to Self (or Any for read-only helpers). Passing in an external-content handle usually throws.
World gameplay APIs (LocalPlayer.*, FileStorage.*, WorldPermissions.*, CVR_Prop_Destroy, …)(World, Any, —) — only callable from world scripts.
Getter on network / instance state (Networking.GetPing, GetInstanceOwner, Player.GetAllPlayers, …)(Any, Any, Any) but sometimes (World, Any, Any) when the data is per-instance.

Observe freely, modify conservatively. Scripts may inspect anything in the scene (raycast the world from an avatar, read a prop’s position, iterate players), but may not modify external content without explicit binding support.

When a call fails with WasmAccessDeniedException, the message names the failing axis:

  • scope context — you passed or touched an object outside your own content root. Wire the reference at build-time to a child of your root.
  • object context — the API is world-only or avatar/prop-only. Move the logic to a script in the right content.
  • owner context — the API requires you to own the content (worn avatar, spawned prop). Gate the call on an ownership check first.
  • CVR-GameFiles/WasmScripting/CVRScriptObjectContext.csNone, Avatar, Prop, World, Any.
  • CVR-GameFiles/WasmScripting/CVRScriptOwnerContext.csNone, Self, Other, Any.
  • CVR-GameFiles/WasmScripting/CVRScriptScopeContext.csNone, Self, ExternalContent, Any.
  • CVR-GameFiles/WasmScripting/CVRScriptContext.csObjectContext is derived from ICleanableContent (is the root a CVRAvatar, CVRSpawnable, or CVRWorld?); OwnerContext reads WornByMe / SpawnedByMe.
  • CVR-GameFiles/WasmScripting/WasmAccessManager.cs — the ToWrapped resolver, GetWrappedObjectScope, and both CheckAccess overloads.
  • CVR-GameFiles/WasmScripting/WasmAccessDeniedException.cs — the exception type (registered as code 74 in WasmBindingUtils.ExceptionToId).