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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user