Skip to content

[Security] Non-owner embedded agent runs expose owner-only gateway and cron tools in openclaw-cn #610

Description

@YLChen-007

Advisory Details

Title: Non-owner embedded agent runs expose owner-only gateway and cron tools in openclaw-cn

Description:

Summary

openclaw-cn contains an improper privilege management flaw in the embedded agent tool exposure path. A chat sender who is allowed to enter the command/agent flow but is not the configured owner can still be presented with owner-only gateway and cron tools. If the model selects one of those tools, the run crosses into Gateway control-plane actions that should remain reserved for the owner/operator boundary, including configuration reads and cron mutation.

Details

I verified this issue against the canonical GitHub repository jiulingyun/openclaw-cn and anchored all occurrence references to commit 1b9f16468d9841871cb15103693c3923424c9842, which is the remote commit for release tag v0.2.0, the highest affected public GitHub release on that repository.

The vulnerable chain is straightforward:

  1. src/auto-reply/command-auth.ts computes a sender state where isAuthorizedSender=true while senderIsOwner=false.
  2. src/agents/pi-embedded-runner/run/attempt.ts forwards senderIsOwner into createOpenClawCodingTools(...).
  3. src/agents/openclaw-tools.ts always adds raw cron and gateway tools to the embedded tool bundle.
  4. src/agents/tool-policy.ts already classifies cron and gateway as owner-only and provides applyOwnerOnlyToolPolicy(...).
  5. src/agents/pi-tools.ts returns the final tool list after profile/group/sandbox filtering, but never applies the owner-only policy before return withAbort.
  6. When one of those tools executes, src/gateway/call.ts opens a Gateway client session with broad default scopes ["operator.admin","operator.approvals","operator.pairing"].
  7. The backend authorization layer in src/gateway/server-methods.ts correctly treats config.* and cron.add as admin-only methods, but the embedded tool path bypasses the intended boundary by self-connecting with those broader scopes.

The key logic error is not that Gateway method authorization is absent. It is present and working. The defect is that a non-owner sender can still be offered tools that should never have been visible to that sender in the first place.

Relevant snippets:

const senderIsOwner = Boolean(matchedSender);
const isAuthorizedSender = commandAuthorized && isOwnerForCommands;

return {
  senderIsOwner,
  isAuthorizedSender,
  ...
};
const toolsRaw = createOpenClawCodingTools({
  ...
  senderIsOwner: params.senderIsOwner,
  ...
});
export function applyOwnerOnlyToolPolicy(tools: AnyAgentTool[], senderIsOwner: boolean) {
  ...
  return withGuard.filter((tool) => !isOwnerOnlyTool(tool));
}
const normalized = subagentFiltered.map(normalizeToolParameters);
...
return withAbort;

I confirmed the issue using the existing integration-test harness in the report directory. The harness starts a real loopback Gateway, demonstrates that a plain operator.write client is blocked from cron.add, then constructs a non-owner embedded agent tool surface from the live code and successfully executes gateway config.get and cron.add. The control run applies applyOwnerOnlyToolPolicy(rawTools, false) and shows that both tools disappear and the cron mutation no longer occurs.

PoC

Prerequisites

  • A checkout of https://github.com/jiulingyun/openclaw-cn.
  • pnpm, tsx, and python3 installed locally.
  • No external chat account, reverse proxy, or standalone Gateway instance is required.
  • The three PoC files must remain in the same directory, because the Python wrappers invoke driver.ts by relative path.
  • The reproduced privilege split assumes a deployment where:
    • the sender is allowed into the broader command/agent surface,
    • the sender is not in commands.ownerAllowFrom,
    • owner-only tools are still expected to be hidden from that sender.

Reproduction Steps

  1. Download the TypeScript harness from: driver.ts
  2. Download the verification harness from: verification_test.py
  3. Download the control harness from: control-owner-only-policy.py
  4. Check out the highest affected public release:
    git checkout v0.2.0
  5. Run the verification PoC:
    python3 llm-enhance/cve-finding/similar/improper-privilege-management/Advisory-GHSA-2hm8-rqrm-xfjq-dataflow-non-owner-agent-tool-scope-bypass-exp/verification_test.py
  6. Confirm the experiment output shows:
    • auth.isAuthorizedSender: True
    • auth.senderIsOwner: False
    • raw tool list includes gateway: True
    • raw tool list includes cron: True
    • gateway tool config.get succeeded: True
    • cron tool cron.add succeeded: True
    • canary cron job listed after non-owner tool execution: True
  7. Run the control PoC:
    python3 llm-enhance/cve-finding/similar/improper-privilege-management/Advisory-GHSA-2hm8-rqrm-xfjq-dataflow-non-owner-agent-tool-scope-bypass-exp/control-owner-only-policy.py
  8. Confirm the control output shows:
    • effective tool list includes gateway: False
    • effective tool list includes cron: False
    • canary cron job listed after control execution: False

Log of Evidence

Verification run:

