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