openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
653 lines (560 loc) • 39.5 kB
Markdown
---
summary: "Operator roles, scopes, and approval-time checks for Gateway clients"
read_when:
- Debugging missing operator scope errors
- Reviewing device or node pairing approvals
- Adding or classifying Gateway RPC methods
title: "Operator scopes"
---
Operator scopes gate what a Gateway client can do after it authenticates.
They are a control-plane guardrail inside one trusted Gateway operator domain,
not hostile multi-tenant isolation. For strong separation between people,
teams, or machines, run separate Gateways under separate OS users or hosts.
Related: [Security](/gateway/security), [Gateway protocol](/gateway/protocol),
[Gateway pairing](/gateway/pairing), [Devices CLI](/cli/devices).
## Connection roles
Every Gateway WebSocket client connects with one role:
- `operator`: control-plane clients such as CLI, Control UI, automation, and
trusted helper processes.
- `node`: capability hosts (macOS, iOS, Android, headless) that expose
commands through `node.invoke`.
Operator RPC methods require the `operator` role. Node-originated methods
require the `node` role.
## Scope levels
| Scope | Meaning |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `operator.read` | Read-only status, lists, catalog, logs, session reads, retained audit and execution-identity diagnostics, and other non-mutating calls. |
| `operator.sessions.read` | Read visible sessions, history, and session metadata without general Gateway read access. |
| `operator.sessions.write` | Read visible sessions, organize owned sessions, and start or continue the authenticated person's own work. |
| `operator.write` | Mutating operator actions: sending messages, invoking tools, updating talk/voice settings, node command relay. Also satisfies `operator.read`. |
| `operator.admin` | Administrative access. Satisfies every `operator.*` scope. Required for config mutation, updates, native hooks, reserved namespaces, and high-risk approvals. |
| `operator.pairing` | Device and node pairing management: list, approve, reject, remove, rotate, revoke. |
| `operator.approvals` | Exec and plugin approval APIs. |
| `operator.questions` | Listing, reading, answering, and resolving interactive questions. |
| `operator.talk` | Creating, steering, and closing Talk sessions without general Gateway write access. `operator.write` also satisfies this scope. |
| `operator.talk.secrets` | Reading Talk configuration with secrets included. |
Personal GitHub connection management is a narrowly self-scoped exception to
read-only behavior: `users.github.*` requires `operator.read` plus the exact
authenticated durable profile. That person can connect, poll, cancel,
reconnect, or disconnect only their own account. These methods do not expose
team secrets, mutate shared configuration, or grant OpenClaw write/admin scopes. System
and per-agent GitHub changes remain `operator.admin`.
Session-scoped readers can read shared GitHub publication options and receipts
for sessions they can view through `sessions.github.options` and
`sessions.github.status`. For these narrow callers, the shared publication option
is available only when the session has a current managed worktree or repository
workspace with a supported GitHub remote. Unavailable targets do not hide existing
shared receipts. Reopen the chat or reconnect after the managed workspace changes
to refresh its options. Personal account discovery and personal receipts still
require `operator.read` and the authenticated owner. Identity, role, access grant,
connection, and session visibility are rechecked before returning awaited reads.
With `operator.sessions.write`, a requester can publish ordinary changes from
sessions they created through the shared GitHub account. Workflow definition
changes require the original requester's current full `operator.write`
authority. Personal publication also requires `operator.write`. Every publication
still requires current session authorization. See
[GitHub connections](/concepts/user-model#github-connections).
Unknown future `operator.*` scopes require an exact match unless the caller
already holds `operator.admin`.
`operator.sessions.write` includes `operator.sessions.read`. Broad
`operator.read` also includes session reads, and `operator.write` includes both
session scopes. The session scopes do not grant general diagnostics,
configuration changes, Gateway-wide tool invocation, or publication outside
the own-session shared-account path described above.
Session readers can browse visible conversations and receive their updates.
The Control UI can copy visible history as Markdown. Session writers can rename,
pin, archive, or restore their own existing conversations. Other people's visible
sessions remain read-only under these grants, including shared sessions.
Session grants never authorize deletion or changes to an existing session's
sharing and visibility.
Archiving a session does not grant permission to delete it.
Session readers can use `sessions.files.list`, `sessions.files.get`, and
`sessions.files.assets` for files within a visible session's workspace. Reads
outside that root also require the session's file tools to allow Gateway-host
access and the caller to have current permission to start a turn there, including
ownership, sharing-role, agent, and sandbox checks. Such previews are read-only.
`canvas.document.preview` also accepts session read access: it returns the
caller's HTML and isolated sandbox location without reading session data.
Stored Canvas documents still require broader read access through
`canvas.document.view`.
In the Control UI, session writers can use **New Session**, send messages in
their own conversations, and stop their own active runs. Their model, effort,
fast-mode, and non-full permission choices use the same session grant and the
Gateway's allowed model catalog. Full permission mode, sandbox changes, and
changes to an existing session's context window still require administrator access.
Reconnecting rechecks current permission before replaying a Stop for its original run.
Own-work methods, including message sending, ordinary session creation,
recovery, and forks, accept `operator.sessions.write` where their parameters
do not require administrator access. Session ownership, current authority,
agent access, and runtime and sandbox requirements still apply. The
`artifacts.list`, `artifacts.get`, and `artifacts.download` APIs require
the broader `operator.read` scope.
Hidden native sub-agents started through `sessions_spawn` use the initiating
person's session-write authority for the exact owned child and its private
parent completion. The child retains that person's current authority, model
policy, and required sandbox. Direct `agent` RPC calls keep their existing
broader scope requirements; copying a child session or run identifier does not
grant launch permission.
RPCs, events, and background tools use the same scope rules. A continuation with
`operator.write` can read its GitHub identity and session state without another
interactive message. Session access and execution-lifetime checks still apply.
`question.*` also accepts `operator.sessions.write` for ordinary questions
bound to the caller's own admitted run and owned session. The Gateway records
that binding from trusted run authority, never from caller-supplied session or
run identifiers. The same ownership check filters question events and recovery
reads. Session visibility or membership alone does not grant this access.
`operator.sessions.read` alone cannot answer questions. Sessionless questions,
secret prompts, and other privileged question workflows retain their existing
`operator.questions` and administrative checks.
## Named operator roles
Team Gateways can bind authenticated durable profiles to named operator roles.
Each role controls access to other people's sessions,
agents available for session creation and agent runs, a maximum set of operator
scopes, and whether newly created sessions require sandboxing. It can also require
an access policy supplied by a plugin.
```json5
{
gateway: {
roles: {
default: "guest",
assignments: {
byGithubLogin: { octocat: "maintainer" },
},
definitions: {
maintainer: {
sessions: { others: "write" },
agents: ["roboclaw"],
scopes: ["operator.read", "operator.write", "operator.approvals"],
},
guest: {
sessions: { others: "view" },
agents: ["roboclaw"],
scopes: ["operator.sessions.read", "operator.sessions.write"],
sandbox: "required",
},
},
},
},
}
```
Use the administrator-scoped `users.setRole` Gateway method with
`{ profileId, role }` to assign a configured role. Set `role: null` to clear an
assignment. Assignment changes immediately invalidate and close that profile's
active Gateway connections. Reconnecting applies the current role and scope
ceiling. A committed change still retires the previous access if returning the
result fails. An authorized self-downgrade receives its response before its
connection closes.
Use `gateway.roles.assignments.byGithubLogin` to declare assignments before a
person's first login or share the same mapping across Gateways. Keys are GitHub
logins, matched case-insensitively after trimming; malformed logins and duplicate
normalized logins are rejected. Every mapped role must exist in `definitions`.
A valid explicit `users.setRole` assignment wins, followed by the mapping for the
profile's cached verified primary GitHub identity, then `gateway.roles.default`. Profiles without
a cached GitHub identity never match this mapping. Clearing an explicit assignment
with `role: null` exposes the configured mapping or default again.
`users.list` keeps `role` as the explicit assignment and reports `effectiveRole`
with `roleSource` (`"assigned"`, `"githubLogin"`, or `"default"`).
`gateway.roles.default` is required whenever roles are configured and must name
an existing definition. Omitting `gateway.roles` entirely leaves solo and
shared-secret deployments unchanged.
Set a role's optional `accessPolicyPlugin` to the exact plugin ID when that plugin
must confirm the person's current access. For example, the Visitor Access plugin
requires `accessPolicyPlugin: "visitor-access"` on its restricted default role.
A denied connection receives the `OPERATOR_ACCESS_DENIED` connect error detail,
and the Control UI explains that the account has no access.
The requirement belongs to Gateway configuration and remains enforced when the
plugin or its manifest is missing, disabled, broken, or still starting. A loaded
plugin must return current authority for the person; another plugin's policy
cannot satisfy the requirement. Configuration validation permits an unavailable
plugin reference so the Gateway can still start for repair. Independent staff
roles without this binding and the Gateway owner retain their existing access.
Restore the required plugin to admit the bound role. Removing or changing the
binding applies through the same live role-policy update described below.
With live configuration reload enabled, edits to `gateway.roles` (including
GitHub login assignments) and
`gateway.auth.identityScopes` apply without restarting the Gateway. Existing
Gateway clients reconnect to receive the current scope ceiling, except for changes
confined to model policies as described below and identity-scope edits that leave
an operator WebSocket login’s resolved grant set unchanged. Editing another login
or reordering the same scopes preserves that connection, its accepted runs, and
queued inputs. Changing its own resolved grants revokes retained and delegated
work; restoring the grant does not revive the original authority. Clients without
verified identities, node connections, HTTP requests, and plugin auth cookies do
not consume identity-scope grants and are unaffected by these edits. Pending handshakes and
mutations recheck the policy before acquiring authority;
already-admitted runs retain their normal completion and cancellation lifecycle,
including cancellation when their original access-policy grant expires or is revoked.
When roles are configured, identity-authenticated operator connections do not
receive reusable device or bootstrap tokens: those tokens are not bound to a
person and could bypass the role ceiling. Device-token or bootstrap-token
authentication without a verified user identity is rejected for operator
Gateway connections and HTTP requests. Reconnect through the trusted proxy or
another supported verified identity, such as Tailscale. Node connections,
shared-secret/password access, and Gateways without role configuration retain
their existing behavior.
For sessions created by other people, `sessions.others` supports these values:
- `"none"`: hides foreign sessions from lists and targeted access, filters
session-level usage to visible sessions, and denies Gateway-wide `usage.cost`
because its aggregate can include hidden sessions.
- `"view"`: allows reading but does not allow mutation, even when a session is
otherwise shared.
- `"suggest"`: allows viewing and the existing suggestion flow.
- `"write"`: allows participation in foreign sessions. Draft and incognito
restrictions remain in force.
A person always owns their own sessions. Explicit session membership can raise
`"view"` or `"suggest"` access for a specific session, and connections already
holding `operator.admin` retain their administrative session access.
Set `agents: "*"` to allow session creation and agent runs on every agent, list
agent IDs to allow only those agents, or use an empty array to disallow both.
The allowlist also applies when a run targets an already-existing session.
Agent discovery and the Control UI agent picker show only agents allowed by the
caller's role. An empty allowlist returns an empty agent roster; the Gateway's
session-routing ownership remains unchanged.
Discovery does not grant or revoke access to existing sessions: the role's
separate session-read policy still governs which sessions the caller can read.
The Control UI waits for a live agent roster after loading or reconnecting, so
an older browser roster cannot restore agents excluded by the current role.
Set a role's optional `modelPolicy` to limit the models used by its requests:
```json5 validate=false
// Inside gateway.roles.definitions.guest
modelPolicy: {
sourceAgent: "shared-agent",
deny: ["provider/restricted-*"],
}
```
With no `allow` override, the policy follows that agent's configured primary and
fallback models in order. Omitting `sourceAgent` uses the configured system or
default agent, or the sole agent. A multi-agent Gateway without such an owner
must name a source agent. This reads the existing agent configuration; it does
not copy a model list or grant access to every installed model.
Use `allow` to replace the permitted set centrally, and `deny` to exclude models
from either source. An empty `allow: []` denies all models. Both lists accept
exact `provider/model` references, aliases from the source agent's model settings,
and trailing wildcards such as `provider/*`, `provider/family/*`, or
`provider/restricted-*`. Other wildcard placements are rejected. Exclusions
match resolved identities, so aliases cannot bypass them and a family prefix
also excludes newly configured family members.
The role policy is an additional ceiling. Existing agent rules still govern
manual model selection. Default can use the first permitted source model that
the target agent already allows manually or through its configured primary and
fallback chain. Automatic retries filter that chain before preparing providers;
an empty permitted result returns an error instead of widening access.
With config reload enabled, edits confined to existing roles' `modelPolicy`
settings apply when the config transaction commits, without restarting the
Gateway or reconnecting its clients. Other role changes hot-apply and reconnect
clients with current authority, including when combined with a model-policy edit.
Disabling config reload leaves the current policy active until config application
resumes.
The original role ceiling follows queued work and child runs. Accepted work must
satisfy both its original model ceiling and the current policy. Removing one
source model cancels its active model requests and blocks later calls using it,
while still-permitted sibling models and unrelated work retain their authority.
New requests use the updated source choices. Direct model requests, title
previews, and user-invoked model completion or decision tools apply the same
policy. Interactive plugin runtime attempts must certify exact model-policy
enforcement before they can execute restricted requests, regardless of who supplies
their credentials. Currently the built-in OpenClaw runtime supports these attempts;
uncertified plugin runtimes, including Codex, refuse them with a compatible-runtime error.
Adding a policy also cancels uncertified work that started without one, including
retained work after its foreground turn finishes. An outer selected model does
not establish which model a native runtime actually uses. Isolated prompt-only
completions keep their separate exact-route contract and bind the selected model
through completion and cleanup.
Bounded automatic metadata, operator-configured
inbound media preprocessing, and host-owned execution approval keep their
existing service authority. Omitting `modelPolicy` preserves the role's existing
model access, and shared-secret System access is unchanged.
Native Codex staff work without a model policy has a limited attribution case
when qualified native hooks are disabled or unavailable: previously accepted
unrestricted input mixed with other work may continue under the receiver's valid
authority after its sender loses authorization or becomes restricted. Direct and
otherwise unambiguously bound work still observes revocation. See the
[native model-policy boundary](/plugins/codex-harness/routing#operator-role-model-permissions).
Visitor Access requires an explicit model policy; its Codex runs require the
qualified integration.
The optional `sandbox` policy defaults to `"inherit"`, which keeps the agent's
configured sandbox mode. Set `sandbox: "required"` to sandbox every new session
created by an authenticated person with that role, even when the agent's
sandbox mode is `"off"`. The example lets maintainers use host execution on
`roboclaw` while guest-created sessions on the same agent remain sandboxed.
Required sandboxes are isolated per authenticated session creator, not merely
per agent or per session. Different guests using the same agent receive separate
sandbox environments and workspaces. Multiple sessions created by the same guest
reuse that guest's environment and workspace. This per-guest boundary applies
regardless of the configured sandbox scope. If the agent configures
`workspaceAccess: "rw"`, OpenClaw reduces access to `"ro"` for role-required
sessions and logs an `agent/sandbox` warning, preventing the shared agent
workspace from becoming a writable bridge between guests. Maintainer sessions
and other sessions without a role-required sandbox keep their configured scope
and workspace access.
The Gateway records the authenticated creator and their sandbox requirement
together before a new session first runs, including chat, the OpenAI-compatible
HTTP endpoints, Talk, recovery, forks, checkpoint branches, cron, outbound
messages, and spawned children.
Delegated child work inherits a required parent's original creator and sandbox
policy, even after role changes. Recovery and branching requested by another
person use that person's own role rather than the source session's policy.
Required creation provenance is immutable. Role changes, sharing, participation,
`sessions.patch`, whole-entry replacement, legacy imports, and canonical-key
repair cannot remove or replace an existing required stamp. Blocked persisted
overwrites emit a `session-sqlite` warning. Inspect them with
[`openclaw logs --follow`](/cli/logs). Existing unstamped sessions and new sessions
whose creator does not require sandboxing retain their existing behavior.
A person whose role requires sandboxing cannot start a run in an existing
host-execution session, even when explicitly invited. Required sessions
fail if their sandbox backend is unavailable or provisioning fails. They never
fall back to the Gateway or a node. `/elevated`, `exec` host overrides, and
configured host targets cannot bypass this restriction.
By default, the agent's managed GitHub identity is not injected into sandboxed
execution: `GH_CONFIG_DIR` is absent, and `GH_TOKEN` and `GITHUB_TOKEN` are blanked.
An administrator can set `agents.entries.<id>.tools.github.allowInSandbox: true`
to expose that agent's managed identity to its own Docker or Podman sandbox,
including role-required sandboxes isolated per creator. The profile is mounted
read-only at `/openclaw/github`; sandboxed commands receive the managed token and
Git author. Effective `"shared"` scope refuses this injection and logs a warning
naming the agent. `openclaw security audit` warns for each opted-in agent. See
[GitHub identity](/gateway/config-tools/github-identity#sandbox-opt-in) for the
credential boundary and backend requirements.
The role's `scopes` list caps scopes granted through connection auth, identity
grants, pairing, scope upgrades, and authenticated trusted-proxy HTTP requests.
The ceiling uses the normal scope implications: `operator.admin` permits every
operator scope, and `operator.write` permits `operator.read` and `operator.talk`.
It only filters existing grants. It cannot add scopes the connection did not
already receive.
The ceiling intersects capabilities, including those implied by a broader grant.
A write grant narrowed to a read-only role retains `operator.read`; an admin-only
grant narrowed to a write role retains `operator.write`. The role cannot grant
capabilities that the original credential did not allow, and an empty grant or
role remains empty.
Session reads and organization use the last successfully applied role
configuration. A rejected configuration reload leaves those permissions
unchanged.
This includes plugin HTTP requests and WebSocket upgrades: without a scope
header, ordinary Gateway-authenticated plugin routes start with only
`operator.write`, then apply the role ceiling. A read-only role therefore retains
`operator.read` on that path, while an empty role receives no runtime scopes.
Control UI plugin grants carry the authenticated profile inside a signed
cookie. Plugin HTTP requests reapply the profile's current role ceiling and
reject grants without a matching durable identity when roles are enabled.
Include `operator.admin` explicitly only when that role should retain
administrative connection authority.
An administrator-attested [channel identity link](/concepts/user-model#channel-identity-links)
also lets a sender inherit channel-owner authority from their effective role's
`operator.admin` scope. This does not require an additional identity-scope grant.
When roles are absent, channel ownership uses a matching administrative
identity-scope grant instead. Connection scope grants and ceilings are unchanged.
Named roles apply to authenticated durable profiles and their attested channel
identities. They organize collaboration within one trusted Gateway domain and do
not replace separate Gateways when hostile-tenant isolation is required.
Diagnostic audit methods, including `audit.run.inspect`, remain shared-domain
`operator.read` surfaces and are not filtered by session role. Likewise,
`operator.write` still authorizes Gateway-wide operations such as tool
invocation, ordinary node command relay, and other write-scoped control-plane
actions. Session restrictions do not turn that scope into a per-person
isolation boundary. Use separate Gateways when mutually untrusted people must
not share diagnostics or control-plane write authority.
## Identity scope grants
`gateway.auth.identityScopes` grants operator scopes to verified user
identities from trusted-proxy auth or Tailscale WhoIs:
```json5
{
gateway: {
auth: {
identityScopes: {
"admin@example.com": ["operator.admin"],
"operator@example.com": ["operator.read", "operator.write"],
},
},
},
}
```
The key is the verified proxy identity or Tailscale WhoIs login. Email keys
match case-insensitively. Non-email identities match exactly. Config validation
rejects scope names outside the closed set above.
Connection authority is resolved in this order:
1. For trusted-proxy Control UI connections, `x-openclaw-scopes` first caps
device enrollment or upgrade requests. Device authorization then establishes
the persistent scopes. A device-less session contributes no self-declared
scopes.
2. OpenClaw unions a matching server-side identity grant with those scopes.
3. OpenClaw applies `x-openclaw-scopes` to the final union as the session cap.
An absent header means no cap. A present-but-empty header yields no scopes.
4. If the authenticated profile has an effective named operator role,
OpenClaw intersects the result with that role's configured scope ceiling.
The result is used for both `hello.auth.scopes` and Gateway method
authorization. Identity grants are session-only: they do not create or modify
pairing records or request a device scope upgrade. Token, password, and no-auth
connections carry no verified identity and receive no grant.
Identity grants apply only to `operator`-role connections. `node`-role connections never receive them.
## Method scope is only the first gate
Each Gateway RPC has a least-privilege method scope that decides whether a
request reaches its handler. Params-aware methods derive that scope before
dispatch so authorization failures have one canonical structured response:
- `agent` needs `operator.write` for ordinary turns and `operator.admin` for
`/new` or `/reset` session lifecycle commands.
- `node.invoke` needs `operator.write` for ordinary relay commands and
`operator.admin` when relaying `browser.proxy`, `browser.proxy.upload.v1`,
`fs.listDir`, or `terminal.upload` to a node.
- The top-level `fs.listDir` RPC needs `operator.write` for Gateway-host
requests and `operator.admin` when `nodeId` targets a node. Its handler limits
non-admin Gateway-host browsing to configured agent workspaces.
- `plugins.sessionAction` requires every scope declared in the selected action's
`requiredScopes`. Omitted or empty lists default to `operator.write`.
`operator.write` satisfies `operator.read` and `operator.talk`. Other scopes
require an exact match, or `operator.admin`.
- `sessions.create` accepts `operator.sessions.write` for ordinary own-session
creation, including a `projectId`, or the broader `operator.write` scope.
Incognito sessions and any `execNode` request require `operator.admin`.
For non-admin callers, the handler limits `cwd` to configured agent
workspaces. `projectId` cannot be combined with `cwd` or `execNode`.
- `environments.list` needs `operator.read` for plain inventory and
`operator.write` when `runtimeId` requests runtime-specific command eligibility.
Session placement methods derive
their scope from the requested target before schema validation:
`sessions.dispatch` needs `operator.write` for `deviceId` and
`operator.admin` for `profileId` or a target-less
`cloudWorkers.projectProfiles` lookup. `sessions.move` needs `operator.write`
for Gateway or device targets and `operator.admin` for profile targets.
`abandonSource: true` remains `operator.write` but is schema-valid only with
a Gateway target and runtime-valid only for an exact offline device source.
`sessions.reclaim` remains `operator.write`. Malformed dispatch params or a
malformed move target use `operator.write` so the handler can return the
precise schema error. All three methods retain session ownership,
participation, and commit-time revalidation fences. `operator.read` alone
cannot start, stop, or move a session. Cloud profile allocation and mutation,
pairing and Connect machine, raw `environments.create` or
`environments.destroy`, incognito sessions, direct `execNode` execution, and
arbitrary host or node paths remain `operator.admin`.
- `worktrees.branches` needs `operator.write`. Its handler limits non-admin
callers to workspace-contained paths or registered-project roots. Other host
paths require `operator.admin`.
- `talk.config` needs `operator.read`. `includeSecrets: true` also needs
`operator.talk.secrets`.
- `talk.client.*`, `talk.session.*`, `talk.speak`, and `talk.mode` need
`operator.talk` (or the compatible broader `operator.write`).
- `sessions.patch` and `sessions.patchMany` accept `operator.sessions.write`
for organization fields, `model`, `agentRuntime`, thinking level, fast mode,
and non-full permission modes on owned sessions. Metadata patches require
an existing owned session; they cannot claim an unused key. Broad
`operator.write` retains its existing session access. Full permission mode,
sandbox changes, native runtime consent, and privileged or unknown fields
require `operator.admin`. Runtime availability and sandbox checks still
apply. Persisting a selected model as the configured agent default is
admin-only.
- `sessions.delete` requires `operator.write` for an archived-only request
with the supported fields, and `operator.admin` otherwise. Neither session
scope authorizes deletion.
- `session.reactions.list` accepts `operator.sessions.read` or broader read
access and requires current session visibility. `session.reactions.set` requires `operator.write`, an identified
author, and permission to send to or suggest in the session. A view-only
operator role cannot react.
Project RPCs use these scopes:
| Method | Required scope and additional gate |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `projects.list` | `operator.sessions.read` or broader read access; only callers satisfying `operator.write` receive `repoRoot` and `originUrl`. |
| `projects.add` | `operator.write` and the `controlPlaneWrite` method flag. |
| `projects.register`, `projects.remove` | `operator.admin`. |
| `projects.searchRemote` | `operator.read`. |
Some handlers then apply stricter checks based on the concrete thing being
approved or mutated:
- `device.pair.approve` is reachable with `operator.pairing`, but approving an
operator device can only mint or preserve scopes the caller already holds.
- `node.pair.approve` is reachable with `operator.pairing`, then derives extra
approval scopes from the pending node's declared command list.
- `chat.send` is a write-scoped method, but the `/config set` and
`/config unset` chat commands require `operator.admin` on top of that,
regardless of the caller's chat-send scope.
This lets lower-scope operators perform low-risk pairing actions without
making all pairing approval admin-only.
Session mutation RPCs are authorized by their negotiated operator scopes,
independent of the connecting client's `client.id` or `client.mode`. Client
identity can still affect connection and device-auth policy, but it neither
grants nor removes session mutation authority.
`audit.run.inspect` intentionally uses `operator.read`. Every client with that
scope in a Gateway operator domain may receive the retained execution-identity
context, including bounded pseudonymized references and secret-redacted display
labels. `operator.read` is not a per-person or hostile multi-tenant privacy
boundary. Operators who must keep this data separate need separate Gateway
trust domains.
## Device pairing approvals
Device pairing records are the durable source of approved roles and scopes.
An already-paired device does not get broader access silently: a reconnect
that asks for a broader role or broader scopes creates a new pending upgrade
request.
A connected limited Control UI with broad read access can file that same pending request through
**Inbox > System > Limited access > Request admin** without attempting a broader
reconnect. The request is bound to the signed device identity on the live connection. Approval still
comes from `device.pair.approve` and therefore requires `operator.pairing` plus
authority for every requested scope. After approval rotates the operator token,
the Gateway returns the new token only to that device's live waiter. The browser
stores it before reconnecting. Canceling the wait or disconnecting before
approval falls back to the ordinary pairing repair flow on the next connection.
A role with only `operator.admin` permits the Control UI's full operator scope
request. Approval is still required. The role ceiling does not grant device
scopes on its own.
Requests outside the authenticated person's assigned role ceiling are denied,
not queued for device approval. The Gateway checks the current role again after
approval, before returning the token, so a role demotion during the wait still
blocks an out-of-role result. The Control UI shows the denial and administrator
guidance without **Retry**. An administrator must change the role first.
The explicit exception is the administrator-capable Control UI owner profile
issued directly on the Gateway host by `openclaw dashboard` or graphical
onboarding. Its short-lived, single-use bootstrap can approve the exact closed
scope set for a fresh browser or upgrade an existing limited credential only
when it binds to that same signed browser keypair. Generic Control UI and
Telegram handoffs, mobile setup profiles, shared credentials, locality, and
caller-selected scopes do not receive this exception.
Approving a device request:
- A request with no operator role does not need operator scope approval.
- A request for a non-operator device role (for example `node`) requires
`operator.admin`, even though `device.pair.approve` itself only needs
`operator.pairing`.
- A request for `operator.read`, `operator.write`, `operator.approvals`,
`operator.sessions.read`, `operator.sessions.write`, `operator.questions`, `operator.pairing`, `operator.talk`, or
`operator.talk.secrets` requires
the caller to already hold that scope, or `operator.admin`.
- A request for `operator.admin` requires `operator.admin`.
- A repair request with no explicit scopes can inherit the existing operator
token's scopes. If that token is admin-scoped, approval still requires
`operator.admin`.
Non-admin shared-secret and trusted-proxy sessions can only approve
operator-device requests within their own declared operator scopes. Approving
non-operator roles is admin-only even when those sessions can otherwise use
`operator.pairing`.
For paired-device token sessions, management is self-scoped unless the caller
has `operator.admin`: a non-admin caller sees only its own pairing entries, and
can approve, reject, rotate, revoke, or remove only its own device entry.
## Node pairing approvals
`node.pair.*` capability approvals are stored on the paired device record in
the shared SQLite pairing store. Gateways migrate any remaining entries from
the retired standalone `nodes/paired.json` store into those records once at
startup. See [Gateway pairing](/gateway/pairing) for details.
`node.pair.approve` derives extra required scopes from the pending request's
command list:
| Declared commands | Required scopes |
| ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| none | `operator.pairing` |
| ordinary node commands | `operator.pairing` + `operator.write` |
| `system.run`, `system.run.prepare`, `system.which`, `browser.proxy`, `browser.proxy.upload.v1`, `fs.listDir`, or `system.execApprovals.get/set` | `operator.pairing` + `operator.admin` |
Here, `fs.listDir` is the node command declared for relay through `node.invoke`,
not the top-level Gateway RPC described above.
Approving a node declaration records its command surface. For `computer.act`,
the node advertises that surface only after Computer Control is enabled locally.
Once the pairing update is approved, invoking it through `node.invoke` requires
write scope but not admin scope for each action. Commands classified as
dangerous or privacy-heavy still require a persistent
`gateway.nodes.commands.allow` entry in addition to pairing.
Node pairing establishes identity and trust. It does not replace a node's own
`system.run` exec approval policy.
## Shared-secret auth
Shared gateway token/password auth is treated as trusted operator access for
that Gateway. OpenAI-compatible HTTP surfaces, `/tools/invoke`, and HTTP
session-history endpoints restore the full default operator scope set for
shared-secret bearer auth, even if a caller sends narrower declared scopes.
Identity-bearing modes, such as trusted proxy auth or private-ingress `none`,
can still honor explicit declared scopes. Use separate Gateways for real trust
boundary separation.
## Related
- [Trusted proxy auth](/gateway/trusted-proxy-auth) — how a trusted proxy supplies the operator identity these scopes attach to
- [Gateway protocol](/gateway/protocol) — the methods these scopes authorize
- [Cloud Workers](/gateway/cloud-workers) — worker dispatch, whose environment and session calls are authorized against these scopes