Преглед изворни кода

Harden /audio/stream uploads

tags/v0.9.0
Jan пре 1 месец
родитељ
комит
da668863a1
4 измењених фајлова са 37 додато и 1 уклоњено
  1. +2
    -1
      docs/API.md
  2. +2
    -0
      docs/pro-runtime-hardening-workboard.md
  3. +11
    -0
      internal/control/control.go
  4. +22
    -0
      internal/control/control_test.go

+ 2
- 1
docs/API.md Прегледај датотеку

@@ -294,7 +294,7 @@ Push raw audio data into the live stream buffer. Format: **S16LE stereo PCM** at
Requires `--audio-stdin`, `--audio-http`, or another configured stream source to feed the buffer.
**Request:** Binary body, `application/octet-stream`, raw S16LE stereo PCM bytes. Set `Content-Type` to `application/octet-stream` or `audio/L16`; other media types are rejected.
**Request:** Binary body, `application/octet-stream`, raw S16LE stereo PCM bytes. Set `Content-Type` to `application/octet-stream` or `audio/L16`; other media types are rejected. Requests larger than 512 MiB are rejected with `413 Request Entity Too Large`.
**Response:**
```json
@@ -325,6 +325,7 @@ ffmpeg -i song.mp3 -f s16le -ar 44100 -ac 2 - | \
**Errors:**
- `405` if not POST
- `415` if Content-Type is missing or unsupported (must be `application/octet-stream` or `audio/L16`)
- `413` if the upload body exceeds the 512 MiB limit
- `503` if no audio stream configured
---


+ 2
- 0
docs/pro-runtime-hardening-workboard.md Прегледај датотеку

@@ -443,12 +443,14 @@ Diese Punkte könnten ggf. vorgezogen werden, auch wenn WS-05 formal nach WS-01/
## WS-05 Entscheidungslog
- 2026-04-06: `/audio/stream` now enforces a binary `Content-Type` (`application/octet-stream` or `audio/L16`) before queuing any samples.
- 2026-04-06: `/audio/stream` caps uploads at 512 MiB and rejects larger bodies with `413 Request Entity Too Large` before touching the ring buffer.
## WS-05 Verifikation
| Datum | Fokus | Ergebnis |
|---|---|---|
| 2026-04-05 | `/audio/stream` rejects non-POST requests | `TestAudioStreamRejectsNonPost` enforces POST-only access to `/audio/stream` before a stream source is configured |
| 2026-04-06 | `/audio/stream` enforces binary Content-Type headers | `TestAudioStreamRejectsMissingContentType` and `TestAudioStreamRejectsUnsupportedContentType` confirm 415 when the media type is missing or wrong |
| 2026-04-06 | `/audio/stream` rejects oversized uploads | `TestAudioStreamRejectsBodyTooLarge` confirms a 413 Request Entity Too Large before buffering when the HTTP body exceeds the 512 MiB guard |
---


+ 11
- 0
internal/control/control.go Прегледај датотеку

@@ -3,6 +3,7 @@ package control
import (
_ "embed"
"encoding/json"
"errors"
"io"
"mime"
"net/http"
@@ -56,6 +57,7 @@ const (
configContentTypeHeader = "application/json"
noBodyErrMsg = "request must not include a body"
audioStreamContentTypeError = "Content-Type must be application/octet-stream or audio/L16"
audioStreamBodyLimitDefault = 512 << 20 // 512 MiB
)

var audioStreamAllowedMediaTypes = []string{
@@ -63,6 +65,8 @@ var audioStreamAllowedMediaTypes = []string{
"audio/l16",
}

var audioStreamBodyLimit = int64(audioStreamBodyLimitDefault) // bytes allowed per /audio/stream request; tests may override.

func isJSONContentType(r *http.Request) bool {
ct := strings.TrimSpace(r.Header.Get("Content-Type"))
if ct == "" {
@@ -284,6 +288,8 @@ func (s *Server) handleAudioStream(w http.ResponseWriter, r *http.Request) {
return
}

r.Body = http.MaxBytesReader(w, r.Body, audioStreamBodyLimit)

// Read body in chunks and push to ring buffer
buf := make([]byte, 32768)
totalFrames := 0
@@ -296,6 +302,11 @@ func (s *Server) handleAudioStream(w http.ResponseWriter, r *http.Request) {
if err == io.EOF {
break
}
var maxErr *http.MaxBytesError
if errors.As(err, &maxErr) {
http.Error(w, maxErr.Error(), http.StatusRequestEntityTooLarge)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}


+ 22
- 0
internal/control/control_test.go Прегледај датотеку

@@ -388,6 +388,28 @@ func TestAudioStreamRejectsUnsupportedContentType(t *testing.T) {
}
}

func TestAudioStreamRejectsBodyTooLarge(t *testing.T) {
orig := audioStreamBodyLimit
t.Cleanup(func() {
audioStreamBodyLimit = orig
})
audioStreamBodyLimit = 1024
limit := int(audioStreamBodyLimit)
body := make([]byte, limit+1)
srv := NewServer(cfgpkg.Default())
srv.SetStreamSource(audio.NewStreamSource(256, 44100))
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/audio/stream", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/octet-stream")
srv.Handler().ServeHTTP(rec, req)
if rec.Code != http.StatusRequestEntityTooLarge {
t.Fatalf("expected 413 for oversized body, got %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "request body too large") {
t.Fatalf("unexpected response body: %q", rec.Body.String())
}
}

func TestTXStartWithoutController(t *testing.T) {
srv := NewServer(cfgpkg.Default())
rec := httptest.NewRecorder()


Loading…
Откажи
Сачувај