Skip to content

fix(gateway): resolve plugin method scope from channel-pinned registry in gateway mode#92358

Closed
jiewent1-cmyk wants to merge 1 commit into
openclaw:mainfrom
jiewent1-cmyk:fix/gateway-plugin-method-scope-channel-registry
Closed

fix(gateway): resolve plugin method scope from channel-pinned registry in gateway mode#92358
jiewent1-cmyk wants to merge 1 commit into
openclaw:mainfrom
jiewent1-cmyk:fix/gateway-plugin-method-scope-channel-registry

Conversation

@jiewent1-cmyk

Copy link
Copy Markdown

fix(gateway): resolve plugin method scope from channel-pinned registry

Suggested PR title: fix(gateway): resolve plugin method scope from channel-pinned registry in gateway mode
Branch: fix/gateway-plugin-method-scope-channel-registry (one commit, off main)

🤖 AI-assisted (Claude Code). Author understands the change; human-run evidence below.

Problem

In gateway mode the active plugin registry is installed on the channel surface
via pinActivePluginChannelRegistry(); the flat activeRegistry field is never
set in that code path. But src/gateway/method-scopes.ts reads
getPluginRegistryState()?.activeRegistry directly in two places:

  • resolveScopedMethod()gatewayMethodDescriptors lookup
  • resolveSessionActionRegisteredScopes()sessionActions lookup

So in gateway mode every plugin-registered gateway method / session action
fails to resolve its declared scope
, falls through to the ADMIN_SCOPE
default, and is then rejected for any operator.write client — e.g. a bridge
plugin calling its own gateway method registered with operator.write.

method-scopes.ts is the only place in the channel/gateway code still reading
activeRegistry directly; the rest of the codebase already uses
getActivePluginChannelRegistryFromState() (which returns
channel.registry ?? activeRegistry). The existing tests only exercised the
setActivePluginRegistry/activeRegistry path, never the channel-pinned
(gateway) path — which is why this shipped.

Fix

Use getActivePluginChannelRegistryFromState() for both lookups, so scope
resolution works in both gateway and standalone/test modes. One-line import
swap + the two call sites.

Tests

Adds regression tests to method-scopes.test.ts exercising the channel-pinned
path (pinActivePluginChannelRegistry) for both a gateway method and a session
action — the path that previously had no coverage.

Real behavior proof

Built main (Node 24.16, pnpm build) and ran the new tests against the real
resolver + the real pinActivePluginChannelRegistry (not a mock):

Without the fix (revert method-scopes.ts, keep the test) — both fail:

× resolves a plugin gateway method scope from the channel-pinned registry
    AssertionError: expected [] to deeply equal [ 'operator.write' ]
× resolves a plugin session action scope from the channel-pinned registry
    AssertionError: expected [ 'operator.admin', …(5) ] to deeply equal [ 'operator.read' ]

(i.e. the plugin method resolves to "unknown" → admin-only, and the session
action falls back to the broad CLI default — exactly the production symptom.)

With the fix — full suite green:

✓ src/gateway/method-scopes.test.ts (77 tests)   # ×3 project configs = 231 passed

Production: this exact fix is deployed on our own OpenClaw gateway (source
install, v2026.5.28) running an MCP bridge plugin that registers
operator.write gateway methods; with the patch those calls succeed, without it
they were rejected as admin-only.

Not run: a full network-client demo with a dedicated operator.write-scoped
token (the gateway has no CLI flag to downscope a local caller; the local
operator is always admin, which masks the bug). The unit regression drives the
real resolver through the real channel-pin path, and the fix is in production.

Gates

Against main (Node 24.16): pnpm build ✅, pnpm check (whole-repo
lint+typecheck) ✅, full pnpm test ✅ (incl. method-scopes.test.ts 77/77).

Notes

  • No CHANGELOG.md edit (per CONTRIBUTING).
  • "Allow edits by maintainers" enabled.

In gateway mode the plugin registry is installed on the channel surface via
pinActivePluginChannelRegistry(); the flat `activeRegistry` field is never set
in that code path. resolveScopedMethod() and resolveSessionActionRegisteredScopes()
read getPluginRegistryState()?.activeRegistry directly, so every plugin-registered
gateway method / session action fails to resolve its scope, falls through to the
ADMIN_SCOPE default, and rejects operator.write clients (e.g. a bridge plugin
calling its own registered gateway method).

