T05 · Unauthorized Access and Privilege Escalation
Warning
- Location
- hooks/openclaw/handler.js:213
- Finding
- Unrestricted Transcript Path Can Read Files Outside the Workspace<![CDATA[ ## Vulnerability Details **File Location**: `hooks/openclaw/handler.js:213-229, 304` **Vulnerability Type**: Improper path validation and filesystem scope enforcement **Risk Level**: Medium ### Vulnerable Code ```js function resolveSessionFilePath(context, workspaceDir) { const sessionEntry = isObject(context.previousSessionEntry) ? context.previousSessionEntry : isObject(context.sessionEntry) ? context.sessionEntry : {}; if (typeof sessionEntry.sessionFile === 'string' && sessionEntry.sessionFile.trim()) { return sessionEntry.sessionFile; } const sessionId = typeof sessionEntry.sessionId === 'string' ? sessionEntry.sessionId.trim() : ''; if (sessionId && workspaceDir) { return path.join(workspaceDir, 'sessions', `${sessionId}.jsonl`); } return undefined; } ``` The resulting path is passed directly to the transcript scanner: ```js const sessionFilePath = resolveSessionFilePath(context, workspaceDir); if (!sessionFilePath) { return; } const excerpts = await scanTranscriptForErrors(sessionFilePath); ``` The scanner opens that path without validating its location: ```js async function scanTranscriptForErrors(sessionFilePath) { let raw; try { raw = await fs.readFile(sessionFilePath, 'utf-8'); } catch { return []; } ``` The equivalent TypeScript implementation appears at `hooks/openclaw/handler.ts:219-239, 320`. ### Technical Analysis The hook treats `context.previousSessionEntry.sessionFile` or `context.sessionEntry.sessionFile` as a trusted filesystem path. It does not canonicalize the path or verify that it is contained beneath `<workspace>/sessions`. The fallback path is also unsafe because `sessionId` is not restricted to a safe identifier format. Values containing `..` or path separators can cause `path.join()` to resolve outside the intended sessions directory. The vulnerability requires an attacker or compromised component to influence OpenClaw hook event metadata. Under ...[truncated 1591 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Resolve the canonical sessions directory and candidate transcript path: ```js const sessionsRoot = await fs.realpath(path.join(workspaceDir, 'sessions')); const candidate = await fs.realpath(sessionFilePath); const relative = path.relative(sessionsRoot, candidate); if ( relative === '' || relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative) ) { return undefined; } ``` 2. Validate `sessionId` against a strict allowlist before constructing a path, for example: ```js if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) { return undefined; } ``` 3. Do not accept arbitrary absolute `sessionFile` values. If OpenClaw requires absolute paths, verify that their canonical form remains beneath the canonical sessions directory. 4. Consider rejecting symbolic links or opening files using platform-specific no-follow protections where available. 5. Add tests covering: - Absolute paths outside the workspace. - `../` traversal through `sessionId`. - Encoded or platform-specific separators. - Symlinks from the sessions directory to external files. - Valid transcript paths that remain functional after hardening. ]]>
