T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/estimate_tokens.py:30- Finding
Unrestricted Recursive Reading of Agent Memory Files
- Content
View full analysis
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: MediumVulnerable 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_bytespython # 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
View remediation
Remediation Suggestions
-
Avoid reading memory contents by default. Use
os.stat()orPath.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. -
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)- 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-
Skip symbolic links. Use
os.lstat()orPath.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. -
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.
-
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. -
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.
-
