T05 · Unauthorized Access and Privilege Escalation
- Location
- scripts/memory_summarizer.py:863
- Finding
- Arbitrary Local File Read, Persistent Archival, and External Transmission<![CDATA[ ## Vulnerability Details **File Location**: `scripts/memory_summarizer.py:292, 321-325, 863`; `scripts/memory_db_tool.py:137-143` **Vulnerability Type**: Path traversal and missing workspace-boundary enforcement **Risk Level**: High ### Vulnerable Code ```python # scripts/memory_summarizer.py file_path = Path(memory_dir) / process_file success = await summarizer.process_single_file( file_path, store_raw=store_raw ) ``` ```python # scripts/memory_db_tool.py file_path = Path(args.process_file) if not file_path.is_absolute(): file_path = Path(summarizer.memory_dir) / file_path success = asyncio.run( summarizer.process_single_file(file_path, store_raw) ) ``` After reading the selected file, its contents are included in the LLM prompt and transmitted: ```python full_prompt = self.extraction_prompt_template + "\n" + content[:max_content_length] ``` ```python async with session.post( f"{base_url}/chat/completions", json=payload, headers=headers, timeout=timeout ) as response: ``` ### Technical Analysis The `process_file` parameter is intended to identify a memory file relative to the workspace memory directory. However, neither entry point canonicalizes the resulting path nor verifies that it remains beneath the authorized memory root. An absolute path bypasses the intended base directory because joining a `Path` with an absolute operand selects the absolute path. A relative value containing `../` can similarly traverse outside the memory directory. Symlinks within the memory directory can also point to files outside the permitted tree. `process_single_file()` reads the resulting path. By default, the complete file is stored in the L2 SQLite archive, while up to `max_content_length` characters are inserted into the LLM prompt and sent over the network. ### Attack Path 1. An attacker or manipulated agent invokes `summarize_memory_files` or the CLI with a path such as: - `/home/user/.ssh/id_rsa` - `../../.env` ...[truncated 895 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Resolve and validate both the memory root and requested path: ```python memory_root = Path(memory_dir).resolve(strict=True) if Path(process_file).is_absolute(): raise ValueError("Absolute paths are not permitted") candidate = (memory_root / process_file).resolve(strict=True) if not candidate.is_relative_to(memory_root): raise ValueError("Requested file is outside the memory directory") ``` 2. Reject symlink escapes and require the target to be a regular file. 3. Restrict accepted files to the necessary extension, such as `.md`. 4. Apply the same validation in the module tool wrapper, CLI, and `process_single_file()` so callers cannot bypass checks. 5. Require explicit approval before transmitting file contents externally. 6. Add secret scanning and redaction before archival or network transmission. 7. Disable raw-content archival by default. 8. Run the Skill under an operating-system account that cannot read unrelated credentials or workspaces. ]]>
