T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/vault-read.py:146- Finding
Arbitrary File Read Through Vault Path Traversal
- Content
View full analysis
dict: """Read and parse a specific vault file.""" # Try direct path first filepath = vault / filename if not filepath.exists(): # Try discovery by type files = discover_files(vault) target_type = guess_type(filename) matches = [f for f in files if f["type"] == target_type] if matches: filepath = vault / matches[0]["path"] else: print(f"ERROR: File not found: {filename}", file=sys.stderr) sys.exit(ERR_FILE_NOT_FOUND) # Read content try: with open(filepath, 'r', encoding='utf-8') as f: ``` ### Technical Analysis The `filename` argument is controlled through the `--file` command-line option and is joined directly to the configured vault path. The resulting path is not canonicalized or checked to ensure that it remains inside the vault. A filename containing parent-directory components, such as `../../sensitive-file`, can therefore resolve outside the intended vault. An absolute filename can also cause `pathlib` to discard the vault prefix. If the resulting external path exists, the script opens it and returns its contents. A symlink located inside the vault can produce the same boundary escape if it resolves to a file outside the vault. The vault directory consequently does not function as an effective security boundary. ### Attack Path 1. The attacker obtains the ability to influence the `--file` argument passed to `vault-read.py`. 2. The attacker supplies a traversal or absolute path, such as: ```text --file ../../sensitive-file ``` 3. The expression `vault / filename` resolves to a path outside the configured vault. 4. The script verifies only that the path exists; ...[truncated 685 chars]- Remediation
View remediation
Path: vault_root = vault.resolve() requested = Path(filename) if requested.is_absolute(): raise ValueError("Absolute file paths are not allowed") filepath = (vault_root / requested).resolve(strict=True) if not filepath.is_relative_to(vault_root): raise ValueError("Requested file is outside the vault") if not filepath.is_file(): raise ValueError("Requested path is not a regular file") return filepath ``` Use this validation for every direct and discovered file before opening it. In addition: - Reject absolute paths and explicit `..` path components as early validation. - Resolve symlinks before checking containment. - Consider restricting reads to Markdown files when the requested operation only supports vault notes. - Return a controlled error without exposing unnecessary absolute filesystem paths. - Add tests covering traversal paths, absolute paths, nested valid paths, and symlinks that point outside the vault. ]]>
