fix(security): keep stream failures out of logs

This commit is contained in:
debpalash
2026-08-09 23:18:16 +00:00
parent bebe462dc6
commit 544d345069
4 changed files with 18 additions and 9 deletions
+1 -1
View File
@@ -1448,7 +1448,7 @@ async def dub_transcribe_stream(
async for ev in _gen_body():
yield ev
except Exception: # noqa: BLE001 — last-resort stream finalizer
logger.exception("transcribe stream crashed (job=%r)", job_id)
logger.error("Transcription stream failed unexpectedly")
from core.public_errors import stream_failure
yield _sse_event("error", stream_failure("transcription_failed"))
yield _sse_event("done", {})
+1 -1
View File
@@ -1548,7 +1548,7 @@ async def generate_speech(
from core.public_errors import stream_failure
yield _line({"type": "error", **stream_failure("invalid_request")})
except Exception:
logger.exception("Streaming generation failed")
logger.error("Streaming generation failed unexpectedly")
from core.public_errors import stream_failure
yield _line({"type": "error", **stream_failure("generation_failed")})
finally:
+7 -3
View File
@@ -160,7 +160,9 @@ def test_transcribe_stream_surfaces_model_load_failure(tmp_path, monkeypatch):
assert "CUDA driver init failed: simulated" in body, body
def test_transcribe_stream_never_closes_without_terminal_event(tmp_path, monkeypatch):
def test_transcribe_stream_never_closes_without_terminal_event(
tmp_path, monkeypatch, caplog
):
"""Regression #516: an unanticipated exception INSIDE the stream body (one
that escapes the per-chunk handler, e.g. segmentation blowing up) must still
end the stream with a terminal `error` then `done` — never a silent
@@ -203,7 +205,7 @@ def test_transcribe_stream_never_closes_without_terminal_event(tmp_path, monkeyp
# Make the post-chunk segmentation (outside the per-chunk try/except) blow
# up — the exact class of "unanticipated escape" the guard must catch.
def _boom_segment(*a, **k):
raise RuntimeError("segmentation exploded: simulated")
raise RuntimeError("API_KEY=dub-secret /home/alice/private-video.mp4")
monkeypatch.setattr(dc, "segment_transcript", _boom_segment)
# Don't touch the GPU/TTS during the test.
monkeypatch.setattr(dc, "offload_tts_for_asr", lambda *a, **k: None)
@@ -224,8 +226,10 @@ def test_transcribe_stream_never_closes_without_terminal_event(tmp_path, monkeyp
assert "event: error" in body, body
assert "transcription_failed" in body, body
assert "Transcription failed. Check the selected ASR engine and try again." in body, body
assert "segmentation exploded: simulated" not in body, body
assert "dub-secret" not in body, body
assert "Traceback" not in body, body
assert "dub-secret" not in caplog.text
assert "/home/alice/private-video.mp4" not in caplog.text
err_idx = body.rfind("event: error")
done_idx = body.rfind("event: done")
assert done_idx > err_idx >= 0, f"error must precede the terminal done: {body}"
+9 -4
View File
@@ -111,7 +111,9 @@ def _make_deterministic_engine(engine_id="stream-fake", *, delay_s=0.0,
def generate(self, text, **kw) -> torch.Tensor:
type(self).calls.append((text, time.monotonic()))
if fail_on_call is not None and len(type(self).calls) == fail_on_call:
raise RuntimeError("engine exploded mid-stream (test)")
raise RuntimeError(
"TOKEN=stream-secret /home/alice/private-reference.wav"
)
if delay_s:
time.sleep(delay_s)
# Deterministic, text-dependent waveform (crc-seeded sine-ish ramp).
@@ -277,8 +279,9 @@ def test_stream_final_file_identical_to_classic_output(client, monkeypatch,
assert done["duration"] > 0
def test_stream_midstream_error_yields_error_event(client, monkeypatch,
no_omnivoice_model):
def test_stream_midstream_error_yields_error_event(
client, monkeypatch, no_omnivoice_model, caplog
):
"""Chunk 2 blowing up must surface as an in-band error event AFTER the
already-delivered chunk — no done, no history row, no saved file."""
fake = _make_deterministic_engine(fail_on_call=2)
@@ -297,8 +300,10 @@ def test_stream_midstream_error_yields_error_event(client, monkeypatch,
error = events[-1][0]
assert error["code"] == "generation_failed"
assert error["detail"] == "Generation failed. Check the selected engine and try again."
assert "engine exploded" not in repr(error)
assert "stream-secret" not in repr(error)
assert "Traceback" not in repr(error)
assert "stream-secret" not in caplog.text
assert "/home/alice/private-reference.wav" not in caplog.text
after_ids = {h["id"] for h in client.get("/history").json()}
assert after_ids == before_ids # nothing was recorded for the failure