chore(deps): upgrade PyYAML to address known security vulnerability
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 39s
CI / helm (pull_request) Successful in 48s
CI / build (pull_request) Successful in 1m1s
CI / benchmark-regression (pull_request) Failing after 1m15s
CI / lint (pull_request) Failing after 1m20s
CI / quality (pull_request) Successful in 1m21s
CI / typecheck (pull_request) Successful in 1m40s
CI / security (pull_request) Successful in 1m47s
CI / integration_tests (pull_request) Successful in 3m12s
CI / unit_tests (pull_request) Failing after 3m48s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 4m28s
CI / status-check (pull_request) Failing after 4s
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 39s
CI / helm (pull_request) Successful in 48s
CI / build (pull_request) Successful in 1m1s
CI / benchmark-regression (pull_request) Failing after 1m15s
CI / lint (pull_request) Failing after 1m20s
CI / quality (pull_request) Successful in 1m21s
CI / typecheck (pull_request) Successful in 1m40s
CI / security (pull_request) Successful in 1m47s
CI / integration_tests (pull_request) Successful in 3m12s
CI / unit_tests (pull_request) Failing after 3m48s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 4m28s
CI / status-check (pull_request) Failing after 4s
- Added `pyyaml>=6.0.3` dependency constraint to pyproject.toml after aiohttp to prevent installation of vulnerable older versions with known YAML parsing security issues. - Updated uv.lock package dependencies and requires-dist to include pyyaml entry with specifier '>=6.0.3'. - Added changelog Security section entry under [Unreleased] documenting the upgrade for issue #9055. - Updated CONTRIBUTORS.md with contribution note (PR #9244). - Added BDD test scenario verifying PyYAML security dependency constraint. ISSUES CLOSED: #9055
This commit is contained in:
@@ -96,6 +96,11 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
### Security
|
||||
|
||||
- **PyYAML upgraded to >=6.0.3 to address known security vulnerability** (#9055):
|
||||
Added an explicit `pyyaml>=6.0.3` dependency constraint to `pyproject.toml` to
|
||||
prevent installation of vulnerable older versions with known YAML parsing security
|
||||
issues.
|
||||
|
||||
- **aiohttp upgraded to >=3.13.4 to remediate CVE-2026-34513 and CVE-2026-34515** (#1549, #1544):
|
||||
Added an explicit `aiohttp>=3.13.4` dependency constraint to `pyproject.toml` to remediate
|
||||
two high-severity open redirect vulnerabilities. Both CVEs affect the CleverAgents platform's
|
||||
|
||||
+3
-1
@@ -33,4 +33,6 @@ Below are some of the specific details of various contributions.
|
||||
* HAL 9000 has contributed the LLMTraceRepository data-integrity fix (PR #8185 / issue #7505): replaced the unconditional `session.commit()` in `LLMTraceRepository.save()` with a dual-path implementation that respects the UnitOfWork pattern — flushing only when an external session is provided, and flushing + committing + closing when operating standalone. This eliminates premature transaction commits, loss of rollback capability, and a docstring/implementation mismatch.
|
||||
* HAL 9000 has contributed the ACMS Index Data Model and File Traversal Engine (PR #9664 / issue #9579): foundational data structures for indexed context entries with hot/warm/cold/archive storage tier classification, tag system, and a timeout-safe chunked file traversal engine for large projects with 10,000+ files.
|
||||
|
||||
* HAL 9000 has contributed the error-suppression removal fix (PR #9247 / issue #9060): removed both `try...except Exception:` blocks in `register_registry_agents()` that silently suppressed errors from `actor_registry.list_actors()` and the route bridge refresh, enabling exceptions to propagate per CONTRIBUTING.md fail-fast policy. Added three Behave scenarios verifying RuntimeError, AttributeError, and TypeError propagation.
|
||||
* HAL 9000 has contributed the error-suppression removal fix (PR #9247 / issue #9060): removed both `try...except Exception:` blocks in `register_registry_agents()` that silently suppressed errors from `actor_registry.list_actors()` and the route bridge refresh, enabling exceptions to propagate per CONTRIBUTING.md fail-fast policy. Added three Behave scenarios verifying RuntimeError, AttributeError, and TypeError propagation.
|
||||
|
||||
* HAL 9000 has contributed the PyYAML security upgrade (PR #9244 / issue #9055): added `pyyaml>=6.0.3` dependency constraint to address known YAML parsing vulnerabilities.
|
||||
@@ -0,0 +1,23 @@
|
||||
@tdd_issue @tdd_issue_9055
|
||||
Feature: PyYAML is a declared project dependency with minimum secure version
|
||||
As a CleverAgents developer
|
||||
I want the PyYAML dependency to be listed in pyproject.toml with a minimum secure version
|
||||
So that vulnerable older versions of PyYAML cannot be installed
|
||||
|
||||
Background:
|
||||
Given the pyproject.toml file exists at "pyproject.toml"
|
||||
|
||||
@tdd_issue @tdd_issue_9055
|
||||
Scenario: PyYAML is listed in project dependencies with minimum version constraint
|
||||
When I read the project dependencies from pyproject.toml
|
||||
Then the dependency list should include a package matching "pyyaml>="
|
||||
|
||||
@tdd_issue @tdd_issue_9055
|
||||
Scenario: PyYAML minimum version is >=6.0.3
|
||||
When I read the PyYAML dependency specification from pyproject.toml
|
||||
Then the minimum version should be at least "6.0.3"
|
||||
|
||||
@tdd_issue @tdd_issue_9055
|
||||
Scenario: yaml module is importable as a project dependency
|
||||
When I attempt to import the "yaml" module
|
||||
Then the import should succeed without errors
|
||||
@@ -0,0 +1,132 @@
|
||||
"""Step definitions for PyYAML security dependency verification (#9055)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from behave import given, then, when
|
||||
from behave.runner import Context
|
||||
|
||||
|
||||
@given('the pyproject.toml file exists at "{path}"')
|
||||
def step_pyproject_exists(context: Context, path: str) -> None:
|
||||
"""Verify pyproject.toml exists at the given path."""
|
||||
pyproject_path = Path(path)
|
||||
assert pyproject_path.exists(), f"pyproject.toml not found at {path}"
|
||||
context.pyproject_path = pyproject_path
|
||||
|
||||
|
||||
@when("I read the project dependencies from pyproject.toml")
|
||||
def step_read_project_dependencies(context: Context) -> None:
|
||||
"""Read the [project.dependencies] list from pyproject.toml."""
|
||||
content = context.pyproject_path.read_bytes()
|
||||
data: dict[str, Any] = _load_toml(content)
|
||||
context.project_dependencies = data.get("project", {}).get("dependencies", [])
|
||||
|
||||
|
||||
@then('the dependency list should include a package matching "{pattern}"')
|
||||
def step_dependency_matches_pattern(context: Context, pattern: str) -> None:
|
||||
"""Assert that a dependency in the project dependencies matches the given regex pattern."""
|
||||
deps: list[str] = context.project_dependencies
|
||||
found = any(re.search(pattern, dep) for dep in deps)
|
||||
assert found, (
|
||||
f"No dependency matching '{pattern}' found in "
|
||||
f"[project.dependencies]. Current deps: {deps}"
|
||||
)
|
||||
|
||||
|
||||
@when("I read the PyYAML dependency specification from pyproject.toml")
|
||||
def step_read_pyyaml_dependency(context: Context) -> None:
|
||||
"""Read and parse the PyYAML dependency from pyproject.toml."""
|
||||
content = context.pyproject_path.read_bytes()
|
||||
data: dict[str, Any] = _load_toml(content)
|
||||
deps: list[str] = data.get("project", {}).get("dependencies", [])
|
||||
|
||||
# Find the pyyaml dependency entry
|
||||
pyyaml_dep = None
|
||||
for dep in deps:
|
||||
if dep.strip().startswith("pyyaml"):
|
||||
pyyaml_dep = dep.strip()
|
||||
break
|
||||
|
||||
assert pyyaml_dep is not None, (
|
||||
f"PyYAML dependency not found in [project.dependencies]. Current deps: {deps}"
|
||||
)
|
||||
|
||||
# Parse the minimum version from the constraint
|
||||
# Examples: "pyyaml>=6.0.3", "pyyaml>=6.0", "pyyaml==6.0.2"
|
||||
match = re.search(r"(?:>=|==|~>)([\d]+(?:\.[\d]+)*(?:\.\w+)*)", pyyaml_dep)
|
||||
assert match is not None, f"Could not parse version from PyYAML dependency: {pyyaml_dep}"
|
||||
|
||||
context.pyyaml_version_spec = pyyaml_dep
|
||||
context.pyyaml_min_version = match.group(1)
|
||||
|
||||
|
||||
@then("the minimum version should be at least \"{expected}\"")
|
||||
def step_pyyaml_minimum_version(context: Context, expected: str) -> None:
|
||||
"""Assert that the PyYAML minimum version is >= expected version."""
|
||||
min_version = context.pyyaml_min_version
|
||||
assert min_version == expected, (
|
||||
f"PyYAML minimum version {min_version} does not match expected {expected}. "
|
||||
f"Dependency spec: {context.pyyaml_version_spec}"
|
||||
)
|
||||
|
||||
|
||||
@when('I attempt to import the "{module_name}" module')
|
||||
def step_attempt_import(context: Context, module_name: str) -> None:
|
||||
"""Attempt to import the given module and record the result."""
|
||||
try:
|
||||
context.imported_module = importlib.import_module(module_name)
|
||||
context.import_error = None
|
||||
except ImportError as exc:
|
||||
context.imported_module = None
|
||||
context.import_error = exc
|
||||
|
||||
|
||||
@then("the import should succeed without errors")
|
||||
def step_import_succeeded(context: Context) -> None:
|
||||
"""Assert that the previous import attempt succeeded."""
|
||||
assert context.import_error is None, f"Import failed with: {context.import_error}"
|
||||
assert context.imported_module is not None, "Module was not imported"
|
||||
|
||||
|
||||
@then('the version constraint in uv.lock should include specifier "{specifier}"')
|
||||
def step_uv_lock_specifier(context: Context, specifier: str) -> None:
|
||||
"""Assert that pyyaml appears in both package.dependencies and requires-dist in uv.lock."""
|
||||
lock_path = context.pyproject_path.parent / "uv.lock"
|
||||
content = lock_path.read_bytes().decode("utf-8")
|
||||
|
||||
# Check that pyyaml is in the cleveragents package dependencies section
|
||||
assert (
|
||||
'[[package]]\nname = "cleveragents"' in content
|
||||
), "cleveragents package section not found in uv.lock"
|
||||
|
||||
# Verify pyyaml entry exists in file after cleveragents section
|
||||
cleveragents_match = re.search(
|
||||
r'\[\[package\]\]\s*name = "cleveragents"(.*?)^\[\[package\]\]',
|
||||
content,
|
||||
re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
assert cleveragents_match, "Could not find cleveragents package section"
|
||||
|
||||
cleveragents_block = cleveragents_match.group(1)
|
||||
|
||||
assert '{ name = "pyyaml" }' in cleveragents_block, (
|
||||
f"pyyaml not found in cleveragents package dependencies in uv.lock"
|
||||
)
|
||||
|
||||
|
||||
def _load_toml(content: bytes) -> dict[str, Any]:
|
||||
"""Load a TOML file from bytes. Uses tomllib if available, else fallback."""
|
||||
try:
|
||||
import tomllib
|
||||
|
||||
return tomllib.loads(content.decode("utf-8"))
|
||||
except ImportError:
|
||||
# Fallback for older Python versions without tomllib
|
||||
import tomlkit
|
||||
|
||||
return tomlkit.parse(content.decode("utf-8")).unwrap() # type: ignore[no-any-return]
|
||||
@@ -47,6 +47,7 @@ dependencies = [
|
||||
"tomlkit>=0.13.0", # TOML writing with comment preservation for config CLI
|
||||
"tenacity>=8.2.0", # Retry framework for service layer resilience
|
||||
"aiohttp>=3.13.4", # CVE-2026-34515 mitigation: open redirect vulnerability
|
||||
"pyyaml>=6.0.3", # Security: address known YAML parsing vulnerabilities
|
||||
"a2a-sdk>=0.3.0,<1.0.0", # A2A Python SDK — required transport for local (stdio) and server (HTTP) modes (ADR-047); pinned <1.0.0 (removed legacy A2AClient)
|
||||
]
|
||||
|
||||
|
||||
@@ -446,6 +446,7 @@ dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "python-ulid" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "restrictedpython" },
|
||||
{ name = "rx" },
|
||||
{ name = "structlog" },
|
||||
@@ -528,6 +529,7 @@ requires-dist = [
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" },
|
||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23.0" },
|
||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" },
|
||||
{ name = "pyyaml", specifier = ">=6.0.3" },
|
||||
{ name = "python-ulid", specifier = ">=2.7.0" },
|
||||
{ name = "radon", marker = "extra == 'dev'", specifier = ">=6.0.1" },
|
||||
{ name = "restrictedpython", specifier = ">=7.0" },
|
||||
|
||||
Reference in New Issue
Block a user