Skip to content

AI Restyle: narrow exception handling for Gemini content-policy refusals + pipeline errors #38

@vansteenbergenmatisse

Description

@vansteenbergenmatisse

Surfaced by the Codex adversarial security audit on PR #35. One of 4 deferred MEDIUMs.

Where (two spots)

Spot A — backend/app/ml/frame_relight.py:71-78:

for part in response.parts or []:
    inline = getattr(part, "inline_data", None)
    if inline and getattr(inline, "data", None):
        with open(out_path, "wb") as f:
            f.write(inline.data)
        return out_path

raise RuntimeError("Nano Banana returned no image (likely content policy)")

Spot B — backend/app/restyle/pipeline.py:129-132:

except Exception as exc:
    job = _ensure_job(jobs, job_id)
    job["status"] = "failed"
    job["logs"].append(f"❌ {exc}")

What's wrong

A: Content-policy refusals from gemini-3.1-flash-image-preview come back as a response with no inline_data parts. We infer the refusal from the absence — but the google-genai SDK actually raises typed exceptions (google.genai.errors.ClientError, google.genai.errors.ServerError) on hard failures, and surfaces response.prompt_feedback.block_reason on soft policy hits. The current code conflates "hard API error", "network timeout", "safety block", and "empty result" under one generic RuntimeError.

B: The pipeline's outer except Exception catches everything from a ValueError(\"video too long\") to httpx.ConnectError to a programmer mistake. Users see "❌ {exc}" with whatever message the framework happened to throw. No structured error class on the response.

Severity

MEDIUM. Doesn't expose security info, but it makes incidents painful to debug and produces a poor UX (policy refusal vs network blip look identical to the user).

Suggested fix

  1. In frame_relight.py:

    from google.genai import errors as genai_errors
    try:
        response = client.models.generate_content(...)
    except genai_errors.ClientError as e:
        raise ContentPolicyError(...)  # new typed class
    except genai_errors.ServerError as e:
        raise ProviderError(...) from e
    
    if response.prompt_feedback and response.prompt_feedback.block_reason:
        raise ContentPolicyError(reason=response.prompt_feedback.block_reason)
  2. In restyle/pipeline.py, catch typed errors explicitly and surface user-facing messages keyed by error class:

    • ContentPolicyError → "This style request was rejected by Gemini's safety policy. Try a different background/lighting prompt."
    • FalError → "The video model encountered an error. Try again in a moment."
    • FileNotFoundError / OSError → log internally, generic message externally.
    • Bare Exception → keep as last-resort log; mark status=\"failed\" but with a generic user message.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions