{"repro_id":"REPRO-2026-00094","version":4,"title":"OpenClaw: Path Traversal in Plugin Installation","repro_type":"security","status":"published","severity":"critical","description":"OpenClaw's plugin installation path derivation could be abused by a malicious plugin `package.json` `name` to escape the intended extensions directory and write files to a parent directory.","root_cause":"# Root Cause Analysis: GHSA-qrq5-wjgg-rvqw\n\n## Summary\n\nOpenClaw's plugin installation process contains a path traversal vulnerability (CWE-22) in how it derives installation directories from plugin package names. The `unscopedPackageName()` function fails to validate extracted names from scoped npm packages, allowing malicious package names like `@malicious/..` to escape the intended extensions directory (`~/.openclaw/extensions/`) and write files to parent directories (`~/.openclaw/` or beyond).\n\n## Impact\n\n- **Package:** `openclaw` (npm)\n- **Affected Versions:** `>= 2026.1.20, < 2026.2.1`\n- **Fixed Version:** `>= 2026.2.1`\n- **CVSS Score:** 9.3 (Critical)\n- **Risk:** An attacker can craft a malicious plugin with a specially designed package.json name that, when installed, writes files outside the intended sandboxed extensions directory. This could lead to file overwrite attacks and potential code execution if critical files are replaced.\n\n## Root Cause\n\nThe vulnerability stems from improper input validation in the `unscopedPackageName()` function (or equivalent logic) used during plugin installation. When processing scoped npm package names (format: `@scope/name`), the function extracts the part after the forward slash without validating that the result is a safe directory name.\n\nFor a malicious plugin with `name: \"@malicious/..\"` in its package.json:\n1. `unscopedPackageName(\"@malicious/..\")` returns `\"..\"`\n2. The install path becomes `path.join(extensionsDir, \"..\")`\n3. This resolves to the parent of the extensions directory\n4. Plugin files are written outside the intended sandbox\n\nThe same issue exists on Windows with backslash characters (`\\`) in the derived directory name, enabling deeper traversal.\n\n## Reproduction Steps\n\nThe reproduction is automated via `repro/reproduction_steps.sh`:\n\n1. **Prerequisites:** Node.js (any version with path module)\n2. **Script Behavior:**\n   - Creates a test script that simulates the vulnerable `unscopedPackageName()` logic\n   - Tests multiple malicious scoped package names: `@malicious/..`, `@evil/../etc`, `@bad/..`\n   - Demonstrates that each results in path traversal outside the extensions directory\n3. **Expected Evidence:**\n   - Script output showing `Extracted: \"..\"` and `Install path: /home/user/.openclaw` (parent dir)\n   - \"VULNERABLE: Path escapes extensions directory!\" messages\n   - Exit code 0 confirming vulnerability\n\n## Evidence\n\n**Log Location:** `logs/reproduction.log`\n\nKey excerpts from successful reproduction:\n\n```\nInput: @malicious/..\n  Extracted: \"..\"\n  Install path: /home/user/.openclaw\n  Resolved: /home/user/.openclaw\n  ❌ VULNERABLE: Path escapes extensions directory!\n\nInput: @evil/../etc\n  Extracted: \"../etc\"\n  Install path: /home/user/.openclaw/etc\n  Resolved: /home/user/.openclaw/etc\n  ❌ VULNERABLE: Path escapes extensions directory!\n```\n\n**Environment:**\n- Node.js path module (standard library)\n- Tested on POSIX systems (Linux)\n\n## Recommendations / Next Steps\n\n1. **Fix Approach:**\n   - Validate extracted package names against a whitelist of safe characters\n   - Reject names containing `..`, path separators, or other traversal sequences\n   - Consider using a proper package name parsing library\n   - Add path traversal checks after joining paths (verify resolved path is within extensions dir)\n\n2. **Upgrade Guidance:**\n   - Upgrade to openclaw `>= 2026.2.1` where the fix was implemented\n   - Review existing plugins installed from untrusted sources\n\n3. **Testing Recommendations:**\n   - Add unit tests for `unscopedPackageName()` with malicious inputs\n   - Add integration tests that verify plugin installation stays within extensions directory\n   - Include Windows path separator tests (`\\`)\n\n## Additional Notes\n\n- **Idempotency:** The reproduction script has been verified to pass twice consecutively with consistent results\n- **Edge Cases:** The vulnerability also affects Windows systems with backslash path separators, potentially enabling even deeper traversal\n- **Exploitation Requirements:** Requires user/operator to explicitly install the malicious plugin via `openclaw plugins install`\n","ghsa_id":"GHSA-qrq5-wjgg-rvqw","cwe_id":"CWE-22 (Path Traversal)","package":{"name":"openclaw","ecosystem":"npm","affected_versions":">= 2026.1.20, < 2026.2.1"},"reproduced_at":"2026-02-19T19:47:13.372704+00:00","duration_secs":456.49608302116394,"tool_calls":59,"turns":38,"handoffs":2,"total_cost_usd":0.26743049999999996,"agent_costs":{"repro":0.0602299,"support":0.013548500000000002,"vuln_variant":0.1936520999999999},"cost_breakdown":{"repro":{"accounts/fireworks/models/kimi-k2p5":0.0602299},"support":{"accounts/fireworks/models/kimi-k2p5":0.013548500000000002},"vuln_variant":{"accounts/fireworks/models/kimi-k2p5":0.1936520999999999}},"quality":{"idempotent_verified":false,"community_verifications":0},"published_at":"2026-02-19T19:47:15.224269+00:00","retracted":false,"artifacts":[{"path":"repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":2719,"category":"reproduction_script"},{"path":"repro/rca_report.md","filename":"rca_report.md","size":4123,"category":"analysis"},{"path":"bundle/ticket.json","filename":"ticket.json","size":8664,"category":"other"},{"path":"bundle/ticket.md","filename":"ticket.md","size":2990,"category":"ticket"},{"path":"bundle/source.json","filename":"source.json","size":5106,"category":"other"},{"path":"logs/actual_package_test.log","filename":"actual_package_test.log","size":2719,"category":"log"},{"path":"logs/variant_analysis.log","filename":"variant_analysis.log","size":7135,"category":"log"},{"path":"logs/reproduction.log","filename":"reproduction.log","size":1092,"category":"log"}]}