mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-07 16:09:30 +00:00
Compare commits
1 Commits
fix/output
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83d7e8235e |
2
.github/workflows/vulnerability-scan.yml
vendored
2
.github/workflows/vulnerability-scan.yml
vendored
@@ -51,7 +51,6 @@ jobs:
|
||||
--ignore-vuln PYSEC-2024-277 \
|
||||
--ignore-vuln PYSEC-2026-89 \
|
||||
--ignore-vuln PYSEC-2026-97 \
|
||||
--ignore-vuln PYSEC-2026-597 \
|
||||
--ignore-vuln PYSEC-2025-148 \
|
||||
--ignore-vuln PYSEC-2025-183 \
|
||||
--ignore-vuln PYSEC-2025-189 \
|
||||
@@ -79,7 +78,6 @@ jobs:
|
||||
# PYSEC-2024-277 - joblib 1.5.3: disputed; NumpyArrayWrapper only used with trusted caches
|
||||
# PYSEC-2026-89 - markdown 3.10.2: DoS via malformed HTML; fix 3.8.1 — already past, advisory range is stale
|
||||
# PYSEC-2026-97 - nltk 3.9.4: arbitrary file read in filestring(); no fix available
|
||||
# PYSEC-2026-597 - nltk 3.9.4: path traversal via percent-encoded sequences in data.load()/find() (incomplete fix for #3504); latest release, no fix available. Transitive via unstructured; crewai never calls nltk.data.load/find with untrusted input.
|
||||
# PYSEC-2025-148 - onnx 1.21.0: path traversal in save_external_data; no fix available
|
||||
# PYSEC-2025-183 - pyjwt 2.12.1: disputed weak-encryption claim; key length is application-chosen
|
||||
# PYSEC-2025-189..197 - torch 2.11.0: memory-corruption/DoS in functions only reachable via untrusted models; no fix available
|
||||
|
||||
@@ -22,7 +22,7 @@ dependencies = [
|
||||
"opentelemetry-sdk~=1.42.0",
|
||||
"opentelemetry-exporter-otlp-proto-http~=1.42.0",
|
||||
# Data Handling
|
||||
"chromadb~=1.1.0",
|
||||
"chromadb~=1.5.9",
|
||||
"tokenizers>=0.21,<1",
|
||||
"openpyxl~=3.1.5",
|
||||
# Authentication and Security
|
||||
|
||||
@@ -10,7 +10,7 @@ from hashlib import md5
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path, PurePosixPath, PureWindowsPath
|
||||
from pathlib import Path
|
||||
import threading
|
||||
from typing import (
|
||||
Annotated,
|
||||
@@ -504,29 +504,6 @@ class Task(BaseModel):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
if "{" in value or "}" in value:
|
||||
template_vars = [part.split("}")[0] for part in value.split("{")[1:]]
|
||||
for var in template_vars:
|
||||
if not var.isidentifier():
|
||||
raise ValueError(f"Invalid template variable name: {var}")
|
||||
# Literal portions are still checked here; the fully interpolated
|
||||
# path is re-validated at runtime (see
|
||||
# interpolate_inputs_and_add_conversation_history) because template
|
||||
# variables may be filled from untrusted kickoff inputs.
|
||||
cls._sanitize_output_file_path(value)
|
||||
return value
|
||||
|
||||
return cls._sanitize_output_file_path(value)
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_output_file_path(value: str) -> str:
|
||||
"""Enforce path-safety on an ``output_file`` value.
|
||||
|
||||
Shared by the field validator's literal and template branches. Rejects
|
||||
traversal sequences, shell expansion, and shell metacharacters, and
|
||||
strips a leading ``/`` so a literal path stays relative to the working
|
||||
directory.
|
||||
"""
|
||||
if ".." in value:
|
||||
raise ValueError(
|
||||
"Path traversal attempts are not allowed in output_file paths"
|
||||
@@ -542,56 +519,17 @@ class Task(BaseModel):
|
||||
"Shell special characters are not allowed in output_file paths"
|
||||
)
|
||||
|
||||
if "{" in value or "}" in value:
|
||||
template_vars = [part.split("}")[0] for part in value.split("{")[1:]]
|
||||
for var in template_vars:
|
||||
if not var.isidentifier():
|
||||
raise ValueError(f"Invalid template variable name: {var}")
|
||||
return value
|
||||
|
||||
if value.startswith("/"):
|
||||
return value[1:]
|
||||
return value
|
||||
|
||||
def _validate_output_file_input_values(
|
||||
self, inputs: dict[str, str | int | float | dict[str, Any] | list[Any]]
|
||||
) -> None:
|
||||
"""Reject untrusted input values that would escape the output path.
|
||||
|
||||
Only the variables that actually appear in the ``output_file`` template
|
||||
are checked. The developer-authored template is trusted (it may contain
|
||||
an absolute base directory), but a value substituted into it must not
|
||||
introduce path traversal (``..``), an absolute path, a home/variable
|
||||
expansion (``~``/``$``), or shell metacharacters that would redirect the
|
||||
write outside the intended location.
|
||||
"""
|
||||
if not self._original_output_file:
|
||||
return
|
||||
|
||||
template_vars = [
|
||||
part.split("}")[0] for part in self._original_output_file.split("{")[1:]
|
||||
]
|
||||
for var in template_vars:
|
||||
if var not in inputs:
|
||||
continue
|
||||
value = str(inputs[var])
|
||||
if ".." in value:
|
||||
raise ValueError(
|
||||
f"Invalid value for output_file variable '{var}': path "
|
||||
"traversal sequences ('..') are not allowed"
|
||||
)
|
||||
if value.startswith(("~", "$")):
|
||||
raise ValueError(
|
||||
f"Invalid value for output_file variable '{var}': shell "
|
||||
"expansion characters are not allowed"
|
||||
)
|
||||
if any(char in value for char in ["|", ">", "<", "&", ";"]):
|
||||
raise ValueError(
|
||||
f"Invalid value for output_file variable '{var}': shell "
|
||||
"special characters are not allowed"
|
||||
)
|
||||
if (
|
||||
PurePosixPath(value).is_absolute()
|
||||
or PureWindowsPath(value).is_absolute()
|
||||
):
|
||||
raise ValueError(
|
||||
f"Invalid value for output_file variable '{var}': absolute "
|
||||
"paths are not allowed"
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def set_attributes_based_on_config(self) -> Task:
|
||||
"""Set attributes based on the agent configuration."""
|
||||
@@ -1088,12 +1026,6 @@ Follow these guidelines:
|
||||
raise ValueError(f"Error interpolating expected_output: {e!s}") from e
|
||||
|
||||
if self.output_file is not None:
|
||||
# Values interpolated into the output path may come from untrusted
|
||||
# kickoff inputs. The developer-authored template (including any
|
||||
# absolute base directory) is trusted, but an injected value must
|
||||
# not introduce path traversal, an absolute path, or shell
|
||||
# expansion that would escape the intended location.
|
||||
self._validate_output_file_input_values(inputs)
|
||||
try:
|
||||
self.output_file = interpolate_only(
|
||||
input_string=self._original_output_file, inputs=inputs
|
||||
|
||||
@@ -931,48 +931,6 @@ def test_interpolate_inputs(tmp_path):
|
||||
assert task.output_file == str(tmp_path / "ML" / "output_2025.txt")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("template", "malicious_inputs", "expected_error"),
|
||||
[
|
||||
("reports/{name}.md", {"name": "../../../../tmp/pwn"}, "path traversal"),
|
||||
("{p}", {"p": "/tmp/abs_pwn"}, "absolute paths"),
|
||||
("{p}", {"p": "~/.bashrc"}, "shell expansion"),
|
||||
("{p}", {"p": "x;rm -rf /"}, "shell special characters"),
|
||||
("{p}", {"p": r"C:\Windows\evil"}, "absolute paths"),
|
||||
],
|
||||
)
|
||||
def test_interpolate_output_file_rejects_unsafe_inputs(
|
||||
template, malicious_inputs, expected_error
|
||||
):
|
||||
"""Untrusted inputs must not escape the output_file path via interpolation."""
|
||||
task = Task(
|
||||
description="do {p} {name}".replace("{p}", "x").replace("{name}", "x"),
|
||||
expected_output="e",
|
||||
output_file=template,
|
||||
)
|
||||
with pytest.raises(ValueError, match=expected_error):
|
||||
task.interpolate_inputs_and_add_conversation_history(inputs=malicious_inputs)
|
||||
|
||||
|
||||
def test_interpolate_output_file_allows_safe_inputs(tmp_path):
|
||||
"""Safe input values and developer-chosen absolute base paths still work."""
|
||||
task = Task(
|
||||
description="d",
|
||||
expected_output="e",
|
||||
output_file="reports/{name}.md",
|
||||
)
|
||||
task.interpolate_inputs_and_add_conversation_history(inputs={"name": "q3_summary"})
|
||||
assert task.output_file == "reports/q3_summary.md"
|
||||
|
||||
abs_task = Task(
|
||||
description="d",
|
||||
expected_output="e",
|
||||
output_file=str(tmp_path / "{topic}" / "out.md"),
|
||||
)
|
||||
abs_task.interpolate_inputs_and_add_conversation_history(inputs={"topic": "sales"})
|
||||
assert abs_task.output_file == str(tmp_path / "sales" / "out.md")
|
||||
|
||||
|
||||
def test_interpolate_only():
|
||||
"""Test the interpolate_only method for various scenarios including JSON structure preservation."""
|
||||
|
||||
|
||||
20
uv.lock
generated
20
uv.lock
generated
@@ -13,7 +13,7 @@ resolution-markers = [
|
||||
]
|
||||
|
||||
[options]
|
||||
exclude-newer = "2026-06-28T20:06:34.114646Z"
|
||||
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
|
||||
exclude-newer-span = "P3D"
|
||||
|
||||
[options.exclude-newer-package]
|
||||
@@ -985,7 +985,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "chromadb"
|
||||
version = "1.1.1"
|
||||
version = "1.5.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "bcrypt" },
|
||||
@@ -1004,9 +1004,9 @@ dependencies = [
|
||||
{ name = "opentelemetry-sdk" },
|
||||
{ name = "orjson" },
|
||||
{ name = "overrides" },
|
||||
{ name = "posthog" },
|
||||
{ name = "pybase64" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "pypika" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "rich" },
|
||||
@@ -1017,13 +1017,13 @@ dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7f/48/11851dddeadad6abe36ee071fedc99b5bdd2c324df3afa8cb952ae02798b/chromadb-1.1.1.tar.gz", hash = "sha256:ebfce0122753e306a76f1e291d4ddaebe5f01b5979b97ae0bc80b1d4024ff223", size = 1338109, upload-time = "2025-10-05T02:49:14.834Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/92/d1/5e33b26985f0c7046a0be1cee2158ada1748ee700d2545057fde1468d74d/chromadb-1.5.9.tar.gz", hash = "sha256:5c20e62a455c28bacac927f26116a73fd8e1799e0d908be8e8a4f02197a54731", size = 2595635, upload-time = "2026-05-05T05:54:51.713Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/59/0d881a9b7eb63d8d2446cf67fcbb53fb8ae34991759d2b6024a067e90a9a/chromadb-1.1.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:27fe0e25ef0f83fb09c30355ab084fe6f246808a7ea29e8c19e85cf45785b90d", size = 19175479, upload-time = "2025-10-05T02:49:12.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/4f/5a9fa317c84c98e70af48f74b00aa25589626c03a0428b4381b2095f3d73/chromadb-1.1.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:95aed58869683f12e7dcbf68b039fe5f576dbe9d1b86b8f4d014c9d077ccafd2", size = 18267188, upload-time = "2025-10-05T02:49:09.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/1a/02defe2f1c8d1daedb084bbe85f5b6083510a3ba192ed57797a3649a4310/chromadb-1.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06776dad41389a00e7d63d936c3a15c179d502becaf99f75745ee11b062c9b6a", size = 18855754, upload-time = "2025-10-05T02:49:03.299Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/0d/80be82717e5dc19839af24558494811b6f2af2b261a8f21c51b872193b09/chromadb-1.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bba0096a7f5e975875ead23a91c0d41d977fbd3767f60d3305a011b0ace7afd3", size = 19893681, upload-time = "2025-10-05T02:49:06.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/6e/956e62975305a4e31daf6114a73b3b0683a8f36f8d70b20aabd466770edb/chromadb-1.1.1-cp39-abi3-win_amd64.whl", hash = "sha256:a77aa026a73a18181fd89bbbdb86191c9a82fd42aa0b549ff18d8cae56394c8b", size = 19844042, upload-time = "2025-10-05T02:49:16.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/5b/3cced915244f43ed14b53fe9f63a37f05f865064f4e4fe7d9448d3f2a352/chromadb-1.5.9-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60701011b5e6409647fa40d12c7c5a66b2b0bfcf33a52db2ad53a30a2abc4957", size = 22564540, upload-time = "2026-05-05T05:54:48.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/4c/adcef1f4e82a2ef69ccd3711d55fc289193d54c4c0ff7a0292a3631db46f/chromadb-1.5.9-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:814b9c95617377f6501e5757d63dfddb554a283a7739c87b9fa573850174e6f3", size = 21699698, upload-time = "2026-05-05T05:54:45.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/4e/937bc4d2e6f8ab9664ec79931fbbd69efff47e513ec2924b071e4b0ff774/chromadb-1.5.9-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9192d111bd662241625867962333d99369a00769a50f8b2f58cb388731274d7e", size = 22680924, upload-time = "2026-05-05T05:54:36.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ec/0c42039e80b9acc534f67b73b7a42471948042859b3a64867b50a4a77fa3/chromadb-1.5.9-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc09b3df76e5a5cb386aed2715a2eea152e3949f9e1ba93c7119505377749929", size = 23316203, upload-time = "2026-05-05T05:54:41.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/ce/0f7be6e5d0feafa2cda54b12e6542afeea7dea89d2d411e14da90f8abb96/chromadb-1.5.9-cp39-abi3-win_amd64.whl", hash = "sha256:4fd0b560e56761b7f3cb4d5c6205fd5f20814484b4a3e4e9af9038c2b428fc6c", size = 23542454, upload-time = "2026-05-05T05:54:54.942Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1430,7 +1430,7 @@ requires-dist = [
|
||||
{ name = "boto3", marker = "extra == 'aws'", specifier = "~=1.42.90" },
|
||||
{ name = "boto3", marker = "extra == 'bedrock'", specifier = "~=1.42.90" },
|
||||
{ name = "cel-python", specifier = ">=0.5.0,<0.6" },
|
||||
{ name = "chromadb", specifier = "~=1.1.0" },
|
||||
{ name = "chromadb", specifier = "~=1.5.9" },
|
||||
{ name = "click", specifier = ">=8.1.7,<9" },
|
||||
{ name = "crewai-cli", editable = "lib/cli" },
|
||||
{ name = "crewai-core", editable = "lib/crewai-core" },
|
||||
|
||||
Reference in New Issue
Block a user