Skip to content

Commit 2576073

Browse files
1 parent 877fa1e commit 2576073

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-94pw-c6m8-p9p9",
4+
"modified": "2026-03-30T18:52:38Z",
5+
"published": "2026-03-30T18:52:38Z",
6+
"aliases": [],
7+
"summary": "OpenClaw: Gateway operator.write Can Reach Admin-Class Channel Allowlist Persistence via chat.send",
8+
"details": "> Fixed in OpenClaw 2026.3.24, the current shipping release.\n\n## Summary\n\nThe shared `/allowlist` command persists channel authorization config through `writeConfigFile(...)` but does not re-validate gateway client scopes for internal gateway callers. Because `chat.send` is intentionally reachable to `operator.write` callers and still creates a generic command-authorized internal context, an authenticated write-scoped gateway client can indirectly mutate channel `allowFrom` and `groupAllowFrom` policy that direct `config.patch` correctly reserves to `operator.admin`.\n\nThis is not just a generic code smell. The current code already shows the intended boundary by adding sink-side internal admin checks to shared `/config` and `/plugins` writes, but `/allowlist` was left behind.\n\n## Details\n\nThe gateway's documented scope split is clear:\n\n- `chat.send` is a write-scoped action.\n- direct config mutation is an admin-scoped action.\n\nThe vulnerable path is:\n\n1. A gateway client authenticates with `operator.write`.\n2. The client calls `chat.send`, which is intentionally allowed for that scope.\n3. `chat.send` builds an internal message context with `CommandAuthorized: true` and carries `GatewayClientScopes` into the reply pipeline.\n4. `resolveCommandAuthorization(...)` converts that internal message into `isAuthorizedSender=true` in the common case where no stricter `commands.allowFrom` override is configured.\n5. `/allowlist add|remove` accepts that generic command authorization and proceeds into its config-backed edit path.\n6. The handler clones the parsed config, calls `plugin.allowlist.applyConfigEdit(...)`, validates the result, and persists it with `writeConfigFile(validated.config)`.\n7. No sink-side check requires `operator.admin` before the persistent write occurs.\n\nThat creates a direct control-plane mismatch:\n\n- `config.patch` rejects the same caller with `missing scope: operator.admin`.\n- `/allowlist add dm ...` or `/allowlist add group ...` reached through `chat.send` can still rewrite channel authorization state.\n\n## Impact\n\n- A gateway client intentionally limited to `operator.write` can persist first-party channel authorization policy.\n- The caller can widen DM or group allowlists for channels using the shared `/allowlist` plumbing.\n- This weakens the repo's documented control-plane privilege split between ordinary write actions and admin-only persistent authorization mutation.\n\n## Remediation\n\n### 1) Add the Missing Sink-Side Internal Admin Check to `/allowlist`\n\nMirror the existing hardened pattern from `/config` and `/plugins`.\n\nBefore any config-backed `/allowlist add|remove` write, require:\n\n- `operator.admin` for internal gateway channels\n\nThis should happen before `plugin.allowlist.applyConfigEdit(...)` and before `writeConfigFile(...)`.\n\n### 2) Keep Pairing-Store and Config-Write Policy Checks, but Do Not Treat Them as Scope Enforcement\n\n`configWrites` policy and pairing-store behavior are useful secondary controls, but they do not replace the missing privilege check between `operator.write` and `operator.admin`.",
9+
"severity": [],
10+
"affected": [
11+
{
12+
"package": {
13+
"ecosystem": "npm",
14+
"name": "openclaw"
15+
},
16+
"ranges": [
17+
{
18+
"type": "ECOSYSTEM",
19+
"events": [
20+
{
21+
"introduced": "0"
22+
},
23+
{
24+
"fixed": "2026.3.24"
25+
}
26+
]
27+
}
28+
],
29+
"database_specific": {
30+
"last_known_affected_version_range": "<= 2026.3.23"
31+
}
32+
}
33+
],
34+
"references": [
35+
{
36+
"type": "WEB",
37+
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-94pw-c6m8-p9p9"
38+
},
39+
{
40+
"type": "PACKAGE",
41+
"url": "https://github.com/openclaw/openclaw"
42+
}
43+
],
44+
"database_specific": {
45+
"cwe_ids": [
46+
"CWE-269"
47+
],
48+
"severity": "HIGH",
49+
"github_reviewed": true,
50+
"github_reviewed_at": "2026-03-30T18:52:38Z",
51+
"nvd_published_at": null
52+
}
53+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-m3mh-3mpg-37hw",
4+
"modified": "2026-03-30T18:52:09Z",
5+
"published": "2026-03-30T18:52:09Z",
6+
"aliases": [],
7+
"summary": "OpenClaw has an Arbitrary Malicious Code Execution Vulnerability",
8+
"details": "> Fixed in OpenClaw 2026.3.24, the current shipping release.\n\n### Summary\nDuring the installation phase of OpenClaw local plugins/hooks, the Git executable can be hijacked by a project-level .npmrc file, leading to arbitrary code execution during installation.\n\n### Details\nPlease note that the source code locations mentioned below are based on version openclaw-2026.3.13-1, but the issue has been confirmed to still exist in the current latest version, 2026.3.23.\n\nWhen installing a local plugin directory, local plugin archive, local hook pack directory, or local hook pack archive, OpenClaw first copies the source directory to a temporary `stageDir`, then executes the following in that directory:\n\n```\nnpm install --omit=dev --silent --ignore-scripts\n```\n\nSee `src/infra/install-package-dir.ts:176-199`.\n\nSince this process does not strip the project root `.npmrc`, and npm reads the project-level `.npmrc` during local project installation, an attacker could use a `.npmrc` file in a malicious plugin or hook directory to override npm’s `git` executable path. By leveraging a Git dependency, the attacker could trigger npm to call this malicious program, thereby executing arbitrary local code during the installation phase.\n\n**Affected Paths**\n\n- Plugin CLI entry point: `src/cli/plugins-cli.ts:199-255`\n- Hook CLI entry point: `src/cli/hooks-cli.ts:573-676`\n- Plugin local directory / archive installation: `src/plugins/install.ts:379-405`, `src/plugins/install.ts:541-565`\n- Hook local directory / archive installation: `src/hooks/install.ts:380-403`, `src/hooks/install.ts:443-470`\n- Actual execution of `npm install --ignore-scripts`: `src/infra/install-package-dir.ts:176-199`\n\n**Vulnerability Trigger Flow**\n\n1. The user executes one of the following commands:\n\n - `openclaw plugins install <path-or-spec>`\n - `openclaw hooks install <path-or-spec>`\n2. If the argument is a local directory or local archive, OpenClaw navigates to the local installation path.\n3. OpenClaw copies the source directory to a temporary `stageDir`. See `src/infra/install-package-dir.ts:176-177`.\n4. If `dependencies` are present in `package.json`, OpenClaw executes the following in `stageDir`:\n\n```\nnpm install --omit=dev --silent --ignore-scripts\n```\n\nSee `src/infra/install-package-dir.ts:188-199`.\n\n5. npm reads the project-level `.npmrc` file in this directory. Official documentation: [`.npmrc`](https://docs.npmjs.com/cli/v11/configuring-npm/npmrc/)\n6. If `.npmrc` is set to `git=<path to malicious program>` and there is a git dependency in the dependency tree, npm will invoke that `git` program when resolving the dependency. Official documentation: [`npm config git`](https://docs.npmjs.com/cli/v11/using-npm/config/) Git dependency documentation: [`package.json`](https://docs.npmjs.com/cli/v11/configuring-npm/package-json/)\n7. Consequently, an attacker can execute arbitrary local programs during the plugin/hook installation phase without waiting for the plugin or hook to be loaded later.\n\n**Triggering Commands**\n\n- Plugin installation command:\n\n```\nopenclaw plugins install <path-or-spec>\n```\n\n- Hook installation command:\n\n```\nopenclaw hooks install <path-or-spec>\n```\n\nWhen `<path-or-spec>` is a local directory or local archive, it will be resolved to the path used by the `npm install --omit=dev --silent --ignore-scripts` command mentioned above.\n\n### PoC\n\n\n\nCurrently, `testpoc/` is a minimal PoC directory used to verify that “when installing local packages, OpenClaw enters the `npm install --ignore-scripts` path.” It is divided into two core sections:\n\ntestpoc/pkg/\nPurpose: Simulates the local package directory installed by `openclaw plugins install ...` or `openclaw hooks install ...`\ntestpoc/repo/\nPurpose: Simulates a Git dependency repository within the npm dependency tree\nDirectory Structure\n\ntestpoc/\n├─ pkg/\n│ ├─ .npmrc\n│ ├─ package.json\n│ └─ sample-hook/\n│ ├─ HOOK.md\n│ └─ handler.js\n└─ repo/\n ├─ package.json\n └─ .git/...\nFunction of Each Component\n\ntestpoc/pkg/.npmrc\n\nCurrent content:\ngit=calc.exe\nFunction: Overrides npm’s Git executable configuration.\nMeaning: When npm encounters a git dependency during installation, it will not call the system git but will attempt to call the program specified here.\nThis is the core trigger point of this PoC. See testpoc/pkg/.npmrc:1\ntestpoc/pkg/package.json\n\nCurrently, this is a “mixed-use” manifest that includes both plugin and hook fields:\n{\n “name”: “probe-host”,\n “version”: “1.0.0”,\n “private”: true,\n “openclaw”: {\n “extensions”: [“./dist/index.js”],\n “hooks”: [“./sample-hook”]\n },\n “dependencies”: {\n “probe-git-dep”: “git+file:///D:/AI Agent Source/OpenClaw/openclaw-2026.3.13-1/.testpoc/repo”\n }\n}\nIts functionality is divided into three layers:\nopenclaw.extensions: Allows it to be validated as a plugin package\nopenclaw.hooks: Enables it to be validated as a hook package\nThe Git URL in dependencies: Forces npm to enter the Git dependency resolution path during installation\nSee testpoc/pkg/package.json:1\ntestpoc/pkg/sample-hook/HOOK.md\n\nPurpose: To meet the minimum metadata requirements for a hook package.\nThis is the key file that allows `openclaw hooks install pkg` to pass the pre-check. See testpoc/pkg/sample-hook/HOOK.md:1\ntestpoc/pkg/sample-hook/handler.js\n\nCurrent content:\nexport default async function handler() {\n return { ok: true };\n}\nPurpose: Meets the requirement that the hook directory must contain a handler entry file.\nIt is not a usage point in itself; its sole purpose is to allow OpenClaw to proceed to the dependency installation phase. See testpoc/pkg/sample-hook/handler.js:1\ntestpoc/repo/package.json\n\nCurrent content:\n{“name”:“probe-git-dep”,‘version’:“1.0.0”}\nPurpose: Serves as the minimum repository content corresponding to a Git dependency.\nThe focus is not on the repository code itself, but on the fact that “it is a Git repository,” allowing npm to perform Git-related operations on it. See testpoc/repo/package.json:1\ntestpoc/repo/.git/\n\nPurpose: Makes testpoc/repo/ a real Git repository rather than a regular directory.\nWhen npm resolves git+file://... When installing dependencies, this is treated as the Git source.\nHow the current PoC works\n\nIf installing via hooks:\n\nopenclaw hooks install testpoc/pkg\nThe trigger chain is:\n\nOpenClaw identifies testpoc/pkg as the local hook package path\nThrough pre-validation in openclaw.hooks, HOOK.md, and handler.js\nProceeds to src/infra/install-package-dir.ts:188-199\nExecutes:\nnpm install --omit=dev --silent --ignore-scripts\nnpm reads testpoc/pkg/.npmrc\nnpm processes the git dependency in package.json\nnpm attempts to call the git=calc.exe specified in .npmrc\n\n### Impact\nIt is best described as an installation-time local command execution / unsafe package-install configuration issue.\n\nMore precisely:\n\nOpenClaw installs local plugin and hook packs by running npm install --omit=dev --silent --ignore-scripts inside the staged package directory, see src/infra/install-package-dir.ts:188-199.\nIf that local package directory contains an attacker-controlled .npmrc, npm will still read it.\nIf .npmrc overrides npm’s git executable and the package has a git dependency, npm can invoke the attacker-chosen program during install.\n\nWho is impacted\n\nUsers who run:\n\nopenclaw plugins install <local path/archive>\nopenclaw hooks install <local path/archive>\n\nAnd who install a malicious or untrusted local package that includes:\n\na controlled .npmrc\na git dependency\na runnable attacker-controlled git target on that platform\n\nThis should be treated as a security issue, not just “malicious plugin behavior,” because the code execution happens during OpenClaw’s install workflow, before the plugin or hook is ever loaded as trusted runtime code.\n\nThe important distinction is:\n\nA normal “trusted plugin” case is: the operator installs a plugin, enables it, and later that plugin runs with plugin privileges.\nThis issue is different: OpenClaw’s installer executes npm install --omit=dev --silent --ignore-scripts inside an attacker-controlled package directory, and npm still honors attacker-controlled project config from .npmrc.\n\nThat means an untrusted local plugin or hook package can influence the package manager itself and reach arbitrary program execution at install time, via npm’s git setting and a git dependency, even though --ignore-scripts is present.\n\nWhy this matters from a security perspective:\n\nIt is install-time execution, not post-install trusted execution.\n\nThe execution is triggered by OpenClaw’s installer in src/infra/install-package-dir.ts:188-199.\n\nThis occurs before the package is accepted as a trusted loaded plugin/hook in the usual sense.\n\nIt defeats an expected safety boundary.\n\nThe code explicitly uses --ignore-scripts, which strongly suggests an intent to make installation safer.\n\nBut the installer still allows attacker-controlled package-manager configuration from .npmrc to affect execution.\n\nSo the current mitigation is incomplete in a security-relevant way.\n\nThe dangerous input is part of a supported user flow.\n\nOpenClaw explicitly supports installing plugins and hook packs from local directories and archives:\n\nsrc/cli/plugins-cli.ts:199-255\nsrc/cli/hooks-cli.ts:573-676\n\nThat makes “download a package/archive, then install it” a realistic operator action, not an artificial lab setup.\n\nThe issue is broader than plugin trust.\n\nThe problem is not “plugins can do bad things once trusted.”\n\nThe problem is “the installer consumes attacker-controlled package-manager config before trust is established.”\n\nThat is much closer to an unsafe install / supply-chain execution flaw than to ordinary trusted-plugin behavior.\n\nHooks are affected too.\n\nThe same installer path is used for hook packs, not only plugins.\n\nSo this is a shared install-surface issue, not an isolated plugin-runtime concern.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "openclaw"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "2026.3.24"
30+
}
31+
]
32+
}
33+
],
34+
"database_specific": {
35+
"last_known_affected_version_range": "<= 2025.3.23"
36+
}
37+
}
38+
],
39+
"references": [
40+
{
41+
"type": "WEB",
42+
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-m3mh-3mpg-37hw"
43+
},
44+
{
45+
"type": "PACKAGE",
46+
"url": "https://github.com/openclaw/openclaw"
47+
}
48+
],
49+
"database_specific": {
50+
"cwe_ids": [
51+
"CWE-426"
52+
],
53+
"severity": "HIGH",
54+
"github_reviewed": true,
55+
"github_reviewed_at": "2026-03-30T18:52:09Z",
56+
"nvd_published_at": null
57+
}
58+
}

0 commit comments

Comments
 (0)