T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/qa_doc_manager.py:348
- Finding
- Arbitrary File Read Through the Q&A Document Retrieval Command<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qa_doc_manager.py:348-358` and `scripts/qa_doc_manager.py:476-484` **Vulnerability Type**: Path traversal and unrestricted absolute-path file read **Risk Level**: High ### Vulnerable Code ```python def get_qa_doc_content(project_dir: str, doc_path: str) -> Optional[str]: """Read Q&A document content.""" full_path = os.path.join(project_dir, ".claude", doc_path) if os.path.exists(full_path): try: with open(full_path, 'r', encoding='utf-8') as f: return f.read() except: pass return None ``` The function is directly reachable through the command-line interface: ```python elif command == "get": if len(sys.argv) < 4: print("Usage: qa_doc_manager.py <projectDir> get <doc_path>") sys.exit(1) content = get_qa_doc_content(project_dir, sys.argv[3]) if content: print(content) else: print(json.dumps({"error": "Document does not exist"})) ``` ### Technical Analysis The `doc_path` argument comes directly from `sys.argv[3]` and is passed to `os.path.join()` without validation or canonical containment checking. An attacker can use either of the following path behaviors: 1. A path containing `../` components can escape the intended `<project>/.claude` directory. 2. On supported platforms, an absolute `doc_path` causes `os.path.join()` to discard the preceding project path components. The code only verifies that the resulting path exists. It does not verify that the canonical path remains within the intended Q&A document directory. It then prints the complete file contents to standard output. ### Attack Path 1. An attacker or untrusted instruction causes the agent to invoke the Q&A `get` command with a crafted path. 2. The attacker supplies an absolute path or a traversal path, for example: ```bash python3 scripts/qa_doc_manager.py /target/project get /etc/passwd ``` or: ```bash python ...[truncated 995 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Reject absolute paths supplied as document paths. 2. Resolve both the trusted root and requested path before opening the file. 3. Require the requested path to remain under `.claude/docs/qa`, not merely under the broader `.claude` directory. 4. Reject symlinks or verify containment after resolving symlinks. 5. Return a controlled error instead of suppressing all exceptions. 6. Avoid printing sensitive file content unless the document was obtained from a trusted index entry. Example hardening: ```python def get_qa_doc_content(project_dir: str, doc_path: str) -> Optional[str]: qa_root = ( Path(project_dir).resolve() / ".claude" / "docs" / "qa" ).resolve() supplied = Path(doc_path) if supplied.is_absolute(): return None candidate = (Path(project_dir).resolve() / ".claude" / supplied).resolve() try: candidate.relative_to(qa_root) except ValueError: return None if not candidate.is_file() or candidate.is_symlink(): return None return candidate.read_text(encoding="utf-8") ``` ]]>
