fix(image-gen): sanitize config validation errors; widen expected_revision type

load_image_generation_settings now re-raises pydantic ValidationError as a
sanitized ImageGenError carrying only field locations and error types, so a
mis-indented config.yaml can never echo a literal API key into agent-visible
errors. Also widen save_registry's expected_revision annotation to
int | None to match the http_api caller (value remains ignored).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
m4
2026-07-24 07:51:36 +08:00
parent 395baab7d5
commit 7f26ecc19a
3 changed files with 43 additions and 6 deletions
+16 -2
View File
@@ -17,7 +17,7 @@ from pathlib import Path
from typing import Any, Literal
import yaml
from pydantic import BaseModel, Field, field_validator
from pydantic import BaseModel, Field, ValidationError, field_validator
IMAGE_GENERATION_SECTION = "image_generation"
DEFAULT_IMAGE_GENERATION_TIMEOUT_SECONDS = 120.0
@@ -117,4 +117,18 @@ def load_image_generation_settings(
section = data.get(IMAGE_GENERATION_SECTION)
if not isinstance(section, dict):
return ImageGenerationSettings()
return ImageGenerationSettings.model_validate(section)
try:
return ImageGenerationSettings.model_validate(section)
except ValidationError as exc:
# Never echo input values: a mis-indented yaml can put a literal API
# key under the wrong field and pydantic's default message embeds it
# verbatim. Report only field locations and error types.
from EvoScientist.image_gen.adapters.base import ImageGenError
details = "; ".join(
f"{'.'.join(str(part) for part in error['loc'])}: {error['type']}"
for error in exc.errors(include_url=False, include_input=False)
)
raise ImageGenError(
f"invalid {IMAGE_GENERATION_SECTION} section: {details}"
) from None
+4 -4
View File
@@ -244,16 +244,16 @@ class ModelRuntimeStore:
def save_registry(
self,
*,
expected_revision: int,
expected_revision: int | None,
registry: RegistryV4,
credential_writes: list[CredentialWrite] | None = None,
validate: SaveValidator | None = None,
) -> RegistryV4:
"""Save the registry inside one ``BEGIN IMMEDIATE`` transaction.
``expected_revision`` is accepted for API compatibility but ignored:
saves are last-write-wins and the stored revision is always
``current + 1``. Credential versions are written first (immutable,
``expected_revision`` (``int`` or ``None``) is accepted for API
compatibility but ignored: saves are last-write-wins and the stored
revision is always ``current + 1``. Credential versions are written first (immutable,
with incrementing revisions), then the optional ``validate`` hook
runs the section 9.2 save-time checks against the post-write state,
then the registry row is replaced. A bootstrap document atomically
+23
View File
@@ -2,6 +2,9 @@
import os
import pytest
from EvoScientist.image_gen.adapters.base import ImageGenError
from EvoScientist.image_gen.config import (
ImageGenerationSettings,
ImageModelEntry,
@@ -93,6 +96,26 @@ image_generation:
assert settings.models[0].resolved_api_key() == "sk-literal"
def test_validation_error_never_echoes_config_values(tmp_path):
"""A malformed section must raise a sanitized error: field locations and
error types only, never the offending input values (key-leak red line)."""
path = _write_config(
tmp_path,
"""
image_generation:
models:
- id: gpt-image-2
provider: sk-secret-value-123
api_key: sk-secret-value-123
""",
)
with pytest.raises(ImageGenError) as excinfo:
load_image_generation_settings(config_path=path)
message = str(excinfo.value)
assert "sk-secret-value-123" not in message
assert "provider" in message
def test_is_image_generation_model():
assert is_image_generation_model("gpt-image-2") is True
assert is_image_generation_model("dall-e-3") is True