GHSA-4xgf-cpjx-pc3j: pydantic-settings Remote Code Execution Vulnerability

MODERATE
CVSS 5.3
CWE-400 CWE-59 CWE-22
Published
Advisory Summary

GHSA-4xgf-cpjx-pc3j is a MODERATE (CVSS 5.3) remote code execution vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting pydantic-settings libraries in the PyPI ecosystem. The issue involves nestedsecretssettingssource follows symlinks outside secrets_dir, enabling local file read and bypassing secrets_dir_max_size. Remediation is available by upgrading affected packages to version 2.14.2.

GHSA-4xgf-cpjx-pc3j at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-4xgf-cpjx-pc3j
Severity MODERATE
CVSS v3.1 Base Score 5.3
CWE Classification CWE-400: Uncontrolled Resource Consumption
Affected Product pydantic-settings
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 5.3

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L).

Attack Vector Local (AV:L)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity Low (I:L)
Availability Low (A:L)

How to Fix GHSA-4xgf-cpjx-pc3j

To remediate this vulnerability, upgrade all affected pydantic-settings dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

pydantic-settings fixed: 2.14.2
pip install pydantic-settings>=2.14.2
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show pydantic-settings

How to Check if You Are Affected

To determine whether your Python environment is affected by GHSA-4xgf-cpjx-pc3j, inspect your installed packages using pip:

pip show pydantic-settings

Analyze the terminal output to locate any version of pydantic-settings in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.14.2), your application is affected.

Note: The pip show pydantic-settings command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption, where the product does not properly control the allocation and maintenance of a limited resource. In pydantic-settings, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary `NestedSecretsSettingsSource` reads secret values from files in a configured `secrets_dir`. When `secrets_nested_subdir=True`, a directory entry inside `secrets_dir` that is a symbolic link pointing **outside** `secrets_dir` is followed, so files outside the configured directory are read into settings values. The same code path bypasses the documented `secrets_dir_max_size` protection. An attacker or lower-privileged component able to influence entries in the configured secrets directory (for example, a writable or shared secrets mount) can turn this into an unintended local file read into settings and can defeat the advertised loading-size cap. This report does not claim network reachability by itself. ### Details `NestedSecretsSettingsSource` performed two passes over `secrets_dir` using two different, inconsistent directory-traversal implementations: * The size check in `validate_secrets_path()` used `Path.glob('**/*')`, which does **not** descend into a symbolically-linked directory. * The loader in `load_secrets()` used `glob.iglob(f'{path}/**/*', recursive=True)` followed by `read_text()`, which **does** follow symlinked directories and reads through the link target. Because the two passes disagreed on symlinks, a symlinked directory inside `secrets_dir` whose target lives elsewhere was invisible to the size accounting (counted as 0 bytes) while still being fully read by the loader. This produces two distinct problems: 1. **Out-of-tree read (CWE-22 / CWE-59).** A symlinked directory (or file) inside `secrets_dir` that resolves outside it is followed, and the external file's contents are loaded into the corresponding settings field. 2. **`secrets_dir_max_size` bypass (CWE-400).** The size check never sees the out-of-tree content, so the documented size cap is neither respected nor able to reject the oversized external file. A related amplification exists for cyclic in-tree symlinks, which `glob.iglob(recursive=True)` re-traverses, inflating the size accounting and the number of loaded secrets. #### Reproduction In a clean Linux container, with a `secrets_dir` containing a symlink `secrets/db -> /path/outside` and an `outside/passwd` file of 512 bytes, while `secrets_dir_max_size=100`: ```python from pydantic import BaseModel from pydantic_settings import ( BaseSettings, SettingsConfigDict, NestedSecretsSettingsSource, ) class Db(BaseModel): passwd: str | None = None class Settings(BaseSettings): model_config = SettingsConfigDict( secrets_dir='secrets', secrets_nested_subdir=True, secrets_dir_max_size=100, # outside/passwd is 512 bytes ) db: Db = Db() @classmethod def settings_customise_sources( cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings ): return (NestedSecretsSettingsSource(file_secret_settings),) ``` On affected versions, `Settings().db.passwd` is populated with the 512-byte out-of-tree file and **no** `SettingsError` is raised, even though the file exceeds `secrets_dir_max_size`. ### Impact Applications that opt into `NestedSecretsSettingsSource` with `secrets_nested_subdir=True` and load secrets from a directory whose entries can be influenced by an attacker or a lower-privileged component (for example, a writable or shared secrets mount, or a secrets directory partially populated from untrusted input) are affected. The impact is: * **Confidentiality:** files outside the configured `secrets_dir` can be read into settings values (local file read). * **Integrity / availability of the safeguard:** the advertised `secrets_dir_max_size` cap can be bypassed, and cyclic symlinks can inflate resource usage during loading. The vulnerability requires the ability to place a symbolic link inside the configured secrets directory; it is not remotely reachable on its own. Applications that do not use `NestedSecretsSettingsSource`, or that point `secrets_dir` at a directory fully under the application's control, are not affected. ### Mitigation Upgrade to **pydantic-settings 2.14.2**, which: * walks the secrets directory explicitly and only descends into directories whose resolved path stays within `secrets_dir`, so symlinked directories pointing outside are never followed; * uses a single, cycle-safe iterator for both the size check and the loader, so the size accounting and the loaded set are always consistent and each real directory is visited at most once; * skips any file whose resolved path escapes `secrets_dir`, as defense in depth. If upgrading is not immediately possible, ensure the configured `secrets_dir` is fully owned and controlled by the application (no writable or attacker-influenced entries), or avoid `secrets_nested_subdir=True`.

