fix: harden upstream path + binary-body validation (gadfly round 1)
- upstreamPath rejects '..' in model ids AND in the rest path — the rest can embed SERVER-SUPPLIED components (ACE-Step result file URLs), so dot-dot/scheme smuggling toward other proxy endpoints is refused - singleImageResult requires positive image evidence (sniffed magic OR declared image/*): an empty-Content-Type error page can no longer pass as 'the image' via sniffImageMIME's PNG-default labelling - upscale/background responses get a dedicated 256MB cap (the 64MB cap is JSON-sized; a 4x PNG legitimately exceeds it) - mesh JSON-detection widened (512-byte whitespace-tolerant peek + reject declared application/json) - Transcribe now reuses buildMultipart; transcriptionFilename takes (filename, mime) so diarize shares it without a fake request struct; truncateForError stops shadowing builtin cap; OnlyMask doc de-ambiguated Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -22,6 +22,12 @@ import (
|
||||
"gitea.stevedudenhoeffer.com/steve/majordomo/videogen"
|
||||
)
|
||||
|
||||
// maxImageResponseBytes caps the raw-image bodies from the upscale and
|
||||
// background-removal endpoints. The 64MB maxResponseBytes is a JSON cap;
|
||||
// a 4x-upscaled PNG legitimately exceeds it. Bounded (not video-sized)
|
||||
// because a single still image past this is an upstream bug, not data.
|
||||
const maxImageResponseBytes = 256 << 20
|
||||
|
||||
// --- upscale ---
|
||||
|
||||
// UpscaleModel implements imagegen.UpscaleProvider against the mediautils
|
||||
@@ -62,7 +68,7 @@ func (m *upscaleModel) Upscale(ctx context.Context, req imagegen.UpscaleRequest,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, respType, err := m.p.doRaw(ctx, http.MethodPost, path, m.id, contentType, body, maxResponseBytes)
|
||||
raw, respType, err := m.p.doRaw(ctx, http.MethodPost, path, m.id, contentType, body, maxImageResponseBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -112,23 +118,31 @@ func (m *backgroundModel) RemoveBackground(ctx context.Context, req imagegen.Bac
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, respType, err := m.p.doRaw(ctx, http.MethodPost, path, m.id, contentType, body, maxResponseBytes)
|
||||
raw, respType, err := m.p.doRaw(ctx, http.MethodPost, path, m.id, contentType, body, maxImageResponseBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return singleImageResult(m.p.name, m.id, "background removal", raw, respType)
|
||||
}
|
||||
|
||||
// singleImageResult wraps one raw image body into an imagegen.Result,
|
||||
// rejecting empty or non-image payloads (a proxy error page must not become
|
||||
// "the image").
|
||||
// singleImageResult wraps one raw image body into an imagegen.Result.
|
||||
// Acceptance requires positive evidence of image-ness — sniffed magic
|
||||
// bytes or a declared image/* Content-Type — so a proxy error page with
|
||||
// an empty Content-Type can never become "the image" (sniffImageMIME's
|
||||
// PNG default is a labelling fallback, not a validator).
|
||||
func singleImageResult(provider, model, verb string, raw []byte, contentType string) (*imagegen.Result, error) {
|
||||
if len(raw) == 0 {
|
||||
return nil, &llm.APIError{Provider: provider, Model: model, Message: verb + " response contained no image"}
|
||||
}
|
||||
mimeType := sniffImageMIME(raw)
|
||||
if ct := strings.TrimSpace(contentType); ct != "" && !strings.HasPrefix(ct, "image/") && !strings.HasPrefix(http.DetectContentType(raw), "image/") {
|
||||
return nil, &llm.APIError{Provider: provider, Model: model, Message: fmt.Sprintf("%s response is not an image (Content-Type %q)", verb, ct)}
|
||||
sniffed := http.DetectContentType(raw)
|
||||
declared := strings.TrimSpace(contentType)
|
||||
if !strings.HasPrefix(sniffed, "image/") && !strings.HasPrefix(declared, "image/") {
|
||||
return nil, &llm.APIError{Provider: provider, Model: model,
|
||||
Message: fmt.Sprintf("%s response is not an image (Content-Type %q, sniffed %q)", verb, declared, sniffed)}
|
||||
}
|
||||
mimeType := sniffed
|
||||
if !strings.HasPrefix(mimeType, "image/") {
|
||||
mimeType = declared
|
||||
}
|
||||
return &imagegen.Result{Images: []llm.ImagePart{{MIME: mimeType, Data: raw}}}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user