T09 · Insecure Skill Coding Practices
Warning
- Location
- src/utils/log.js:38
- Finding
- Automatic Storage of Sensitive Results in an Unsafe Shared Temporary Directory<![CDATA[ ## Vulnerability Details **File Location**: `src/utils/log.js:38-48` **Additional Call Sites**: `src/xiaohongshu/search-cli.js:207-209`, `src/xiaohongshu/detail-cli.js:146-148`, `src/xiaohongshu/post-cli.js:165-167`, `src/xiaohongshu/comment-cli.js:185-187` **Vulnerability Type**: Unsafe temporary-file handling and sensitive-data exposure **Risk Level**: Medium ### Vulnerable Code ```js const outputDir = path.join( os.tmpdir(), "xiaohongshu-guaikei", "logs", dayDir, ); const outputFilename = path.join(outputDir, safeFilename); try { await fs.promises.mkdir(outputDir, { recursive: true }); await fs.promises.writeFile(outputFilename, content); ``` All four successful command workflows pass the complete serialized result to this function. For example: ```js await log.taskWrite( `${startTime}_${validator.url2Name(url)}_comment.json`, JSON.stringify(finalOutput, null, 2), ); ``` Equivalent persistence calls are present in the search, detail, and post commands. ### Technical Analysis The Skill automatically archives complete API results beneath the predictable shared path: ```text SYSTEM_TEMP/xiaohongshu-guaikei/logs/YYYY-MM-DD/ ``` The code does not explicitly create the directory with owner-only permissions, does not create files with an explicit `0600` mode, and does not prevent symbolic-link traversal. The resulting access controls therefore depend on the operating system and the invoking process's umask. On a typical multi-user Unix system with a permissive umask, result files may be readable by other local users. The archived JSON includes the submitted keyword or URL and the complete API response. Search and detail responses may contain generated Xiaohongshu URLs carrying `xsec_token` query values. These values are distinct from `GUAIKEI_API_TOKEN`, which is not written to the log, but they may still be sensitive access-bearing link parameters. Because `mkdir` accepts an already existing path and `writeFile` follows symb ...[truncated 2383 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store archived results in a user-private application data directory rather than a globally shared temporary directory. 2. If temporary storage is required, create a randomized private directory: ```js const privateDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), "xiaohongshu-guaikei-"), ); await fs.promises.chmod(privateDir, 0o700); ``` 3. Enforce restrictive permissions on every directory and result file: ```js await fs.promises.mkdir(outputDir, { recursive: true, mode: 0o700, }); await fs.promises.writeFile(outputFilename, content, { mode: 0o600, flag: "wx", }); ``` 4. Use exclusive creation so existing files are not silently overwritten. Where the platform supports it, open files with protections equivalent to `O_CREAT`, `O_EXCL`, and `O_NOFOLLOW` to reject symbolic links. 5. Do not rely only on `lstat` followed by `writeFile`, because that introduces a time-of-check/time-of-use race. Use atomic file-opening flags when available. 6. Make result archival opt-in through an explicit CLI option such as `--save`. Clearly disclose the destination and retention behavior before writing. 7. Redact access-bearing query parameters before persistence, particularly `xsec_token`. Preserve only the minimum fields needed for the documented archival purpose. 8. Add retention controls and automatic cleanup for old result files. 9. Add automated tests that verify: - Directories are owner-accessible only. - Files are created with mode `0600`. - Existing files are not overwritten. - Symbolic-link destinations are rejected. - Sensitive URL parameters are removed from archived output. ]]>
