T09 · Insecure Skill Coding Practices
Warning
- Location
- guard.mjs:45
- Finding
- Protected-file overwrite through non-canonical shared-state paths<![CDATA[ ## Vulnerability Details **File Location**: `guard.mjs:45-60, 119-123` **Vulnerability Type**: Path traversal and unsafe path allowlisting **Risk Level**: Medium ### Vulnerable Code ```js const SHARED_STATE_PATHS = [ /\.openclaw\/workspace\//, // OpenClaw agent workspace (live shared state, not code) /workspace\/memory\/\d{4}-\d{2}-\d{2}\.md$/, /\.ldm\/agents\/.*\/memory\/daily\/.*\.md$/, /\.ldm\/memory\/daily\/.*\.md$/, /\.ldm\/memory\/shared-log\.jsonl$/, /\.claude\/projects\/.*\/memory\/.*\.md$/, // harness auto-memory files /\.claude\/memory\/.*\.md$/, // harness global memory files ]; function isSharedState(filePath) { const name = basename(filePath); if (SHARED_STATE_FILES.has(name)) return true; return SHARED_STATE_PATHS.some(p => p.test(filePath)); } ``` ```js if (toolName === 'Write') { // Path-based shared state gets Write access (workspace files, harness memory). // Checked before exact-match so workspace TOOLS.md/MEMORY.md are writable. // Name-based shared state (SHARED_STATE_FILES) still goes through exact-match // to prevent accidental overwrites of SHARED-CONTEXT.md outside known paths. if (SHARED_STATE_PATHS.some(p => p.test(filePath))) { process.exit(0); } ``` ### Technical Analysis The hook applies regular-expression allowlists directly to the untrusted path string supplied in `tool_input.file_path`. It does not first normalize the path with `path.resolve()`, canonicalize existing paths with `realpathSync()`, or verify that the resulting target remains inside an authorized shared-state directory. The shared-state check occurs before exact protected-filename enforcement. Consequently, any raw path containing a permitted substring such as `.openclaw/workspace/` is allowed, even when `..` components cause the filesystem operation to resolve outside that directory. Similar discrepancies can arise from symbolic links placed inside an allowed directory. A path ...[truncated 1395 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Normalize every supplied path with `path.resolve()` before applying protection or allowlist rules. 2. For existing files, use `realpathSync()` to resolve symbolic links and compare the canonical target. 3. Define shared-state locations as canonical directory roots rather than substring regular expressions. 4. Verify containment with a directory-boundary-safe check, for example by using `path.relative()` and rejecting results that equal `..`, begin with `../`, or are absolute. 5. Perform exact protected-filename checks against the canonical target before granting any shared-state exception. 6. For new files, canonicalize the nearest existing parent directory and ensure it remains beneath an authorized root. 7. Add tests covering: - `..` traversal out of every shared-state directory; - repeated and mixed path separators; - symbolic links pointing outside shared-state roots; - protected filenames reached through allowed-path substrings; - platform-specific Windows paths where supported. ]]>
