T09 · Insecure Skill Coding Practices
- Location
hooks/fluid-memory-sync/handler.js:40- Finding
Unbounded Plaintext Retention of Conversation Content
- Content
View full analysis
Vulnerability Details
File Location:
hooks/fluid-memory-sync/handler.js:40-66;fluid_skill.py:31-32;fluid_skill.py:234-245
Vulnerability Type: Plaintext sensitive-data storage and unbounded transcript retention
Risk Level: MediumVulnerable Code
javascript export default async function handler(event) { // Only handle message:sent events if (event.type !== "message" || event.action !== "sent") { return; } // Skip if no content if (!event.content) { return; } try { // 记录对话到临时文件 const timestamp = new Date().toISOString(); const logEntry = `[${timestamp}] 用户说: ${event.content}\n`; // 确保目录存在 const dir = path.dirname(CONVERSATION_LOG); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // 追加到日志文件 fs.appendFileSync(CONVERSATION_LOG, logEntry); console.log("[fluid-memory-sync] 对话已记录,等待原生 flush 触发..."); } catch (error) { console.error("[fluid-memory-sync] Failed to log conversation:", error.message); } }The summary buffer is also stored in plaintext:
python # 增量总结缓冲区文件 BUFFER_FILE = os.path.join(WORKSPACE_ROOT, r"database\summary_buffer.json")python def _save_buffer(self, summary, round_count): """保存总结缓冲区""" os.makedirs(os.path.dirname(BUFFER_FILE), exist_ok=True) with open(BUFFER_FILE, 'w', encoding='utf-8') as f: json.dump({'summary': summary, 'round_count': round_count}, f, ensure_ascii=False)Technical Analysis
Every qualifying
message:sentevent is appended verbatim toconversation_log.txt. The hook does not redact credentials or personal information, limit the transcript size, rotate the file, encrypt its contents, define a retention period, or explicitly create it with owner-only permissions.Although the project documentation describes this transcript as temporary and ...[truncated 1888 chars]
- Remediation
View remediation
Remediation Suggestions
- Disable raw transcript collection by default and require explicit user consent before retaining conversation content.
- Store only memories explicitly approved by the user instead of recording every qualifying message.
- Redact common secret formats, including API keys, authorization headers, passwords, private keys, session tokens, and access tokens.
- Create storage directories with mode
0700and files with mode0600on platforms that support POSIX permissions. - Validate that target files are regular files owned by the expected account before writing, and prevent symbolic-link traversal.
- Apply bounded retention, file-size limits, log rotation, and secure deletion after successful processing.
- Encrypt sensitive records at rest using an operating-system-backed key store or a user-managed encryption key.
- Clear
conversation_log.txtonly after processing succeeds, using an atomic workflow that prevents both data loss and indefinite retention. - Store paths portably with separate components, for example:
python DATABASE_DIR = os.path.join(WORKSPACE_ROOT, "database") BUFFER_FILE = os.path.join(DATABASE_DIR, "summary_buffer.json") CHROMA_PATH = os.path.join(DATABASE_DIR, "chroma_store")- Document the exact event semantics, collected fields, retention period, deletion behavior, and local-access risks.
