T05 · Unauthorized Access and Privilege Escalation
Warning
- Location
- scripts/estimate_tokens.py:30
- Finding
- Unrestricted Recursive Reading of Agent Memory Files## Vulnerability Details **File Location**: `scripts/estimate_tokens.py`, lines 30-65 and 89-91 **Vulnerability Type**: Excessive file access and insufficient path containment **Risk Level**: Medium ### Vulnerable Code ```python def scan_memory_dir(workspace: str): """Scan memory/ directory for all files, grouped by type.""" memory_dir = os.path.join(workspace, "memory") if not os.path.isdir(memory_dir): return [], 0, 0 entries = [] total_tokens = 0 total_bytes = 0 for root, dirs, files in os.walk(memory_dir): # Skip hidden dirs dirs[:] = [d for d in dirs if not d.startswith('.')] for fname in sorted(files): if fname.startswith('.'): continue fpath = os.path.join(root, fname) rel = os.path.relpath(fpath, workspace) try: size = os.path.getsize(fpath) total_bytes += size # Estimate tokens for text files only if fname.endswith(('.md', '.json', '.txt', '.yaml', '.yml')): with open(fpath, 'r', encoding='utf-8', errors='replace') as f: content = f.read() tokens = estimate_tokens(content) else: tokens = 0 # binary files total_tokens += tokens entries.append({ "path": rel, "bytes": size, "tokens": tokens, }) except (OSError, IOError): continue return entries, total_tokens, total_bytes ``` ```python # Memory files (NOT in context, but useful inventory) mem_entries, mem_tokens, mem_bytes = scan_memory_dir(workspace) ``` ### Technical Analysis The script accepts a caller-controlled workspace path and recursively traverses its `memory/` directory. It opens every non-hidden file with a supported text extension and reads the enti ...[truncated 2511 chars]
- Remediation
- ## Remediation Suggestions 1. **Avoid reading memory contents by default.** Use `os.stat()` or `Path.stat()` to report file counts and byte sizes. If token estimation requires content access, make it an explicit opt-in operation with a clear privacy warning. 2. **Canonicalize and validate the workspace root.** Resolve the supplied workspace before traversal: ```python workspace_root = Path(workspace).resolve(strict=True) memory_root = (workspace_root / "memory").resolve(strict=True) memory_root.relative_to(workspace_root) ``` 3. **Enforce containment for every file.** Resolve each candidate and reject it unless it remains under `memory_root`: ```python candidate = Path(root, fname) if candidate.is_symlink(): continue resolved = candidate.resolve(strict=True) try: resolved.relative_to(memory_root) except ValueError: continue ``` 4. **Skip symbolic links.** Use `os.lstat()` or `Path.is_symlink()` before size checks and file opening. Where supported, use no-follow semantics and verify the opened file descriptor to reduce time-of-check/time-of-use risks. 5. **Apply resource limits.** Limit maximum file size, total bytes read, and traversal depth to prevent memory exhaustion or denial of service from unusually large files or directory trees. 6. **Handle decoding consistently.** The top-level workspace files are opened without `errors='replace'`, while memory files use replacement behavior. Apply explicit error handling so malformed input cannot unexpectedly terminate the audit operation. 7. **Document the data-access boundary.** Clearly state whether the Skill performs metadata-only inventory or content-based estimation, and require user confirmation before processing long-term Agent memory.
