T09 · Insecure Skill Coding Practices
Error
- Location
- hooks/remem-flush/handler.js:57
- Finding
- Filesystem Boundary Bypass Through Symbolic Links in remem-flush Hook## Vulnerability Details **File Location**: `hooks/remem-flush/handler.js`, lines 57-59, 89-90, 103-104, and 147 **Vulnerability Type**: Symbolic-link following and arbitrary writable-file clobbering **Risk Level**: High ### Vulnerable Code ```javascript function writeJSON(filePath, data) { FS.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8'); } ``` ```javascript const memoryDir = PATH.join(workspaceDir, 'memory'); const flushStatePath = PATH.join(memoryDir, 'flush-state.json'); ``` ```javascript const groupDir = PATH.join(memoryDir, 'groups', group); const attentionFile = PATH.join(groupDir, 'attention.md'); ``` ```javascript writeJSON(flushStatePath, newState); ``` Group enumeration also follows symbolic links: ```javascript return FS.readdirSync(groupsDir) .filter(f => { const stat = FS.statSync(PATH.join(groupsDir, f)); return stat.isDirectory(); }); ``` ### Technical Analysis The hook derives paths from the configured workspace but does not verify that their canonical targets remain inside the workspace memory directory. `FS.statSync()` follows symbolic links during group discovery, while `FS.writeFileSync()` follows an existing symbolic link at `memory/flush-state.json`. There is no use of `lstatSync()`, `realpathSync()`, canonical path containment checks, or no-follow file-opening semantics. Consequently, possession of write access to the workspace is sufficient to redirect hook filesystem operations outside the intended `memory` hierarchy. A symbolic-link group directory can cause the hook to inspect matching Markdown files outside `memory/groups`. Their canonical paths and modification times can then be recorded in `flush-state.json`. More critically, replacing `memory/flush-state.json` with a symbolic link causes the hook to overwrite the link target with generated JSON. ### Attack Path 1. An attacker obtains the ability to create or replace file ...[truncated 1473 chars]
- Remediation
- ## Remediation Suggestions - Use `FS.lstatSync()` for group entries and reject every symbolic link before treating an entry as a directory. - Resolve both the intended memory root and each accessed path with `FS.realpathSync()`, then verify that the resolved target is equal to or nested beneath the canonical memory root. - Refuse to write when `flush-state.json` already exists as a symbolic link, directory, device, or other non-regular file. - Open the destination with no-follow and exclusive semantics where supported, such as `O_NOFOLLOW`, rather than passing the path directly to `writeFileSync()`. - Write to a newly created temporary regular file in the same validated directory, set restrictive permissions such as `0600`, call `fsync` when durability matters, and atomically rename it over the validated destination. - Revalidate the destination immediately before replacement to reduce time-of-check/time-of-use race conditions. - Run the hook under a dedicated low-privilege account with write access limited to the intended workspace memory directory.