[Verification Mode] Integration-Test
Reachability path: non-owner command-authorized chat sender -> resolveCommandAuthorization(senderIsOwner=false, isAuthorizedSender=true) -> embedded agent createOpenClawCodingTools(...) -> gateway/cron tools visible -> tool.execute() -> real localhost gateway -> admin-only methods
gateway url: ws://127.0.0.1:40983
loaded config port: 40983
auth.isAuthorizedSender: True
auth.senderIsOwner: False
control blocked by operator.admin requirement: True
control error: missing scope: operator.admin
raw tool list includes gateway: True
raw tool list includes cron: True
effective tool list includes gateway: True
effective tool list includes cron: True
gateway tool config.get succeeded: True
cron tool cron.add succeeded: True
admin cron.list succeeded: True
canary cron job listed after non-owner tool execution: True
canary job name: ghsa-2hm8-dataflow-1783279193794
[DEFECT CONFIRMED WITH LIMITATIONS]

Control run:

[Control Mode] Integration-Test
Control action: applyOwnerOnlyToolPolicy(rawTools, senderIsOwner=false) before exposing the embedded tool surface.
raw tool list includes gateway: True
raw tool list includes cron: True
effective tool list includes gateway: False
effective tool list includes cron: False
gateway execution blocked: True
cron execution blocked: True
canary cron job listed after control execution: False
[CONTROL PROTECTED]

Impact

This is an improper privilege management issue at the chat-user to embedded-agent control-plane boundary. It affects deployments that intentionally allow a broader set of senders to interact with the agent than the set of senders who should be treated as owners.

A successful trigger allows a non-owner sender context to reach owner-only operational tools. Depending on which tool the model selects, the affected assets include:

  • Gateway configuration confidentiality through config.get
  • Gateway configuration integrity through config.patch / config.apply
  • Scheduled task integrity through cron.add, cron.update, cron.remove, and cron.run
  • Broader operator workflows reachable through the same self-scoped Gateway bridge

This is not an unauthenticated public network issue. The attacker must already be accepted into the command/agent path. The security failure is that the owner-only boundary inside that accepted path is not enforced.

Affected products

  • Ecosystem: npm
  • Package name: openclaw-cn
  • Affected versions: >= 0.1.5, <= 0.2.0
  • Patched versions:

Severity

  • Severity: Medium
  • Vector string: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:L

Weaknesses

  • CWE: CWE-269: Improper Privilege Management

Occurrences

Permalink Description
https://github.com/jiulingyun/openclaw-cn/blob/1b9f16468d9841871cb15103693c3923424c9842/src/auto-reply/command-auth.ts#L205-L230 resolveCommandAuthorization() computes both senderIsOwner and isAuthorizedSender, creating the non-owner but command-authorized sender state that should not receive owner-only tools.
https://github.com/jiulingyun/openclaw-cn/blob/1b9f16468d9841871cb15103693c3923424c9842/src/agents/pi-embedded-runner/run/attempt.ts#L275-L305 The embedded runner forwards senderIsOwner into createOpenClawCodingTools(...), so the owner/non-owner state reaches the raw tool builder.
https://github.com/jiulingyun/openclaw-cn/blob/1b9f16468d9841871cb15103693c3923424c9842/src/agents/openclaw-tools.ts#L102-L123 The embedded tool bundle always includes raw createCronTool(...) and createGatewayTool(...), which are the owner-sensitive tools exposed on the vulnerable path.
https://github.com/jiulingyun/openclaw-cn/blob/1b9f16468d9841871cb15103693c3923424c9842/src/agents/tool-policy.ts#L61-L106 The repository already classifies cron and gateway as owner-only and provides applyOwnerOnlyToolPolicy(...), which would strip them for non-owners if the embedded tool builder actually used it.
https://github.com/jiulingyun/openclaw-cn/blob/1b9f16468d9841871cb15103693c3923424c9842/src/agents/pi-tools.ts#L114-L164 createOpenClawCodingTools() does not declare or consume a senderIsOwner option in its public contract, despite downstream call sites passing that field.
https://github.com/jiulingyun/openclaw-cn/blob/1b9f16468d9841871cb15103693c3923424c9842/src/agents/pi-tools.ts#L318-L367 The tool builder imports the full OpenClaw raw tool surface, including gateway and cron, into the embedded coding tool list.
https://github.com/jiulingyun/openclaw-cn/blob/1b9f16468d9841871cb15103693c3923424c9842/src/agents/pi-tools.ts#L412-L455 After policy filtering, the final tool list is normalized and returned directly. No call to applyOwnerOnlyToolPolicy(...) occurs before return withAbort, so non-owner runs keep the owner-only tools.
https://github.com/jiulingyun/openclaw-cn/blob/1b9f16468d9841871cb15103693c3923424c9842/src/gateway/call.ts#L248-L255 callGateway() self-connects as role: "operator" with operator.admin, operator.approvals, and operator.pairing scopes by default, magnifying the impact once the wrong tools are exposed.
https://github.com/jiulingyun/openclaw-cn/blob/1b9f16468d9841871cb15103693c3923424c9842/src/gateway/server-methods.ts#L105-L135 The backend authorization layer correctly treats config.* and cron.add as admin-only methods, confirming that the bug is the embedded tool exposure path rather than missing method authorization.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions