fix: review findings — clone-route audio sniffing, zip caps, filename sanitization, WAV response caps
- speakWithReference now validates the response IS audio via the same audioResultMIME sniff the sfx/enhance surfaces use — a 2xx JSON soft error or HTML proxy page was previously wrapped up as audio/wav bytes. - audioResultMIME moves to audio.go (next to speechMIME; it was defined in sfx.go but shared by enhance/clone) and learns the Ogg container normalization (application/ogg -> audio/ogg). - Stems zip unpack gains entry-count (16) and total-decompressed (1GB) caps on top of the existing per-entry cap — the per-entry bound alone still let a many-entry bomb multiply up. - sanitizeFilename drops NUL and both path separators too, so upload metadata can never smuggle directory structure to a file-writing shim. - New maxAudioResponseBytes (256MB) for bodies that ARE one audio clip (clone, enhance, sfx): a long WAV legitimately passes the 64MB JSON cap. - speakWithReference local renamed path -> upPath (naming parity with stems/enhance). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WWCQcYStWXBYUy5sZWnbLT
This commit is contained in:
@@ -31,6 +31,15 @@ const maxStemsResponseBytes = 512 << 20
|
||||
// A single WAV stem of even a very long song stays far under this.
|
||||
const maxStemEntryBytes = 256 << 20
|
||||
|
||||
// maxStemEntries caps how many stem entries are unpacked: Demucs emits at
|
||||
// most six, so anything past a small multiple of that is a hostile or
|
||||
// broken archive, not a result.
|
||||
const maxStemEntries = 16
|
||||
|
||||
// maxStemsTotalBytes caps the AGGREGATE decompressed size across entries —
|
||||
// the per-entry bound alone would still let a many-entry bomb multiply up.
|
||||
const maxStemsTotalBytes = 1 << 30
|
||||
|
||||
// StemSeparatorModel implements audio.StemSeparationProvider. The id selects
|
||||
// which upstream llama-swap loads.
|
||||
func (p *Provider) StemSeparatorModel(id string, opts ...audio.StemSeparatorModelOption) (audio.StemSeparator, error) {
|
||||
@@ -95,15 +104,25 @@ func (m *stemSeparatorModel) SeparateStems(ctx context.Context, req audio.StemSe
|
||||
Message: fmt.Sprintf("stems response is not a zip (Content-Type %q): %s", respType, truncateForError(raw))}
|
||||
}
|
||||
res := &audio.StemSeparationResult{}
|
||||
var totalBytes int64
|
||||
for _, f := range zr.File {
|
||||
if f.FileInfo().IsDir() {
|
||||
continue
|
||||
}
|
||||
if len(res.Stems) >= maxStemEntries {
|
||||
return nil, &llm.APIError{Provider: m.p.name, Model: m.id,
|
||||
Message: fmt.Sprintf("stems zip holds more than %d entries", maxStemEntries)}
|
||||
}
|
||||
data, err := readZipEntry(f)
|
||||
if err != nil {
|
||||
return nil, &llm.APIError{Provider: m.p.name, Model: m.id,
|
||||
Message: fmt.Sprintf("stems zip entry %q: %v", f.Name, err)}
|
||||
}
|
||||
totalBytes += int64(len(data))
|
||||
if totalBytes > maxStemsTotalBytes {
|
||||
return nil, &llm.APIError{Provider: m.p.name, Model: m.id,
|
||||
Message: fmt.Sprintf("stems zip decompresses past %d bytes", int64(maxStemsTotalBytes))}
|
||||
}
|
||||
// Entry name → stem name; extension → MIME. Entries may sit in a
|
||||
// per-model directory ("htdemucs/vocals.mp3"), so use the base name.
|
||||
base := path.Base(f.Name)
|
||||
|
||||
Reference in New Issue
Block a user