Skip to content

Access Control

HoloMUSH uses Attribute-Based Access Control (ABAC) as its security model. The same policy engine governs everything: which commands a player can run, what an admin can do, and what a plugin is allowed to access.

PrincipalWhat’s controlledHow policies are set
PlayersCommands they can use (say, describe, create)Role-based seed policies (player, builder, admin)
AdminsPrivileged commands (boot, shutdown, wall, aliases)Admin role seed policies
PluginsEvent emission, world queries, storage, command executionDeclared in the plugin’s plugin.yaml

The engine is default-deny. If there’s no policy granting access, the action is rejected.

Policies are evaluated on every action. The engine receives a request with three parts — who is asking (principal), what they want to do (action), and what they want to do it to (resource) — and checks it against all installed policies.

For players and admins, command authorization uses two layers at dispatch time. Layer 1 checks whether the character can execute the command at all (via command:<name> policies). Layer 2 checks whether the character has the class of permissions the command needs — each command declares structured capabilities like {action: emit, resource: stream, scope: local}, and the engine verifies the character could perform those actions on those resource types. Both layers must pass before the handler runs.

For plugins, policies are declared in the plugin manifest and installed when the plugin loads. They’re removed when the plugin unloads.

Policies use a Cedar-style DSL. Each policy has a name and a dsl block:

policies:
- name: "emit-events"
dsl: |
permit(principal is plugin, action in ["emit"], resource is stream) when {
principal.plugin.name == "my-plugin"
};
PartMeaning
principal is pluginWho is asking — plugin for plugin policies
action in ["emit"]What they want to do — a list of allowed actions
resource is streamWhat they want to do it to — the resource type
when { ... }Additional conditions that must be true
OperatorMeaning
==Equals
!=Not equals
> < >= <=Numeric comparison
&&Logical AND
||Logical OR
!Logical NOT
likeGlob pattern match (* wildcard)
inValue is in list
containsAllCollection contains all values
containsAnyCollection contains any value

Attribute paths use dot notation: principal.plugin.name, resource.stream.name.

Access neededActionResource typeResource pattern
Emit events to streams"emit"stream"stream:*"
Read locations"read"world_object"location:*"
Read characters"read"world_object"character:*"
Read objects"read"world_object"object:*"
Key-value read"read"kv"kv:*"
Key-value write"write"kv"kv:*"
Key-value delete"delete"kv"kv:*"
Execute commands"execute"command"command:*"

Commands declare capability requirements. The built-in roles grant these capabilities:

CapabilityRole(s)Commands
comms.pageplayer and abovepage, whisper
objects.createbuilder, admincreate (objects)
objects.setbuilder, adminset (object properties)
player.aliasplayer and abovealias, unalias, aliases
admin:bootadminboot
admin:shutdownadminshutdown
admin:walladminwall
admin:aliasadminsysalias, sysunsalias, sysaliases
admin:password.resetadminresetpassword
admin:password.setadminresetpassword (explicit password)
admin:session.kickadminresetpassword --kick

Commands without capability requirements (like say, look, pose, quit) are available to all authenticated players.

The server ships with seed policies for the built-in roles. You don’t need to write policy DSL for normal operations.

Key things to know:

  • Plugin policies are self-contained. Plugins declare what they need in their manifest. Review what a plugin asks for before loading it.
  • Denied actions are logged. If a player or plugin hits a permission boundary, the server logs it with the principal, action, and resource.
  • Custom roles are planned but not yet available. Currently the role set is fixed: player, builder, admin.