From b87ebd940227465d611084e5bf49582aaeb4e9bd Mon Sep 17 00:00:00 2001 From: Shxiao Date: Mon, 28 Sep 2026 23:46:39 +0900 Subject: [PATCH] fix(database): validate pool settings and command_timeout against booleans and non-finite values (#6009) --- CHANGELOG.md | 9 +++ CHANGELOG_zh.md | 6 ++ .../deerflow/config/database_config.py | 16 +++++ ...test_persistence_engine_postgres_config.py | 58 +++++++++++++++++++ 4 files changed, 89 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b816bbf3c..e87a8e7bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -327,6 +327,15 @@ This release closes that milestone with **181 merged pull requests**. ### Fixed +- **database:** `DatabaseConfig` now validates `pool_size`, `pool_recycle`, and + `command_timeout` strictly. Previously, YAML booleans (`true`/`false`) were + coerced to `1`/`0` respectively, allowing `pool_size: true` (pool size 1) and + `command_timeout: true` (a 1-second statement timeout) to silently pass + configuration loading. `pool_size` also accepted non-positive values (`0`, + `-1`), and `command_timeout` accepted `inf` (which never times out). + `pool_size` and `pool_recycle` now enforce positive integers, and + `command_timeout` rejects booleans and non-finite floats while retaining + `null` to disable timeouts. - **frontend:** The optimistic human bubble keeps its quote and conversation reference chips once a file upload finishes. The upload-complete update replaced the bubble's `additional_kwargs` with only the uploaded files, so diff --git a/CHANGELOG_zh.md b/CHANGELOG_zh.md index ed106095f3..7d12e9b2c3 100644 --- a/CHANGELOG_zh.md +++ b/CHANGELOG_zh.md @@ -332,6 +332,12 @@ ### 修复 +- **数据库:** `DatabaseConfig` 现在严格校验 `pool_size`、`pool_recycle` 与 + `command_timeout`。此前,YAML 布尔值(`true`/`false`)会被强制转换为 `1`/`0`, + 导致 `pool_size: true`(变成仅 1 个连接)和 `command_timeout: true`(变成 1 秒超时) + 静默通过配置加载;`pool_size` 还接受非正数(`0`、`-1`),`command_timeout` 接受 + `inf`(导致超时机制永不触发)。现在 `pool_size` 和 `pool_recycle` 强制要求正整数, + `command_timeout` 拒绝布尔值与非有限浮点数,同时保留 `null` 显式禁用超时。 - **前端:** 文件上传完成后,乐观显示的用户消息气泡不再丢失引用与对话 引用标签。上传完成时的更新会用仅含已上传文件的对象替换气泡的 `additional_kwargs`,因此在服务端回传该消息之前这些标签会消失;随本次 diff --git a/backend/packages/harness/deerflow/config/database_config.py b/backend/packages/harness/deerflow/config/database_config.py index ee984a5fec..87eb7d83f7 100644 --- a/backend/packages/harness/deerflow/config/database_config.py +++ b/backend/packages/harness/deerflow/config/database_config.py @@ -184,6 +184,7 @@ class DatabaseConfig(BaseModel): ) pool_size: int = Field( default=5, + gt=0, description="Connection pool size for the app ORM engine (postgres only).", ) pool_recycle: int = Field( @@ -194,6 +195,7 @@ class DatabaseConfig(BaseModel): command_timeout: float | None = Field( default=30, gt=0, + allow_inf_nan=False, description="Timeout in seconds for app ORM PostgreSQL commands. Set to null to disable the command timeout.", ) postgres_schema: str = Field( @@ -213,6 +215,20 @@ class DatabaseConfig(BaseModel): def _validate_postgres_schema(cls, value: str) -> str: return validate_postgres_schema(value) + @field_validator("pool_size", "pool_recycle", mode="before") + @classmethod + def _reject_boolean_pool_settings(cls, value: object) -> object: + if isinstance(value, bool): + raise ValueError("must be an integer, not a boolean") + return value + + @field_validator("command_timeout", mode="before") + @classmethod + def _reject_boolean_command_timeout(cls, value: object) -> object: + if isinstance(value, bool): + raise ValueError("command_timeout must be a number of seconds or null, not a boolean") + return value + # -- Legacy key migration (not user-configured) -- @model_validator(mode="before") diff --git a/backend/tests/test_persistence_engine_postgres_config.py b/backend/tests/test_persistence_engine_postgres_config.py index 81bca3c575..570b1d3825 100644 --- a/backend/tests/test_persistence_engine_postgres_config.py +++ b/backend/tests/test_persistence_engine_postgres_config.py @@ -9,6 +9,8 @@ from types import ModuleType from unittest.mock import AsyncMock, MagicMock, call, patch import pytest +import yaml +from pydantic import ValidationError from deerflow.config.database_config import DatabaseConfig from deerflow.persistence import engine as engine_mod @@ -54,6 +56,62 @@ def test_postgres_engine_kwargs_allow_command_timeout_opt_out() -> None: assert kwargs["connect_args"] == {} +@pytest.mark.parametrize( + ("raw", "expected"), + [ + (None, None), + (30, 30.0), + (0.5, 0.5), + ("60", 60.0), + ], +) +def test_database_command_timeout_accepts_finite_positive_seconds(raw, expected: float | None) -> None: + assert DatabaseConfig(command_timeout=raw).command_timeout == expected + + +@pytest.mark.parametrize( + "raw", + [ + True, + False, + yaml.safe_load("on"), + yaml.safe_load("off"), + yaml.safe_load("yes"), + yaml.safe_load("no"), + 0, + -1, + -0.5, + float("inf"), + float("-inf"), + float("nan"), + yaml.safe_load(".inf"), + yaml.safe_load(".nan"), + "1e999", + ], +) +def test_database_command_timeout_rejects_boolean_and_non_finite_values(raw) -> None: + with pytest.raises(ValidationError, match="command_timeout"): + DatabaseConfig(command_timeout=raw) + + +@pytest.mark.parametrize( + ("field", "invalid_value"), + [ + ("pool_size", True), + ("pool_size", False), + ("pool_size", 0), + ("pool_size", -1), + ("pool_recycle", True), + ("pool_recycle", False), + ("pool_recycle", 0), + ("pool_recycle", -1), + ], +) +def test_database_pool_settings_reject_booleans_and_non_positive_integers(field: str, invalid_value) -> None: + with pytest.raises(ValidationError): + DatabaseConfig(**{field: invalid_value}) + + @pytest.mark.asyncio async def test_configured_command_timeout_ends_stalled_command(monkeypatch) -> None: config = DatabaseConfig(