GHSA-4xgf-cpjx-pc3j Affected pydantic-settings Versions

1 component(s)
Fixed Versions:
2.14.2
Product / Package Ecosystem Affected Versions Fixed Version
pydantic-settings
PyPI
≥ 2.12.0
< 2.14.2
2.14.2

References

Frequently Asked Questions

What is GHSA-4xgf-cpjx-pc3j?
GHSA-4xgf-cpjx-pc3j is a security vulnerability in pydantic-settings involving NestedSecretsSettingsSource follows symlinks outside secrets_dir, enabling local file read and bypassing secrets_dir_max_size under CWE-400: Uncontrolled Resource Consumption.
Which pydantic-settings versions are affected?
Affected versions include releases prior to 2.14.2 across affected packages: pydantic-settings.
What version fixes GHSA-4xgf-cpjx-pc3j?
Upgrading to 2.14.2 (or the latest patched release for your respective branch) resolves the vulnerability.
Is pydantic-settings affected?
Yes, pydantic-settings is affected in releases prior to 2.14.2. Upgrade your dependency to 2.14.2 or later.
What is the CVSS score of GHSA-4xgf-cpjx-pc3j?
GHSA-4xgf-cpjx-pc3j has a CVSS v3.1 base score of 5.3 (MODERATE) with vector string CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L.
What CWE is associated with GHSA-4xgf-cpjx-pc3j?
GHSA-4xgf-cpjx-pc3j is classified under CWE-400: Uncontrolled Resource Consumption (The product does not properly control the allocation and maintenance of a limited resource.).
How can I check PyPI dependencies for GHSA-4xgf-cpjx-pc3j?
Run the dependency tree command: 'pip show pydantic-settings'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-4xgf-cpjx-pc3j",
  "modified" : "2026-06-19T22:10:42Z",
  "published" : "2026-06-19T22:10:42Z",
  "aliases" : [ ],
  "summary" : "pydantic-settings: NestedSecretsSettingsSource follows symlinks outside secrets_dir, enabling local file read and bypassing secrets_dir_max_size",
  "details" : "### Summary\n\n`NestedSecretsSettingsSource` reads secret values from files in a configured `secrets_dir`. When `secrets_nested_subdir=True`, a directory entry inside `secrets_dir` that is a symbolic link pointing **outside** `secrets_dir` is followed, so files outside the configured directory are read into settings values. The same code path bypasses the documented `secrets_dir_max_size` protection. An attacker or lower-privileged component able to influence entries in the configured secrets directory (for example, a writable or shared secrets mount) can turn this into an unintended local file read into settings and can defeat the advertised loading-size cap. This report does not claim network reachability by itself.\n\n### Details\n\n`NestedSecretsSettingsSource` performed two passes over `secrets_dir` using two different, inconsistent directory-traversal implementations:\n\n* The size check in `validate_secrets_path()` used `Path.glob('**/*')`, which does **not** descend into a symbolically-linked directory.\n* The loader in `load_secrets()` used `glob.iglob(f'{path}/**/*', recursive=True)` followed by `read_text()`, which **does** follow symlinked directories and reads through the link target.\n\nBecause the two passes disagreed on symlinks, a symlinked directory inside `secrets_dir` whose target lives elsewhere was invisible to the size accounting (counted as 0 bytes) while still being fully read by the loader. This produces two distinct problems:\n\n1. **Out-of-tree read (CWE-22 / CWE-59).** A symlinked directory (or file) inside `secrets_dir` that resolves outside it is followed, and the external file's contents are loaded into the corresponding settings field.\n2. **`secrets_dir_max_size` bypass (CWE-400).** The size check never sees the out-of-tree content, so the documented size cap is neither respected nor able to reject the oversized external file. A related amplification exists for cyclic in-tree symlinks, which `glob.iglob(recursive=True)` re-traverses, inflating the size accounting and the number of loaded secrets.\n\n#### Reproduction\n\nIn a clean Linux container, with a `secrets_dir` containing a symlink `secrets/db -> /path/outside` and an `outside/passwd` file of 512 bytes, while `secrets_dir_max_size=100`:\n\n```python\nfrom pydantic import BaseModel\nfrom pydantic_settings import (\n    BaseSettings,\n    SettingsConfigDict,\n    NestedSecretsSettingsSource,\n)\n\n\nclass Db(BaseModel):\n    passwd: str | None = None\n\n\nclass Settings(BaseSettings):\n    model_config = SettingsConfigDict(\n        secrets_dir='secrets',\n        secrets_nested_subdir=True,\n        secrets_dir_max_size=100,  # outside/passwd is 512 bytes\n    )\n    db: Db = Db()\n\n    @classmethod\n    def settings_customise_sources(\n        cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings\n    ):\n        return (NestedSecretsSettingsSource(file_secret_settings),)\n```\n\nOn affected versions, `Settings().db.passwd` is populated with the 512-byte out-of-tree file and **no** `SettingsError` is raised, even though the file exceeds `secrets_dir_max_size`.\n\n### Impact\n\nApplications that opt into `NestedSecretsSettingsSource` with `secrets_nested_subdir=True` and load secrets from a directory whose entries can be influenced by an attacker or a lower-privileged component (for example, a writable or shared secrets mount, or a secrets directory partially populated from untrusted input) are affected. The impact is:\n\n* **Confidentiality:** files outside the configured `secrets_dir` can be read into settings values (local file read).\n* **Integrity / availability of the safeguard:** the advertised `secrets_dir_max_size` cap can be bypassed, and cyclic symlinks can inflate resource usage during loading.\n\nThe vulnerability requires the ability to place a symbolic link inside the configured secrets directory; it is not remotely reachable on its own. Applications that do not use `NestedSecretsSettingsSource`, or that point `secrets_dir` at a directory fully under the application's control, are not affected.\n\n### Mitigation\n\nUpgrade to **pydantic-settings 2.14.2**, which:\n\n* walks the secrets directory explicitly and only descends into directories whose resolved path stays within `secrets_dir`, so symlinked directories pointing outside are never followed;\n* uses a single, cycle-safe iterator for both the size check and the loader, so the size accounting and the loaded set are always consistent and each real directory is visited at most once;\n* skips any file whose resolved path escapes `secrets_dir`, as defense in depth.\n\nIf upgrading is not immediately possible, ensure the configured `secrets_dir` is fully owned and controlled by the application (no writable or attacker-influenced entries), or avoid `secrets_nested_subdir=True`.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "pydantic-settings"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.12.0"
      }, {
        "fixed" : "2.14.2"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/pydantic/pydantic-settings/security/advisories/GHSA-4xgf-cpjx-pc3j"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/pydantic/pydantic-settings"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-22", "CWE-400", "CWE-59" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-19T22:10:42Z",
    "nvd_published_at" : null
  }
}