mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-25 00:35:09 +00:00
fix(deps): bump json-repair past GHSA-xf7x-x43h-rpqh vulnerable range
json-repair < 0.60.1 has an unbounded-CPU DoS on circular JSON Schema $ref structures, and crewai feeds LLM output straight into repair_json, so the ~=0.25.2 pin kept every install inside the vulnerable range and flagged HIGH by dependency scanners with no resolvable path. Raise the requirement to >=0.60.1,<1.0.0 and adapt the agent parser to the new failure semantics: json-repair >= 0.60 returns "" (not '""') for unrepairable input and coerces non-JSON brace text into arrays, so _safe_repair_json now also keeps the original tool input unless the repair produced a JSON object. Adds a regression test asserting the published requirement excludes the vulnerable range and admits patched releases. Linear: OSS-91 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ dependencies = [
|
||||
"click>=8.1.7,<9",
|
||||
"appdirs~=1.4.4",
|
||||
"jsonref~=1.1.0",
|
||||
"json-repair~=0.25.2",
|
||||
"json-repair>=0.60.1,<1.0.0",
|
||||
"cel-python>=0.5.0,<0.6",
|
||||
"tomli-w~=1.1.0",
|
||||
"tomli~=2.0.2",
|
||||
|
||||
@@ -14,7 +14,8 @@ MISSING_ACTION_INPUT_AFTER_ACTION_ERROR_MESSAGE: Final[str] = (
|
||||
FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE: Final[str] = (
|
||||
"I did it wrong. Tried to both perform Action and give a Final Answer at the same time, I must do one or the other"
|
||||
)
|
||||
UNABLE_TO_REPAIR_JSON_RESULTS: Final[list[str]] = ['""', "{}"]
|
||||
# json-repair < 0.60 signaled unrepairable input with '""'; >= 0.60 returns "".
|
||||
UNABLE_TO_REPAIR_JSON_RESULTS: Final[list[str]] = ['""', "{}", ""]
|
||||
ACTION_INPUT_REGEX: Final[re.Pattern[str]] = re.compile(
|
||||
r"Action\s*\d*\s*:\s*(.*?)\s*Action\s*\d*\s*Input\s*\d*\s*:\s*(.*)", re.DOTALL
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ AgentAction or AgentFinish objects.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
|
||||
from json_repair import repair_json # type: ignore[import-untyped]
|
||||
from pydantic import BaseModel
|
||||
@@ -176,4 +177,14 @@ def _safe_repair_json(tool_input: str) -> str:
|
||||
if result in UNABLE_TO_REPAIR_JSON_RESULTS:
|
||||
return tool_input
|
||||
|
||||
# Non-JSON input (e.g. "{temperature in SF}") gets coerced into JSON
|
||||
# scalars/arrays by json-repair rather than repaired; only a repaired
|
||||
# object is a plausible tool input, so keep the original otherwise.
|
||||
try:
|
||||
repaired_object = json.loads(str(result))
|
||||
except (ValueError, TypeError):
|
||||
return tool_input
|
||||
if not isinstance(repaired_object, dict):
|
||||
return tool_input
|
||||
|
||||
return str(result)
|
||||
|
||||
47
lib/crewai/tests/security/test_dependency_pins.py
Normal file
47
lib/crewai/tests/security/test_dependency_pins.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Guardrails for security-sensitive dependency pins in crewai's pyproject.
|
||||
|
||||
These tests parse the published requirement specifiers so that a stale pin
|
||||
cannot silently reintroduce a known-vulnerable dependency range.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
try:
|
||||
import tomllib
|
||||
except ModuleNotFoundError: # Python 3.10
|
||||
import tomli as tomllib # type: ignore[no-redef]
|
||||
|
||||
PYPROJECT_PATH = Path(__file__).parents[2] / "pyproject.toml"
|
||||
|
||||
|
||||
def _get_requirement(name: str) -> Requirement:
|
||||
data = tomllib.loads(PYPROJECT_PATH.read_text())
|
||||
for spec in data["project"]["dependencies"]:
|
||||
requirement = Requirement(spec)
|
||||
if requirement.name == name:
|
||||
return requirement
|
||||
raise AssertionError(f"{name} not found in [project.dependencies]")
|
||||
|
||||
|
||||
class TestJsonRepairPin:
|
||||
"""GHSA-xf7x-x43h-rpqh: json-repair < 0.60.1 has an unbounded-CPU DoS on
|
||||
circular JSON Schema $ref structures. crewai feeds LLM output to
|
||||
json_repair, so the vulnerable range is a real DoS surface (OSS-91)."""
|
||||
|
||||
def test_requirement_excludes_vulnerable_range(self) -> None:
|
||||
requirement = _get_requirement("json-repair")
|
||||
for vulnerable in ("0.25.2", "0.25.3", "0.59.0", "0.60.0"):
|
||||
assert not requirement.specifier.contains(vulnerable), (
|
||||
f"json-repair requirement '{requirement}' admits {vulnerable}, "
|
||||
"which is vulnerable to GHSA-xf7x-x43h-rpqh (fixed in 0.60.1)"
|
||||
)
|
||||
|
||||
def test_requirement_admits_patched_releases(self) -> None:
|
||||
requirement = _get_requirement("json-repair")
|
||||
for patched in ("0.60.1", "0.61.4"):
|
||||
assert requirement.specifier.contains(patched), (
|
||||
f"json-repair requirement '{requirement}' blocks patched "
|
||||
f"version {patched}"
|
||||
)
|
||||
Reference in New Issue
Block a user