{"id":"CVE-2026-58203","aliases":["DEBIAN-CVE-2026-58203","EUVD-2026-41878","GHSA-4xgf-cpjx-pc3j","MGASA-2026-0260","ROOT-APP-PYPI-GHSA-4xgf-cpjx-pc3j","UBUNTU-CVE-2026-58203"],"status":"published","title":"NestedSecretsSettingsSource follows symlinks outside secrets_dir, enabling local file read and bypassing secrets_dir_max_size","description":"pydantic-settings provides settings management using Pydantic. From 2.12.0 until 2.14.2, 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 vulnerability is fixed in 2.14.2.","severity":{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00185,"percentile":0.0816,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-22","CWE-400","CWE-59"],"published":"2026-06-19T22:10:42Z","modified":"2026-11-04T10:47:28Z","fixes":[{"ecosystem":"PyPI","package":"pydantic-settings","fixed_in":["2.14.2"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"pydantic-settings","fixed_in":["2.14.2-1"],"sources":["osv"]},{"ecosystem":"Mageia:10","package":"python-pydantic-settings","fixed_in":["2.14.2-1.mga10"],"sources":["osv"]},{"ecosystem":"Root:PyPI","package":"pydantic-settings","fixed_in":["2.14.0+aikido.1","2.13.1+aikido.1","2.12.0+aikido.1","2.13.0+aikido.1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"pydantic-settings","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.12.0"},{"fixed":"2.14.2"}]}]},{"source":"osv","ecosystem":"Debian:14","package":"pydantic-settings","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.14.2-1"}]}],"versions":["2.10.1-1","2.10.1-2","2.11.0-1","2.12.0-1","2.12.0-2","2.13.0-1","2.14.0-1","2.14.1-1","2.8.1-2"]},{"source":"osv","ecosystem":"PyPI","package":"pydantic-settings","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.12.0"},{"fixed":"2.14.2"}]}],"versions":["2.12.0","2.13.0","2.13.1","2.14.0","2.14.1"]},{"source":"osv","ecosystem":"Mageia:10","package":"python-pydantic-settings","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.14.2-1.mga10"}]}]},{"source":"osv","ecosystem":"Root:PyPI","package":"pydantic-settings","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.14.0+aikido.1"},{"fixed":"2.13.1+aikido.1"},{"fixed":"2.12.0+aikido.1"},{"fixed":"2.13.0+aikido.1"}]}]},{"source":"osv","ecosystem":"Ubuntu:25.10","package":"pydantic-settings","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.8.1-1","2.8.1-2"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"pydantic-settings","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.8.1-2","2.12.0-2","2.13.0-1"]}],"products":[{"vendor":"pydantic","product":"pydantic-settings"}],"references":[{"url":"https://github.com/pydantic/pydantic-settings/security/advisories/GHSA-4xgf-cpjx-pc3j","tags":["x_refsource_CONFIRM","exploit","Exploit","Mitigation","Vendor Advisory","advisory","web","report"]},{"url":"https://github.com/advisories/GHSA-4xgf-cpjx-pc3j"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/58xxx/CVE-2026-58203.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-58203","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-58203","tags":["advisory"]},{"url":"https://github.com/pydantic/pydantic-settings","tags":["package"]},{"url":"https://advisories.mageia.org/MGASA-2026-0260.html","tags":["advisory"]},{"url":"https://bugs.mageia.org/show_bug.cgi?id=35774","tags":["report"]},{"url":"https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RIQBQN77WGZ57LE5TTARNBPREPF3JLEX/","tags":["web"]},{"url":"https://ubuntu.com/security/CVE-2026-58203","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-58203","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-58203","title":"NestedSecretsSettingsSource follows symlinks outside secrets_dir, enabling local file read and bypassing secrets_dir_max_size","description":"pydantic-settings provides settings management using Pydantic. From 2.12.0 until 2.14.2, 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 vulnerability is fixed in 2.14.2.","status":"published","published":"2026-07-06T14:24:15.74Z","modified":"2026-07-06T16:19:59.09Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium","assessor":"cna"}],"cwes":["CWE-22","CWE-59","CWE-400"],"updated_at":"2026-11-02T13:47:28.691429Z"}],"euvd":[{"id":"EUVD-2026-41878","description":"pydantic-settings provides settings management using Pydantic. From 2.12.0 until 2.14.2, 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 vulnerability is fixed in 2.14.2.","status":"published","published":"2026-07-06T14:24:15Z","modified":"2026-07-06T16:19:59Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium"}],"updated_at":"2026-11-02T18:20:13.172179Z"}],"ghsa":[{"id":"GHSA-4xgf-cpjx-pc3j","title":"pydantic-settings: NestedSecretsSettingsSource follows symlinks outside secrets_dir, enabling local file read and bypassing secrets_dir_max_size","description":"### 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 -\u003e /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`.","status":"published","published":"2026-06-19T22:10:42Z","modified":"2026-06-20T00:50:57Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium"}],"cwes":["CWE-22","CWE-59","CWE-400"],"updated_at":"2026-11-02T14:40:31.656952Z"}],"nvd":[{"id":"CVE-2026-58203","description":"pydantic-settings provides settings management using Pydantic. From 2.12.0 until 2.14.2, 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 vulnerability is fixed in 2.14.2.","status":"published","published":"2026-07-06T16:16:35.59Z","modified":"2026-10-25T20:10:00.147Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium","assessor":"secondary"}],"cwes":["CWE-22","CWE-59","CWE-400"],"updated_at":"2026-11-02T14:12:28.040501Z"}],"osv":[{"id":"CVE-2026-58203","title":"NestedSecretsSettingsSource follows symlinks outside secrets_dir, enabling local file read and bypassing secrets_dir_max_size","description":"pydantic-settings provides settings management using Pydantic. From 2.12.0 until 2.14.2, 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 vulnerability is fixed in 2.14.2.","status":"published","published":"2026-07-06T14:24:15.74Z","modified":"2026-11-04T10:47:28.373506329Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium"}],"cwes":["CWE-22","CWE-400","CWE-59"],"updated_at":"2026-11-04T11:06:56.727612Z"},{"id":"DEBIAN-CVE-2026-58203","description":"pydantic-settings provides settings management using Pydantic. From 2.12.0 until 2.14.2, 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 vulnerability is fixed in 2.14.2.","status":"published","published":"2026-07-06T16:16:35.59Z","modified":"2026-10-26T11:47:24.989415104Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium"}],"updated_at":"2026-11-02T15:45:57.590991Z"},{"id":"GHSA-4xgf-cpjx-pc3j","title":"pydantic-settings: NestedSecretsSettingsSource follows symlinks outside secrets_dir, enabling local file read and bypassing secrets_dir_max_size","description":"### 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 -\u003e /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`.","status":"published","published":"2026-06-19T22:10:42Z","modified":"2026-09-10T03:50:48.282428168Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium"}],"cwes":["CWE-22","CWE-400","CWE-59"],"updated_at":"2026-11-02T15:46:37.528068Z"},{"id":"UBUNTU-CVE-2026-58203","description":"pydantic-settings provides settings management using Pydantic. From 2.12.0 until 2.14.2, 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 vulnerability is fixed in 2.14.2.","status":"published","published":"2026-07-06T16:16:00Z","modified":"2026-07-09T17:17:12.900655432Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium"}],"updated_at":"2026-11-02T16:56:41.911783Z"},{"id":"MGASA-2026-0260","title":"Updated python-pydantic-settings packages fix a security vulnerability","description":"The updated packages fix a security vulnerability:\nNestedSecretsSettingsSource follows symlinks outside secrets_dir,\nenabling local file read and bypassing secrets_dir_max_size.\n(CVE-2026-58203)\n","status":"published","published":"2026-07-18T05:48:16Z","modified":"2026-07-18T06:00:04.808308208Z","updated_at":"2026-11-02T15:49:43.795591Z"},{"id":"ROOT-APP-PYPI-GHSA-4xgf-cpjx-pc3j","title":"GHSA-4xgf-cpjx-pc3j in pydantic-settings - Patched by Root","description":"Root has patched GHSA-4xgf-cpjx-pc3j in the pydantic-settings package for Root:PyPI. Multiple fixed versions available.","status":"published","published":"2026-08-03T10:02:36Z","modified":"2026-08-03T13:45:12.880615717Z","updated_at":"2026-11-02T15:53:28.259095Z"}]}}
