diff --git a/docs/ar/concepts/agents.mdx b/docs/ar/concepts/agents.mdx index fe11b2545..7ae5c668c 100644 --- a/docs/ar/concepts/agents.mdx +++ b/docs/ar/concepts/agents.mdx @@ -250,16 +250,12 @@ analysis_agent = Agent( #### تنفيذ الكود -- `allow_code_execution`: يجب أن يكون True لتشغيل الكود -- `code_execution_mode`: - - `"safe"`: يستخدم Docker (موصى به للإنتاج) - - `"unsafe"`: تنفيذ مباشر (استخدم فقط في بيئات موثوقة) + + `allow_code_execution` و`code_execution_mode` مهجوران. تمت إزالة `CodeInterpreterTool` من `crewai-tools`. استخدم خدمة بيئة معزولة مخصصة مثل [E2B](https://e2b.dev) أو [Modal](https://modal.com) لتنفيذ الكود بأمان. + - - يشغّل هذا صورة Docker افتراضية. إذا أردت تهيئة صورة Docker، - راجع أداة Code Interpreter في قسم الأدوات. أضف أداة - مفسر الكود كأداة في معامل أداة الوكيل. - +- `allow_code_execution` _(مهجور)_: كان يُمكّن تنفيذ الكود المدمج عبر `CodeInterpreterTool`. +- `code_execution_mode` _(مهجور)_: كان يتحكم في وضع التنفيذ (`"safe"` لـ Docker، `"unsafe"` للتنفيذ المباشر). #### الميزات المتقدمة @@ -332,9 +328,9 @@ print(result.raw) ### الأمان وتنفيذ الكود -- عند استخدام `allow_code_execution`، كن حذرًا مع مدخلات المستخدم وتحقق منها دائمًا -- استخدم `code_execution_mode: "safe"` (Docker) في بيئات الإنتاج -- فكّر في تعيين حدود `max_execution_time` مناسبة لمنع الحلقات اللانهائية + + `allow_code_execution` و`code_execution_mode` مهجوران وتمت إزالة `CodeInterpreterTool`. استخدم خدمة بيئة معزولة مخصصة مثل [E2B](https://e2b.dev) أو [Modal](https://modal.com) لتنفيذ الكود بأمان. + ### تحسين الأداء diff --git a/docs/ar/tools/ai-ml/codeinterpretertool.mdx b/docs/ar/tools/ai-ml/codeinterpretertool.mdx index dbcf016eb..bbaea809b 100644 --- a/docs/ar/tools/ai-ml/codeinterpretertool.mdx +++ b/docs/ar/tools/ai-ml/codeinterpretertool.mdx @@ -7,6 +7,10 @@ mode: "wide" # `CodeInterpreterTool` + + **مهجور:** تمت إزالة `CodeInterpreterTool` من `crewai-tools`. كما أن معاملَي `allow_code_execution` و`code_execution_mode` على `Agent` أصبحا مهجورَين. استخدم خدمة بيئة معزولة مخصصة — [E2B](https://e2b.dev) أو [Modal](https://modal.com) — لتنفيذ الكود بشكل آمن ومعزول. + + ## الوصف تمكّن `CodeInterpreterTool` وكلاء CrewAI من تنفيذ كود Python 3 الذي يولّدونه بشكل مستقل. هذه الوظيفة ذات قيمة خاصة لأنها تتيح للوكلاء إنشاء الكود وتنفيذه والحصول على النتائج واستخدام تلك المعلومات لاتخاذ القرارات والإجراءات اللاحقة. diff --git a/docs/ar/tools/file-document/csvsearchtool.mdx b/docs/ar/tools/file-document/csvsearchtool.mdx index f9d5d7bf8..9e4e89658 100644 --- a/docs/ar/tools/file-document/csvsearchtool.mdx +++ b/docs/ar/tools/file-document/csvsearchtool.mdx @@ -74,3 +74,19 @@ tool = CSVSearchTool( } ) ``` + +## الأمان + +### التحقق من صحة المسارات + +يتم التحقق من مسارات الملفات المقدمة لهذه الأداة مقابل مجلد العمل الحالي. يتم رفض المسارات التي تحل خارج مجلد العمل وإطلاق `ValueError`. + +للسماح بالمسارات خارج مجلد العمل (مثلاً في الاختبارات أو خطوط الأنابيب الموثوقة)، عيّن متغير البيئة التالي: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### التحقق من صحة الروابط + +يتم التحقق من مدخلات الروابط: يتم حظر مخطط `file://` والطلبات التي تستهدف نطاقات IP الخاصة أو المحجوزة لمنع هجمات تزوير الطلبات من جانب الخادم (SSRF). diff --git a/docs/ar/tools/file-document/directorysearchtool.mdx b/docs/ar/tools/file-document/directorysearchtool.mdx index 2e5595865..577836ad9 100644 --- a/docs/ar/tools/file-document/directorysearchtool.mdx +++ b/docs/ar/tools/file-document/directorysearchtool.mdx @@ -68,3 +68,15 @@ tool = DirectorySearchTool( } ) ``` + +## الأمان + +### التحقق من صحة المسارات + +يتم التحقق من مسارات المجلدات المقدمة لهذه الأداة مقابل مجلد العمل الحالي. يتم رفض المسارات التي تحل خارج مجلد العمل وإطلاق `ValueError`. + +للسماح بالمسارات خارج مجلد العمل (مثلاً في الاختبارات أو خطوط الأنابيب الموثوقة)، عيّن متغير البيئة التالي: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` diff --git a/docs/ar/tools/file-document/jsonsearchtool.mdx b/docs/ar/tools/file-document/jsonsearchtool.mdx index 62ef99081..53aebacea 100644 --- a/docs/ar/tools/file-document/jsonsearchtool.mdx +++ b/docs/ar/tools/file-document/jsonsearchtool.mdx @@ -73,3 +73,19 @@ tool = JSONSearchTool( } ) ``` + +## الأمان + +### التحقق من صحة المسارات + +يتم التحقق من مسارات الملفات المقدمة لهذه الأداة مقابل مجلد العمل الحالي. يتم رفض المسارات التي تحل خارج مجلد العمل وإطلاق `ValueError`. + +للسماح بالمسارات خارج مجلد العمل (مثلاً في الاختبارات أو خطوط الأنابيب الموثوقة)، عيّن متغير البيئة التالي: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### التحقق من صحة الروابط + +يتم التحقق من مدخلات الروابط: يتم حظر مخطط `file://` والطلبات التي تستهدف نطاقات IP الخاصة أو المحجوزة لمنع هجمات تزوير الطلبات من جانب الخادم (SSRF). diff --git a/docs/ar/tools/file-document/pdfsearchtool.mdx b/docs/ar/tools/file-document/pdfsearchtool.mdx index 86e0272ad..96d4b98ba 100644 --- a/docs/ar/tools/file-document/pdfsearchtool.mdx +++ b/docs/ar/tools/file-document/pdfsearchtool.mdx @@ -105,3 +105,19 @@ tool = PDFSearchTool( } ) ``` + +## الأمان + +### التحقق من صحة المسارات + +يتم التحقق من مسارات الملفات المقدمة لهذه الأداة مقابل مجلد العمل الحالي. يتم رفض المسارات التي تحل خارج مجلد العمل وإطلاق `ValueError`. + +للسماح بالمسارات خارج مجلد العمل (مثلاً في الاختبارات أو خطوط الأنابيب الموثوقة)، عيّن متغير البيئة التالي: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### التحقق من صحة الروابط + +يتم التحقق من مدخلات الروابط: يتم حظر مخطط `file://` والطلبات التي تستهدف نطاقات IP الخاصة أو المحجوزة لمنع هجمات تزوير الطلبات من جانب الخادم (SSRF). diff --git a/docs/en/concepts/agents.mdx b/docs/en/concepts/agents.mdx index 5240c5a9f..ffd1a7ec6 100644 --- a/docs/en/concepts/agents.mdx +++ b/docs/en/concepts/agents.mdx @@ -308,16 +308,12 @@ multimodal_agent = Agent( #### Code Execution -- `allow_code_execution`: Must be True to run code -- `code_execution_mode`: - - `"safe"`: Uses Docker (recommended for production) - - `"unsafe"`: Direct execution (use only in trusted environments) + + `allow_code_execution` and `code_execution_mode` are deprecated. `CodeInterpreterTool` has been removed from `crewai-tools`. Use a dedicated sandbox service such as [E2B](https://e2b.dev) or [Modal](https://modal.com) for secure code execution. + - - This runs a default Docker image. If you want to configure the docker image, - the checkout the Code Interpreter Tool in the tools section. Add the code - interpreter tool as a tool in the agent as a tool parameter. - +- `allow_code_execution` _(deprecated)_: Previously enabled built-in code execution via `CodeInterpreterTool`. +- `code_execution_mode` _(deprecated)_: Previously controlled execution mode (`"safe"` for Docker, `"unsafe"` for direct execution). #### Advanced Features @@ -667,9 +663,9 @@ asyncio.run(main()) ### Security and Code Execution -- When using `allow_code_execution`, be cautious with user input and always validate it -- Use `code_execution_mode: "safe"` (Docker) in production environments -- Consider setting appropriate `max_execution_time` limits to prevent infinite loops + + `allow_code_execution` and `code_execution_mode` are deprecated and `CodeInterpreterTool` has been removed. Use a dedicated sandbox service such as [E2B](https://e2b.dev) or [Modal](https://modal.com) for secure code execution. + ### Performance Optimization diff --git a/docs/en/tools/ai-ml/codeinterpretertool.mdx b/docs/en/tools/ai-ml/codeinterpretertool.mdx index 67d371178..660c98a60 100644 --- a/docs/en/tools/ai-ml/codeinterpretertool.mdx +++ b/docs/en/tools/ai-ml/codeinterpretertool.mdx @@ -7,6 +7,10 @@ mode: "wide" # `CodeInterpreterTool` + + **Deprecated:** `CodeInterpreterTool` has been removed from `crewai-tools`. The `allow_code_execution` and `code_execution_mode` parameters on `Agent` are also deprecated. Use a dedicated sandbox service — [E2B](https://e2b.dev) or [Modal](https://modal.com) — for secure, isolated code execution. + + ## Description The `CodeInterpreterTool` enables CrewAI agents to execute Python 3 code that they generate autonomously. This functionality is particularly valuable as it allows agents to create code, execute it, obtain the results, and utilize that information to inform subsequent decisions and actions. diff --git a/docs/en/tools/file-document/csvsearchtool.mdx b/docs/en/tools/file-document/csvsearchtool.mdx index c20f8ec74..ebcfad583 100644 --- a/docs/en/tools/file-document/csvsearchtool.mdx +++ b/docs/en/tools/file-document/csvsearchtool.mdx @@ -75,4 +75,20 @@ tool = CSVSearchTool( }, } ) + +## Security + +### Path Validation + +File paths provided to this tool are validated against the current working directory. Paths that resolve outside the working directory are rejected with a `ValueError`. + +To allow paths outside the working directory (for example, in tests or trusted pipelines), set the environment variable: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### URL Validation + +URL inputs are validated: `file://` URIs and requests targeting private or reserved IP ranges are blocked to prevent server-side request forgery (SSRF) attacks. ``` \ No newline at end of file diff --git a/docs/en/tools/file-document/directorysearchtool.mdx b/docs/en/tools/file-document/directorysearchtool.mdx index 9efd2e910..c6bd537e4 100644 --- a/docs/en/tools/file-document/directorysearchtool.mdx +++ b/docs/en/tools/file-document/directorysearchtool.mdx @@ -67,4 +67,16 @@ tool = DirectorySearchTool( }, } ) + +## Security + +### Path Validation + +Directory paths provided to this tool are validated against the current working directory. Paths that resolve outside the working directory are rejected with a `ValueError`. + +To allow paths outside the working directory (for example, in tests or trusted pipelines), set the environment variable: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` ``` \ No newline at end of file diff --git a/docs/en/tools/file-document/jsonsearchtool.mdx b/docs/en/tools/file-document/jsonsearchtool.mdx index 7b1737faa..2ef8e95b4 100644 --- a/docs/en/tools/file-document/jsonsearchtool.mdx +++ b/docs/en/tools/file-document/jsonsearchtool.mdx @@ -74,3 +74,19 @@ tool = JSONSearchTool( } ) ``` + +## Security + +### Path Validation + +File paths provided to this tool are validated against the current working directory. Paths that resolve outside the working directory are rejected with a `ValueError`. + +To allow paths outside the working directory (for example, in tests or trusted pipelines), set the environment variable: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### URL Validation + +URL inputs are validated: `file://` URIs and requests targeting private or reserved IP ranges are blocked to prevent server-side request forgery (SSRF) attacks. diff --git a/docs/en/tools/file-document/pdfsearchtool.mdx b/docs/en/tools/file-document/pdfsearchtool.mdx index 32e05669e..d8c812f2d 100644 --- a/docs/en/tools/file-document/pdfsearchtool.mdx +++ b/docs/en/tools/file-document/pdfsearchtool.mdx @@ -105,4 +105,20 @@ tool = PDFSearchTool( }, } ) + +## Security + +### Path Validation + +File paths provided to this tool are validated against the current working directory. Paths that resolve outside the working directory are rejected with a `ValueError`. + +To allow paths outside the working directory (for example, in tests or trusted pipelines), set the environment variable: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### URL Validation + +URL inputs are validated: `file://` URIs and requests targeting private or reserved IP ranges are blocked to prevent server-side request forgery (SSRF) attacks. ``` \ No newline at end of file diff --git a/docs/ko/concepts/agents.mdx b/docs/ko/concepts/agents.mdx index 21bebbb82..09d3431fc 100644 --- a/docs/ko/concepts/agents.mdx +++ b/docs/ko/concepts/agents.mdx @@ -291,15 +291,13 @@ multimodal_agent = Agent( - `max_retry_limit`: 오류 발생 시 재시도 횟수 #### 코드 실행 -- `allow_code_execution`: 코드를 실행하려면 True여야 합니다 -- `code_execution_mode`: - - `"safe"`: Docker를 사용합니다 (프로덕션에 권장) - - `"unsafe"`: 직접 실행 (신뢰할 수 있는 환경에서만 사용) - - 이 옵션은 기본 Docker 이미지를 실행합니다. Docker 이미지를 구성하려면 도구 섹션에 있는 Code Interpreter Tool을 확인하십시오. - Code Interpreter Tool을 에이전트의 도구 파라미터로 추가하십시오. - + + `allow_code_execution` 및 `code_execution_mode`는 더 이상 사용되지 않습니다. `CodeInterpreterTool`이 `crewai-tools`에서 제거되었습니다. 안전한 코드 실행을 위해 [E2B](https://e2b.dev) 또는 [Modal](https://modal.com)과 같은 전용 샌드박스 서비스를 사용하세요. + + +- `allow_code_execution` _(지원 중단)_: 이전에 `CodeInterpreterTool`을 통한 내장 코드 실행을 활성화했습니다. +- `code_execution_mode` _(지원 중단)_: 이전에 실행 모드를 제어했습니다 (Docker의 경우 `"safe"`, 직접 실행의 경우 `"unsafe"`). #### 고급 기능 - `multimodal`: 텍스트와 시각적 콘텐츠 처리를 위한 멀티모달 기능 활성화 @@ -627,9 +625,10 @@ asyncio.run(main()) ## 중요한 고려사항 및 모범 사례 ### 보안 및 코드 실행 -- `allow_code_execution`을 사용할 때는 사용자 입력에 주의하고 항상 입력 값을 검증하세요 -- 운영 환경에서는 `code_execution_mode: "safe"`(Docker)를 사용하세요 -- 무한 루프를 방지하기 위해 적절한 `max_execution_time` 제한을 설정하는 것을 고려하세요 + + + `allow_code_execution` 및 `code_execution_mode`는 더 이상 사용되지 않으며 `CodeInterpreterTool`이 제거되었습니다. 안전한 코드 실행을 위해 [E2B](https://e2b.dev) 또는 [Modal](https://modal.com)과 같은 전용 샌드박스 서비스를 사용하세요. + ### 성능 최적화 - `respect_context_window: true`를 사용하여 토큰 제한 문제를 방지하세요. diff --git a/docs/ko/tools/ai-ml/codeinterpretertool.mdx b/docs/ko/tools/ai-ml/codeinterpretertool.mdx index f5053d216..1b2ec234e 100644 --- a/docs/ko/tools/ai-ml/codeinterpretertool.mdx +++ b/docs/ko/tools/ai-ml/codeinterpretertool.mdx @@ -7,6 +7,10 @@ mode: "wide" # `CodeInterpreterTool` + + **지원 중단:** `CodeInterpreterTool`이 `crewai-tools`에서 제거되었습니다. `Agent`의 `allow_code_execution` 및 `code_execution_mode` 파라미터도 더 이상 사용되지 않습니다. 안전하고 격리된 코드 실행을 위해 전용 샌드박스 서비스 — [E2B](https://e2b.dev) 또는 [Modal](https://modal.com) — 을 사용하세요. + + ## 설명 `CodeInterpreterTool`은 CrewAI 에이전트가 자율적으로 생성한 Python 3 코드를 실행할 수 있도록 합니다. 이 기능은 에이전트가 코드를 생성하고, 실행하며, 결과를 얻고, 그 정보를 활용하여 이후의 결정과 행동에 반영할 수 있다는 점에서 특히 유용합니다. diff --git a/docs/ko/tools/file-document/csvsearchtool.mdx b/docs/ko/tools/file-document/csvsearchtool.mdx index e962b11e1..99de2cdda 100644 --- a/docs/ko/tools/file-document/csvsearchtool.mdx +++ b/docs/ko/tools/file-document/csvsearchtool.mdx @@ -76,3 +76,19 @@ tool = CSVSearchTool( } ) ``` + +## 보안 + +### 경로 유효성 검사 + +이 도구에 제공되는 파일 경로는 현재 작업 디렉터리에 대해 검증됩니다. 작업 디렉터리 외부로 확인되는 경로는 `ValueError`로 거부됩니다. + +작업 디렉터리 외부의 경로를 허용하려면 (예: 테스트 또는 신뢰할 수 있는 파이프라인), 다음 환경 변수를 설정하세요: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### URL 유효성 검사 + +URL 입력도 검증됩니다: `file://` URI와 사설 또는 예약된 IP 범위를 대상으로 하는 요청은 서버 측 요청 위조(SSRF) 공격을 방지하기 위해 차단됩니다. diff --git a/docs/ko/tools/file-document/directorysearchtool.mdx b/docs/ko/tools/file-document/directorysearchtool.mdx index 5a46e53b7..4f9becef5 100644 --- a/docs/ko/tools/file-document/directorysearchtool.mdx +++ b/docs/ko/tools/file-document/directorysearchtool.mdx @@ -68,3 +68,15 @@ tool = DirectorySearchTool( } ) ``` + +## 보안 + +### 경로 유효성 검사 + +이 도구에 제공되는 디렉터리 경로는 현재 작업 디렉터리에 대해 검증됩니다. 작업 디렉터리 외부로 확인되는 경로는 `ValueError`로 거부됩니다. + +작업 디렉터리 외부의 경로를 허용하려면 (예: 테스트 또는 신뢰할 수 있는 파이프라인), 다음 환경 변수를 설정하세요: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` diff --git a/docs/ko/tools/file-document/jsonsearchtool.mdx b/docs/ko/tools/file-document/jsonsearchtool.mdx index be0a6f134..3b4a60931 100644 --- a/docs/ko/tools/file-document/jsonsearchtool.mdx +++ b/docs/ko/tools/file-document/jsonsearchtool.mdx @@ -71,3 +71,19 @@ tool = JSONSearchTool( } ) ``` + +## 보안 + +### 경로 유효성 검사 + +이 도구에 제공되는 파일 경로는 현재 작업 디렉터리에 대해 검증됩니다. 작업 디렉터리 외부로 확인되는 경로는 `ValueError`로 거부됩니다. + +작업 디렉터리 외부의 경로를 허용하려면 (예: 테스트 또는 신뢰할 수 있는 파이프라인), 다음 환경 변수를 설정하세요: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### URL 유효성 검사 + +URL 입력도 검증됩니다: `file://` URI와 사설 또는 예약된 IP 범위를 대상으로 하는 요청은 서버 측 요청 위조(SSRF) 공격을 방지하기 위해 차단됩니다. diff --git a/docs/ko/tools/file-document/pdfsearchtool.mdx b/docs/ko/tools/file-document/pdfsearchtool.mdx index 573ed4812..f9cf622d5 100644 --- a/docs/ko/tools/file-document/pdfsearchtool.mdx +++ b/docs/ko/tools/file-document/pdfsearchtool.mdx @@ -102,3 +102,19 @@ tool = PDFSearchTool( } ) ``` + +## 보안 + +### 경로 유효성 검사 + +이 도구에 제공되는 파일 경로는 현재 작업 디렉터리에 대해 검증됩니다. 작업 디렉터리 외부로 확인되는 경로는 `ValueError`로 거부됩니다. + +작업 디렉터리 외부의 경로를 허용하려면 (예: 테스트 또는 신뢰할 수 있는 파이프라인), 다음 환경 변수를 설정하세요: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### URL 유효성 검사 + +URL 입력도 검증됩니다: `file://` URI와 사설 또는 예약된 IP 범위를 대상으로 하는 요청은 서버 측 요청 위조(SSRF) 공격을 방지하기 위해 차단됩니다. diff --git a/docs/pt-BR/concepts/agents.mdx b/docs/pt-BR/concepts/agents.mdx index 383d501c6..69cb2e9d4 100644 --- a/docs/pt-BR/concepts/agents.mdx +++ b/docs/pt-BR/concepts/agents.mdx @@ -304,17 +304,12 @@ multimodal_agent = Agent( #### Execução de Código -- `allow_code_execution`: Deve ser True para permitir execução de código -- `code_execution_mode`: - - `"safe"`: Usa Docker (recomendado para produção) - - `"unsafe"`: Execução direta (apenas em ambientes confiáveis) + + `allow_code_execution` e `code_execution_mode` estão depreciados. O `CodeInterpreterTool` foi removido do `crewai-tools`. Use um serviço de sandbox dedicado como [E2B](https://e2b.dev) ou [Modal](https://modal.com) para execução segura de código. + - - Isso executa uma imagem Docker padrão. Se você deseja configurar a imagem - Docker, veja a ferramenta Code Interpreter na seção de ferramentas. Adicione a - ferramenta de interpretação de código como um parâmetro em ferramentas no - agente. - +- `allow_code_execution` _(depreciado)_: Anteriormente habilitava a execução de código embutida via `CodeInterpreterTool`. +- `code_execution_mode` _(depreciado)_: Anteriormente controlava o modo de execução (`"safe"` para Docker, `"unsafe"` para execução direta). #### Funcionalidades Avançadas @@ -565,9 +560,9 @@ agent = Agent( ### Segurança e Execução de Código -- Ao usar `allow_code_execution`, seja cauteloso com entradas do usuário e sempre as valide -- Use `code_execution_mode: "safe"` (Docker) em ambientes de produção -- Considere definir limites adequados de `max_execution_time` para evitar loops infinitos + + `allow_code_execution` e `code_execution_mode` estão depreciados e o `CodeInterpreterTool` foi removido. Use um serviço de sandbox dedicado como [E2B](https://e2b.dev) ou [Modal](https://modal.com) para execução segura de código. + ### Otimização de Performance diff --git a/docs/pt-BR/tools/ai-ml/codeinterpretertool.mdx b/docs/pt-BR/tools/ai-ml/codeinterpretertool.mdx index 14c4fd51d..9b48a51e4 100644 --- a/docs/pt-BR/tools/ai-ml/codeinterpretertool.mdx +++ b/docs/pt-BR/tools/ai-ml/codeinterpretertool.mdx @@ -7,6 +7,10 @@ mode: "wide" # `CodeInterpreterTool` + + **Depreciado:** O `CodeInterpreterTool` foi removido do `crewai-tools`. Os parâmetros `allow_code_execution` e `code_execution_mode` do `Agent` também estão depreciados. Use um serviço de sandbox dedicado — [E2B](https://e2b.dev) ou [Modal](https://modal.com) — para execução de código segura e isolada. + + ## Descrição O `CodeInterpreterTool` permite que agentes CrewAI executem códigos Python 3 gerados autonomamente. Essa funcionalidade é particularmente valiosa, pois permite que os agentes criem códigos, os executem, obtenham os resultados e usem essas informações para orientar decisões e ações subsequentes. diff --git a/docs/pt-BR/tools/file-document/csvsearchtool.mdx b/docs/pt-BR/tools/file-document/csvsearchtool.mdx index a2ebd3af7..59a07b3ea 100644 --- a/docs/pt-BR/tools/file-document/csvsearchtool.mdx +++ b/docs/pt-BR/tools/file-document/csvsearchtool.mdx @@ -75,4 +75,20 @@ tool = CSVSearchTool( ), ) ) + +## Segurança + +### Validação de Caminhos + +Os caminhos de arquivo fornecidos a esta ferramenta são validados em relação ao diretório de trabalho atual. Caminhos que resolvem fora do diretório de trabalho são rejeitados com um `ValueError`. + +Para permitir caminhos fora do diretório de trabalho (por exemplo, em testes ou pipelines confiáveis), defina a variável de ambiente: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### Validação de URLs + +Entradas de URL também são validadas: URIs `file://` e requisições direcionadas a faixas de IP privadas ou reservadas são bloqueadas para prevenir ataques de falsificação de requisições do lado do servidor (SSRF). ``` \ No newline at end of file diff --git a/docs/pt-BR/tools/file-document/directorysearchtool.mdx b/docs/pt-BR/tools/file-document/directorysearchtool.mdx index 4093bbc8e..50685ff58 100644 --- a/docs/pt-BR/tools/file-document/directorysearchtool.mdx +++ b/docs/pt-BR/tools/file-document/directorysearchtool.mdx @@ -67,4 +67,16 @@ tool = DirectorySearchTool( }, } ) +``` + +## Segurança + +### Validação de Caminhos + +Os caminhos de diretório fornecidos a esta ferramenta são validados em relação ao diretório de trabalho atual. Caminhos que resolvem fora do diretório de trabalho são rejeitados com um `ValueError`. + +Para permitir caminhos fora do diretório de trabalho (por exemplo, em testes ou pipelines confiáveis), defina a variável de ambiente: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true ``` \ No newline at end of file diff --git a/docs/pt-BR/tools/file-document/jsonsearchtool.mdx b/docs/pt-BR/tools/file-document/jsonsearchtool.mdx index 11b76044b..ec75920e5 100644 --- a/docs/pt-BR/tools/file-document/jsonsearchtool.mdx +++ b/docs/pt-BR/tools/file-document/jsonsearchtool.mdx @@ -73,4 +73,20 @@ tool = JSONSearchTool( }, } ) + +## Segurança + +### Validação de Caminhos + +Os caminhos de arquivo fornecidos a esta ferramenta são validados em relação ao diretório de trabalho atual. Caminhos que resolvem fora do diretório de trabalho são rejeitados com um `ValueError`. + +Para permitir caminhos fora do diretório de trabalho (por exemplo, em testes ou pipelines confiáveis), defina a variável de ambiente: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### Validação de URLs + +Entradas de URL também são validadas: URIs `file://` e requisições direcionadas a faixas de IP privadas ou reservadas são bloqueadas para prevenir ataques de falsificação de requisições do lado do servidor (SSRF). ``` \ No newline at end of file diff --git a/docs/pt-BR/tools/file-document/pdfsearchtool.mdx b/docs/pt-BR/tools/file-document/pdfsearchtool.mdx index 83cac48bb..f547ec80a 100644 --- a/docs/pt-BR/tools/file-document/pdfsearchtool.mdx +++ b/docs/pt-BR/tools/file-document/pdfsearchtool.mdx @@ -101,4 +101,20 @@ tool = PDFSearchTool( }, } ) -``` \ No newline at end of file +``` + +## Segurança + +### Validação de Caminhos + +Os caminhos de arquivo fornecidos a esta ferramenta são validados em relação ao diretório de trabalho atual. Caminhos que resolvem fora do diretório de trabalho são rejeitados com um `ValueError`. + +Para permitir caminhos fora do diretório de trabalho (por exemplo, em testes ou pipelines confiáveis), defina a variável de ambiente: + +```shell +CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true +``` + +### Validação de URLs + +Entradas de URL também são validadas: URIs `file://` e requisições direcionadas a faixas de IP privadas ou reservadas são bloqueadas para prevenir ataques de falsificação de requisições do lado do servidor (SSRF). \ No newline at end of file diff --git a/lib/crewai-tools/src/crewai_tools/rag/data_types.py b/lib/crewai-tools/src/crewai_tools/rag/data_types.py index 09d519ce9..2ab62f20f 100644 --- a/lib/crewai-tools/src/crewai_tools/rag/data_types.py +++ b/lib/crewai-tools/src/crewai_tools/rag/data_types.py @@ -109,7 +109,7 @@ class DataTypes: if isinstance(content, str): try: url = urlparse(content) - is_url = bool(url.scheme and url.netloc) or url.scheme == "file" + is_url = bool(url.scheme in ("http", "https") and url.netloc) except Exception: # noqa: S110 pass diff --git a/lib/crewai-tools/src/crewai_tools/tools/directory_search_tool/directory_search_tool.py b/lib/crewai-tools/src/crewai_tools/tools/directory_search_tool/directory_search_tool.py index d218188e7..f17c4699a 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/directory_search_tool/directory_search_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/directory_search_tool/directory_search_tool.py @@ -4,6 +4,7 @@ from pydantic import BaseModel, Field from crewai_tools.rag.data_types import DataType from crewai_tools.tools.rag.rag_tool import RagTool +from crewai_tools.utilities.safe_path import validate_directory_path class FixedDirectorySearchToolSchema(BaseModel): @@ -37,6 +38,7 @@ class DirectorySearchTool(RagTool): self._generate_description() def add(self, directory: str) -> None: # type: ignore[override] + validate_directory_path(directory) super().add(directory, data_type=DataType.DIRECTORY) def _run( # type: ignore[override] diff --git a/lib/crewai-tools/src/crewai_tools/tools/rag/rag_tool.py b/lib/crewai-tools/src/crewai_tools/tools/rag/rag_tool.py index 52fc903e9..eb7e9cefd 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/rag/rag_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/rag/rag_tool.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +import os from typing import Any, Literal, cast from crewai.rag.core.base_embeddings_callable import EmbeddingFunction @@ -246,7 +247,83 @@ class RagTool(BaseTool): # Auto-detect type from extension rag_tool.add("path/to/document.pdf") # auto-detects PDF """ - self.adapter.add(*args, **kwargs) + # Validate file paths and URLs before adding to prevent + # unauthorized file reads and SSRF. + from urllib.parse import urlparse + + from crewai_tools.utilities.safe_path import validate_file_path, validate_url + + def _check_url(value: str, label: str) -> None: + try: + validate_url(value) + except ValueError as e: + raise ValueError(f"Blocked unsafe {label}: {e}") from e + + def _check_path(value: str, label: str) -> None: + try: + validate_file_path(value) + except ValueError as e: + raise ValueError(f"Blocked unsafe {label}: {e}") from e + + validated_args: list[ContentItem] = [] + for arg in args: + source_ref = ( + str(arg.get("source", arg.get("content", ""))) + if isinstance(arg, dict) + else str(arg) + ) + + # Check if it's a URL — only catch urlparse-specific errors here; + # validate_url's ValueError must propagate so it is never silently bypassed. + try: + parsed = urlparse(source_ref) + except (ValueError, AttributeError): + parsed = None + + if parsed is not None and parsed.scheme in ("http", "https", "file"): + try: + validate_url(source_ref) + except ValueError as e: + raise ValueError(f"Blocked unsafe URL: {e}") from e + validated_args.append(arg) + continue + + # Check if it looks like a file path (not a plain text string). + # Check both os.sep (backslash on Windows) and "/" so that + # forward-slash paths like "sub/file.txt" are caught on all platforms. + if ( + os.path.sep in source_ref + or "/" in source_ref + or source_ref.startswith(".") + or os.path.isabs(source_ref) + ): + try: + validate_file_path(source_ref) + except ValueError as e: + raise ValueError(f"Blocked unsafe file path: {e}") from e + + validated_args.append(arg) + + # Validate keyword path/URL arguments — these are equally user-controlled + # and must not bypass the checks applied to positional args. + if "path" in kwargs and kwargs.get("path") is not None: + _check_path(str(kwargs["path"]), "path") + if "file_path" in kwargs and kwargs.get("file_path") is not None: + _check_path(str(kwargs["file_path"]), "file_path") + + if "directory_path" in kwargs and kwargs.get("directory_path") is not None: + _check_path(str(kwargs["directory_path"]), "directory_path") + + if "url" in kwargs and kwargs.get("url") is not None: + _check_url(str(kwargs["url"]), "url") + if "website" in kwargs and kwargs.get("website") is not None: + _check_url(str(kwargs["website"]), "website") + if "github_url" in kwargs and kwargs.get("github_url") is not None: + _check_url(str(kwargs["github_url"]), "github_url") + if "youtube_url" in kwargs and kwargs.get("youtube_url") is not None: + _check_url(str(kwargs["youtube_url"]), "youtube_url") + + self.adapter.add(*validated_args, **kwargs) def _run( self, diff --git a/lib/crewai-tools/src/crewai_tools/utilities/__init__.py b/lib/crewai-tools/src/crewai_tools/utilities/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/crewai-tools/src/crewai_tools/utilities/safe_path.py b/lib/crewai-tools/src/crewai_tools/utilities/safe_path.py new file mode 100644 index 000000000..4dde68e12 --- /dev/null +++ b/lib/crewai-tools/src/crewai_tools/utilities/safe_path.py @@ -0,0 +1,205 @@ +"""Path and URL validation utilities for crewai-tools. + +Provides validation for file paths and URLs to prevent unauthorized +file access and server-side request forgery (SSRF) when tools accept +user-controlled or LLM-controlled inputs at runtime. + +Set CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true to bypass validation (not +recommended for production). +""" + +from __future__ import annotations + +import ipaddress +import logging +import os +import socket +from urllib.parse import urlparse + + +logger = logging.getLogger(__name__) + +_UNSAFE_PATHS_ENV = "CREWAI_TOOLS_ALLOW_UNSAFE_PATHS" + + +def _is_escape_hatch_enabled() -> bool: + """Check if the unsafe paths escape hatch is enabled.""" + return os.environ.get(_UNSAFE_PATHS_ENV, "").lower() in ("true", "1", "yes") + + +# --------------------------------------------------------------------------- +# File path validation +# --------------------------------------------------------------------------- + + +def validate_file_path(path: str, base_dir: str | None = None) -> str: + """Validate that a file path is safe to read. + + Resolves symlinks and ``..`` components, then checks that the resolved + path falls within *base_dir* (defaults to the current working directory). + + Args: + path: The file path to validate. + base_dir: Allowed root directory. Defaults to ``os.getcwd()``. + + Returns: + The resolved, validated absolute path. + + Raises: + ValueError: If the path escapes the allowed directory. + """ + if _is_escape_hatch_enabled(): + logger.warning( + "%s is enabled — skipping file path validation for: %s", + _UNSAFE_PATHS_ENV, + path, + ) + return os.path.realpath(path) + + if base_dir is None: + base_dir = os.getcwd() + + resolved_base = os.path.realpath(base_dir) + resolved_path = os.path.realpath( + os.path.join(resolved_base, path) if not os.path.isabs(path) else path + ) + + # Ensure the resolved path is within the base directory. + # When resolved_base already ends with a separator (e.g. the filesystem + # root "/"), appending os.sep would double it ("//"), so use the base + # as-is in that case. + prefix = resolved_base if resolved_base.endswith(os.sep) else resolved_base + os.sep + if not resolved_path.startswith(prefix) and resolved_path != resolved_base: + raise ValueError( + f"Path '{path}' resolves to '{resolved_path}' which is outside " + f"the allowed directory '{resolved_base}'. " + f"Set {_UNSAFE_PATHS_ENV}=true to bypass this check." + ) + + return resolved_path + + +def validate_directory_path(path: str, base_dir: str | None = None) -> str: + """Validate that a directory path is safe to read. + + Same as :func:`validate_file_path` but also checks that the path + is an existing directory. + + Args: + path: The directory path to validate. + base_dir: Allowed root directory. Defaults to ``os.getcwd()``. + + Returns: + The resolved, validated absolute path. + + Raises: + ValueError: If the path escapes the allowed directory or is not a directory. + """ + validated = validate_file_path(path, base_dir) + if not os.path.isdir(validated): + raise ValueError(f"Path '{validated}' is not a directory.") + return validated + + +# --------------------------------------------------------------------------- +# URL validation +# --------------------------------------------------------------------------- + +# Private and reserved IP ranges that should not be accessed +_BLOCKED_IPV4_NETWORKS = [ + ipaddress.ip_network("10.0.0.0/8"), + ipaddress.ip_network("172.16.0.0/12"), + ipaddress.ip_network("192.168.0.0/16"), + ipaddress.ip_network("127.0.0.0/8"), + ipaddress.ip_network("169.254.0.0/16"), # Link-local / cloud metadata + ipaddress.ip_network("0.0.0.0/32"), +] + +_BLOCKED_IPV6_NETWORKS = [ + ipaddress.ip_network("::1/128"), + ipaddress.ip_network("::/128"), + ipaddress.ip_network("fc00::/7"), # Unique local addresses + ipaddress.ip_network("fe80::/10"), # Link-local IPv6 +] + + +def _is_private_or_reserved(ip_str: str) -> bool: + """Check if an IP address is private, reserved, or otherwise unsafe.""" + try: + addr = ipaddress.ip_address(ip_str) + # Unwrap IPv4-mapped IPv6 addresses (e.g., ::ffff:127.0.0.1) to IPv4 + # so they are only checked against IPv4 networks (avoids TypeError when + # an IPv4Address is compared against an IPv6Network). + if isinstance(addr, ipaddress.IPv6Address) and addr.ipv4_mapped: + addr = addr.ipv4_mapped + networks = ( + _BLOCKED_IPV4_NETWORKS + if isinstance(addr, ipaddress.IPv4Address) + else _BLOCKED_IPV6_NETWORKS + ) + return any(addr in network for network in networks) + except ValueError: + return True # If we can't parse, block it + + +def validate_url(url: str) -> str: + """Validate that a URL is safe to fetch. + + Blocks ``file://`` scheme entirely. For ``http``/``https``, resolves + DNS and checks that the target IP is not private or reserved (prevents + SSRF to internal services and cloud metadata endpoints). + + Args: + url: The URL to validate. + + Returns: + The validated URL string. + + Raises: + ValueError: If the URL uses a blocked scheme or resolves to a + private/reserved IP address. + """ + if _is_escape_hatch_enabled(): + logger.warning( + "%s is enabled — skipping URL validation for: %s", + _UNSAFE_PATHS_ENV, + url, + ) + return url + + parsed = urlparse(url) + + # Block file:// scheme + if parsed.scheme == "file": + raise ValueError( + f"file:// URLs are not allowed: '{url}'. " + f"Use a file path instead, or set {_UNSAFE_PATHS_ENV}=true to bypass." + ) + + # Only allow http and https + if parsed.scheme not in ("http", "https"): + raise ValueError( + f"URL scheme '{parsed.scheme}' is not allowed. Only http and https are supported." + ) + + if not parsed.hostname: + raise ValueError(f"URL has no hostname: '{url}'") + + # Resolve DNS and check IPs + try: + addrinfos = socket.getaddrinfo( + parsed.hostname, parsed.port or (443 if parsed.scheme == "https" else 80) + ) + except socket.gaierror as exc: + raise ValueError(f"Could not resolve hostname: '{parsed.hostname}'") from exc + + for _family, _, _, _, sockaddr in addrinfos: + ip_str = str(sockaddr[0]) + if _is_private_or_reserved(ip_str): + raise ValueError( + f"URL '{url}' resolves to private/reserved IP {ip_str}. " + f"Access to internal networks is not allowed. " + f"Set {_UNSAFE_PATHS_ENV}=true to bypass." + ) + + return url diff --git a/lib/crewai-tools/tests/tools/rag/rag_tool_test.py b/lib/crewai-tools/tests/tools/rag/rag_tool_test.py index 48411699e..93896e8b2 100644 --- a/lib/crewai-tools/tests/tools/rag/rag_tool_test.py +++ b/lib/crewai-tools/tests/tools/rag/rag_tool_test.py @@ -3,10 +3,21 @@ from tempfile import TemporaryDirectory from typing import cast from unittest.mock import MagicMock, Mock, patch +import pytest + from crewai_tools.adapters.crewai_rag_adapter import CrewAIRagAdapter from crewai_tools.tools.rag.rag_tool import RagTool +@pytest.fixture(autouse=True) +def allow_tmp_paths(monkeypatch: pytest.MonkeyPatch) -> None: + """Allow absolute paths outside CWD (e.g. /tmp/) for these RagTool tests. + + Path validation is tested separately in test_rag_tool_path_validation.py. + """ + monkeypatch.setenv("CREWAI_TOOLS_ALLOW_UNSAFE_PATHS", "true") + + @patch("crewai_tools.adapters.crewai_rag_adapter.get_rag_client") @patch("crewai_tools.adapters.crewai_rag_adapter.create_client") def test_rag_tool_initialization( diff --git a/lib/crewai-tools/tests/tools/rag/test_rag_tool_add_data_type.py b/lib/crewai-tools/tests/tools/rag/test_rag_tool_add_data_type.py index 853e6ab00..d8304ee0f 100644 --- a/lib/crewai-tools/tests/tools/rag/test_rag_tool_add_data_type.py +++ b/lib/crewai-tools/tests/tools/rag/test_rag_tool_add_data_type.py @@ -10,6 +10,15 @@ from crewai_tools.rag.data_types import DataType from crewai_tools.tools.rag.rag_tool import RagTool +@pytest.fixture(autouse=True) +def allow_tmp_paths(monkeypatch: pytest.MonkeyPatch) -> None: + """Allow absolute paths outside CWD (e.g. /tmp/) for these data-type tests. + + Path validation is tested separately in test_rag_tool_path_validation.py. + """ + monkeypatch.setenv("CREWAI_TOOLS_ALLOW_UNSAFE_PATHS", "true") + + @pytest.fixture def mock_rag_client() -> MagicMock: """Create a mock RAG client for testing.""" diff --git a/lib/crewai-tools/tests/tools/rag/test_rag_tool_path_validation.py b/lib/crewai-tools/tests/tools/rag/test_rag_tool_path_validation.py new file mode 100644 index 000000000..a58cccde3 --- /dev/null +++ b/lib/crewai-tools/tests/tools/rag/test_rag_tool_path_validation.py @@ -0,0 +1,80 @@ +"""Tests for path and URL validation in RagTool.add() — both positional and keyword args.""" + +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +import pytest + +from crewai_tools.tools.rag.rag_tool import RagTool + + +@pytest.fixture() +def mock_rag_client() -> MagicMock: + mock_client = MagicMock() + mock_client.get_or_create_collection = MagicMock(return_value=None) + mock_client.add_documents = MagicMock(return_value=None) + mock_client.search = MagicMock(return_value=[]) + return mock_client + + +@pytest.fixture() +def tool(mock_rag_client: MagicMock) -> RagTool: + with ( + patch("crewai_tools.adapters.crewai_rag_adapter.get_rag_client", return_value=mock_rag_client), + patch("crewai_tools.adapters.crewai_rag_adapter.create_client", return_value=mock_rag_client), + ): + return RagTool() + + +# --------------------------------------------------------------------------- +# Positional arg validation (existing behaviour, regression guard) +# --------------------------------------------------------------------------- + +class TestPositionalArgValidation: + def test_blocks_traversal_in_positional_arg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe"): + tool.add("../../etc/passwd") + + def test_blocks_file_url_in_positional_arg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe"): + tool.add("file:///etc/passwd") + + +# --------------------------------------------------------------------------- +# Keyword arg validation (the newly fixed gap) +# --------------------------------------------------------------------------- + +class TestKwargPathValidation: + def test_blocks_traversal_via_path_kwarg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe path"): + tool.add(path="../../etc/passwd") + + def test_blocks_traversal_via_file_path_kwarg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe file_path"): + tool.add(file_path="/etc/passwd") + + def test_blocks_traversal_via_directory_path_kwarg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe directory_path"): + tool.add(directory_path="../../sensitive_dir") + + def test_blocks_file_url_via_url_kwarg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe url"): + tool.add(url="file:///etc/passwd") + + def test_blocks_private_ip_via_url_kwarg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe url"): + tool.add(url="http://169.254.169.254/latest/meta-data/") + + def test_blocks_private_ip_via_website_kwarg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe website"): + tool.add(website="http://192.168.1.1/") + + def test_blocks_file_url_via_github_url_kwarg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe github_url"): + tool.add(github_url="file:///etc/passwd") + + def test_blocks_file_url_via_youtube_url_kwarg(self, tool): + with pytest.raises(ValueError, match="Blocked unsafe youtube_url"): + tool.add(youtube_url="file:///etc/passwd") + diff --git a/lib/crewai-tools/tests/tools/test_search_tools.py b/lib/crewai-tools/tests/tools/test_search_tools.py index 52c08633f..533be1ea2 100644 --- a/lib/crewai-tools/tests/tools/test_search_tools.py +++ b/lib/crewai-tools/tests/tools/test_search_tools.py @@ -23,6 +23,15 @@ from crewai_tools.tools.rag.rag_tool import Adapter import pytest +@pytest.fixture(autouse=True) +def allow_tmp_paths(monkeypatch: pytest.MonkeyPatch) -> None: + """Allow absolute paths outside CWD (e.g. /tmp/) for these search-tool tests. + + Path validation is tested separately in test_rag_tool_path_validation.py. + """ + monkeypatch.setenv("CREWAI_TOOLS_ALLOW_UNSAFE_PATHS", "true") + + @pytest.fixture def mock_adapter(): mock_adapter = MagicMock(spec=Adapter) diff --git a/lib/crewai-tools/tests/utilities/__init__.py b/lib/crewai-tools/tests/utilities/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/crewai-tools/tests/utilities/test_safe_path.py b/lib/crewai-tools/tests/utilities/test_safe_path.py new file mode 100644 index 000000000..83e247292 --- /dev/null +++ b/lib/crewai-tools/tests/utilities/test_safe_path.py @@ -0,0 +1,170 @@ +"""Tests for path and URL validation utilities.""" + +from __future__ import annotations + +import os + +import pytest + +from crewai_tools.utilities.safe_path import ( + validate_directory_path, + validate_file_path, + validate_url, +) + + +# --------------------------------------------------------------------------- +# File path validation +# --------------------------------------------------------------------------- + +class TestValidateFilePath: + """Tests for validate_file_path.""" + + def test_valid_relative_path(self, tmp_path): + """Normal relative path within the base directory.""" + (tmp_path / "data.json").touch() + result = validate_file_path("data.json", str(tmp_path)) + assert result == str(tmp_path / "data.json") + + def test_valid_nested_path(self, tmp_path): + """Nested path within base directory.""" + (tmp_path / "sub").mkdir() + (tmp_path / "sub" / "file.txt").touch() + result = validate_file_path("sub/file.txt", str(tmp_path)) + assert result == str(tmp_path / "sub" / "file.txt") + + def test_rejects_dotdot_traversal(self, tmp_path): + """Reject ../ traversal that escapes base_dir.""" + with pytest.raises(ValueError, match="outside the allowed directory"): + validate_file_path("../../etc/passwd", str(tmp_path)) + + def test_rejects_absolute_path_outside_base(self, tmp_path): + """Reject absolute path outside base_dir.""" + with pytest.raises(ValueError, match="outside the allowed directory"): + validate_file_path("/etc/passwd", str(tmp_path)) + + def test_allows_absolute_path_inside_base(self, tmp_path): + """Allow absolute path that's inside base_dir.""" + (tmp_path / "ok.txt").touch() + result = validate_file_path(str(tmp_path / "ok.txt"), str(tmp_path)) + assert result == str(tmp_path / "ok.txt") + + def test_rejects_symlink_escape(self, tmp_path): + """Reject symlinks that point outside base_dir.""" + link = tmp_path / "sneaky_link" + # Create a symlink pointing to /etc/passwd + os.symlink("/etc/passwd", str(link)) + with pytest.raises(ValueError, match="outside the allowed directory"): + validate_file_path("sneaky_link", str(tmp_path)) + + def test_defaults_to_cwd(self): + """When no base_dir is given, use cwd.""" + cwd = os.getcwd() + # A file in cwd should be valid + result = validate_file_path(".", None) + assert result == os.path.realpath(cwd) + + def test_escape_hatch(self, tmp_path, monkeypatch): + """CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true bypasses validation.""" + monkeypatch.setenv("CREWAI_TOOLS_ALLOW_UNSAFE_PATHS", "true") + # This would normally be rejected + result = validate_file_path("/etc/passwd", str(tmp_path)) + assert result == os.path.realpath("/etc/passwd") + + +class TestValidateDirectoryPath: + """Tests for validate_directory_path.""" + + def test_valid_directory(self, tmp_path): + (tmp_path / "subdir").mkdir() + result = validate_directory_path("subdir", str(tmp_path)) + assert result == str(tmp_path / "subdir") + + def test_rejects_file_as_directory(self, tmp_path): + (tmp_path / "file.txt").touch() + with pytest.raises(ValueError, match="not a directory"): + validate_directory_path("file.txt", str(tmp_path)) + + def test_rejects_traversal(self, tmp_path): + with pytest.raises(ValueError, match="outside the allowed directory"): + validate_directory_path("../../", str(tmp_path)) + + +# --------------------------------------------------------------------------- +# URL validation +# --------------------------------------------------------------------------- + +class TestValidateUrl: + """Tests for validate_url.""" + + def test_valid_https_url(self): + """Normal HTTPS URL should pass.""" + result = validate_url("https://example.com/data.json") + assert result == "https://example.com/data.json" + + def test_valid_http_url(self): + """Normal HTTP URL should pass.""" + result = validate_url("http://example.com/api") + assert result == "http://example.com/api" + + def test_blocks_file_scheme(self): + """file:// URLs must be blocked.""" + with pytest.raises(ValueError, match="file:// URLs are not allowed"): + validate_url("file:///etc/passwd") + + def test_blocks_file_scheme_with_host(self): + with pytest.raises(ValueError, match="file:// URLs are not allowed"): + validate_url("file://localhost/etc/shadow") + + def test_blocks_localhost(self): + """localhost must be blocked (resolves to 127.0.0.1).""" + with pytest.raises(ValueError, match="private/reserved IP"): + validate_url("http://localhost/admin") + + def test_blocks_127_0_0_1(self): + with pytest.raises(ValueError, match="private/reserved IP"): + validate_url("http://127.0.0.1/admin") + + def test_blocks_cloud_metadata(self): + """AWS/GCP/Azure metadata endpoint must be blocked.""" + with pytest.raises(ValueError, match="private/reserved IP"): + validate_url("http://169.254.169.254/latest/meta-data/") + + def test_blocks_private_10_range(self): + with pytest.raises(ValueError, match="private/reserved IP"): + validate_url("http://10.0.0.1/internal") + + def test_blocks_private_172_range(self): + with pytest.raises(ValueError, match="private/reserved IP"): + validate_url("http://172.16.0.1/internal") + + def test_blocks_private_192_range(self): + with pytest.raises(ValueError, match="private/reserved IP"): + validate_url("http://192.168.1.1/router") + + def test_blocks_zero_address(self): + with pytest.raises(ValueError, match="private/reserved IP"): + validate_url("http://0.0.0.0/") + + def test_blocks_ipv6_localhost(self): + with pytest.raises(ValueError, match="private/reserved IP"): + validate_url("http://[::1]/admin") + + def test_blocks_ftp_scheme(self): + with pytest.raises(ValueError, match="not allowed"): + validate_url("ftp://example.com/file") + + def test_blocks_empty_hostname(self): + with pytest.raises(ValueError, match="no hostname"): + validate_url("http:///path") + + def test_blocks_unresolvable_host(self): + with pytest.raises(ValueError, match="Could not resolve"): + validate_url("http://this-host-definitely-does-not-exist-abc123.com/") + + def test_escape_hatch(self, monkeypatch): + """CREWAI_TOOLS_ALLOW_UNSAFE_PATHS=true bypasses URL validation.""" + monkeypatch.setenv("CREWAI_TOOLS_ALLOW_UNSAFE_PATHS", "true") + # file:// would normally be blocked + result = validate_url("file:///etc/passwd") + assert result == "file:///etc/passwd" diff --git a/lib/crewai/src/crewai/cli/cli.py b/lib/crewai/src/crewai/cli/cli.py index b0483d570..c40fe656f 100644 --- a/lib/crewai/src/crewai/cli/cli.py +++ b/lib/crewai/src/crewai/cli/cli.py @@ -609,7 +609,6 @@ def env() -> None: @env.command("view") def env_view() -> None: """View tracing-related environment variables.""" - import os from pathlib import Path from rich.console import Console @@ -738,7 +737,6 @@ def traces_disable() -> None: @traces.command("status") def traces_status() -> None: """Show current trace collection status.""" - import os from rich.console import Console from rich.panel import Panel diff --git a/lib/crewai/src/crewai/tasks/llm_guardrail.py b/lib/crewai/src/crewai/tasks/llm_guardrail.py index 3cbd20c65..754596ab7 100644 --- a/lib/crewai/src/crewai/tasks/llm_guardrail.py +++ b/lib/crewai/src/crewai/tasks/llm_guardrail.py @@ -1,6 +1,6 @@ import asyncio -import concurrent.futures from collections.abc import Coroutine +import concurrent.futures import contextvars import inspect from typing import Any