fix: harden upstream path + binary-body validation (gadfly round 1)
CI / Tidy (pull_request) Successful in 9m24s
CI / Build & Test (pull_request) Successful in 10m17s

- 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:
2026-07-13 00:50:21 -04:00
co-authored by Claude Fable 5
parent e98493bcfb
commit 4a752ffa2a
7 changed files with 89 additions and 50 deletions
+12 -22
View File
@@ -7,7 +7,6 @@ import (
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"net/url"
"strings"
@@ -119,28 +118,19 @@ func (m *transcriptionModel) Transcribe(ctx context.Context, req audio.Transcrip
return nil, fmt.Errorf("%w: transcription requires audio bytes", llm.ErrUnsupported)
}
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
fw, err := w.CreateFormFile("file", transcriptionFilename(req))
buf, formType, err := buildMultipart("build transcription form",
filePart{field: "file", filename: transcriptionFilename(req.Filename, req.MIME), data: req.Audio},
[]formField{
{"model", m.id, true},
{"response_format", "json", true},
{"language", req.Language, false},
{"prompt", req.Prompt, false},
})
if err != nil {
return nil, fmt.Errorf("llama-swap: build transcription form: %w", err)
}
if _, err := fw.Write(req.Audio); err != nil {
return nil, fmt.Errorf("llama-swap: build transcription form: %w", err)
}
if err := writeFormFields(w, "build transcription form", []formField{
{"model", m.id, true},
{"response_format", "json", true},
{"language", req.Language, false},
{"prompt", req.Prompt, false},
}); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, fmt.Errorf("llama-swap: build transcription form: %w", err)
}
raw, _, err := m.p.doRaw(ctx, http.MethodPost, "/v1/audio/transcriptions", m.id, w.FormDataContentType(), &buf, maxResponseBytes)
raw, _, err := m.p.doRaw(ctx, http.MethodPost, "/v1/audio/transcriptions", m.id, formType, buf, maxResponseBytes)
if err != nil {
return nil, err
}
@@ -158,11 +148,11 @@ func (m *transcriptionModel) Transcribe(ctx context.Context, req audio.Transcrip
// headers), else one derived from the MIME subtype ("audio.mp3"), else
// "audio". MIME parameters ("audio/ogg; codecs=opus") are stripped before
// matching.
func transcriptionFilename(req audio.TranscriptionRequest) string {
if name := sanitizeFilename(req.Filename); name != "" {
func transcriptionFilename(filename, mimeType string) string {
if name := sanitizeFilename(filename); name != "" {
return name
}
mt := strings.ToLower(strings.TrimSpace(req.MIME))
mt := strings.ToLower(strings.TrimSpace(mimeType))
if parsed, _, err := mime.ParseMediaType(mt); err == nil {
mt = parsed
}