Use getActivePluginChannelRegistryFromState() (channel.registry ?? activeRegistry)
— the accessor already used throughout the channel/gateway code — so scope
resolution works in both gateway and standalone/test modes.

Add regression tests exercising the channel-pinned path; previously only the
activeRegistry/standalone path was covered, which is why this shipped.
@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 12, 2026
@clawsweeper

clawsweeper Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I did a careful shell check against current main, and this is already implemented.

Close: current main already solves the central plugin gateway method authorization problem with the merged attached-registry implementation, which is a better owner-boundary fix than landing this older resolver-level branch. The remaining channel-only session-action test in this PR is not tied to a proven current gateway production path.

Root-cause cluster
Relationship: superseded
Canonical: #94343
Summary: The current PR is superseded by the merged attached-registry gateway authorization fix; one related open PR covers an adjacent stale dispatch-registry problem, not this resolver-level branch.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review details

Best possible solution:

Keep the current attached-registry authorization implementation from #94343; open a fresh focused issue if channel-only session-action scope resolution is observed in production.

Do we have a high-confidence way to reproduce the issue?

Yes at source level for the original active-registry-only plugin method scope failure, which was fixed on current main by the attached-registry authorization path. The branch's remaining channel-only session-action case is synthetic from source review, not a proven current production repro.

Is this the best way to solve the issue?

No for this branch as the landing path: current main's attached-registry authorization is the narrower maintainable fix because dispatch and scope metadata stay paired. A separate session-action issue should be proven before changing the global resolver.

Security review:

Security review cleared: No supply-chain issue was found; the diff is auth-sensitive, but the remaining concern is merge risk rather than a concrete security defect in the obsolete branch.

AGENTS.md: found and applied where relevant.

What I checked:

  • Current main authorization source: Current main authorizes operator requests from methodRegistry.getScope(method) before falling back to the global method-scope resolver, so plugin method scope metadata is paired with the registry used for dispatch. (src/gateway/server-methods.ts:259, fb022a2b07cb)
  • Current main attached registry construction: The gateway builds the attached method registry from createPluginGatewayMethodDescriptors(nextPluginRegistry), so plugin handler and scope descriptors come from the same dispatch-time registry. (src/gateway/server.impl.ts:1227, fb022a2b07cb)
  • Merged fix provenance: The merged replacement is fix(gateway): authorize plugin methods from attached registry, which landed as commit 27f702d. (27f702d68fab)
  • Related PR provenance: Live GitHub data shows fix(gateway): authorize plugin methods from attached registry #94343 is merged, closed the Workboard scope bug, and records the same merge commit as the current-main implementation path. (27f702d68fab)
  • Current-main-only release check: GitHub compare reports current main is ahead of 27f702d with that commit as merge base, while latest release v2026.6.8 was published before the fix; this is present on current main but not proven shipped in a release. (27f702d68fab)
  • Superseded branch shape: This PR changes the global resolver to prefer the channel-pinned registry, while the merged fix moved the central gateway method authorization decision to the exact attached dispatch registry instead. (src/gateway/method-scopes.ts:54, 5317a1555744)

Likely related people:

  • steipete: Merged the current-main attached-registry fix and appears in recent gateway method descriptor history around the same authorization surface. (role: recent merger and gateway descriptor contributor; confidence: high; commits: 27f702d68fab, 622728757fe0, db3c4ba8d33d; files: src/gateway/server-methods.ts, src/gateway/method-scopes.ts, src/gateway/methods/registry.ts)
  • wangmiao0668000666: Authored the merged replacement PR for the same plugin gateway scope failure family and documented the root-cause direction on the linked Workboard issue. (role: merged fix author and issue follow-up owner; confidence: high; commits: 27f702d68fab; files: src/gateway/server-methods.ts, src/gateway/server-methods.authorization.test.ts)
  • vincentkoc: Recent history shows multiple plugin runtime and registry seam changes adjacent to active, pinned, and gateway registry behavior. (role: adjacent plugin runtime seam contributor; confidence: medium; commits: 543c14a4edd1, 81535d394d34, e2292d18e215; files: src/plugins/runtime.ts, src/plugins/runtime-state.ts, src/gateway/method-scopes.ts)

Codex review notes: model internal, reasoning high; reviewed against fb022a2b07cb; fix evidence: commit 27f702d68fab, main fix timestamp 2026-06-19T11:56:24+01:00.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. and removed merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 12, 2026
@clawsweeper

clawsweeper Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant