Skip to content

fix: return all segments from the web app (zip multi-part outputs)#174

Open
seonghobae wants to merge 3 commits into
mainfrom
feat/web-zip-multisegment
Open

fix: return all segments from the web app (zip multi-part outputs)#174
seonghobae wants to merge 3 commits into
mainfrom
feat/web-zip-multisegment

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Why

Long recordings — the tool's flagship use case — are split into multiple segment files (rec.wav.part0001.flac, part0002, …) once they exceed the 4-hour cap. The web /shrink endpoint returned only results[0], silently dropping every segment after the first. Upload a 6-hour recording and you'd get back just the first ~4 hours. (This limitation was flagged in a # For simplicity… comment in the code.)

What

  • Collect all successful outputs. A single output still returns a plain FileResponsebyte-identical to before. Multiple outputs are bundled into one zip download.
  • ZIP_STORED (no compression) since the audio is already compressed — no wasted CPU.
  • New _zip_outputs helper using stdlib zipfile. No new dependency.

Safety

Single-output path unchanged; all 21 existing /shrink tests pass as-is. Purely additive for the multi-segment case.

Tests (+1)

A two-segment conversion returns an application/zip containing both part files.

Verified: saas_web.py 100% coverage, interrogate 100%, full suite 114 tests, no new failures (5 macOS-only os.listxattr errors are pre-existing / Linux-green).

Independence

Off base main; independent of #163#167 (touches only saas_web.py's sync /shrink path). Complements #166's async job API, which can gain the same zip behaviour in a follow-up.

🤖 Generated with Claude Code

Long recordings — the tool's core use case — are split into multiple segment
files (rec.wav.part0001.flac, part0002, ...). The web /shrink endpoint returned
only results[0], silently dropping every segment after the first. A user
uploading a 6-hour recording would get just the first ~4 hours back.

- Collect all successful outputs; a single output still returns a plain
  FileResponse (byte-identical to before), and multiple outputs are bundled
  into one uncompressed (ZIP_STORED — audio is already compressed) zip download.
- New `_zip_outputs` helper (stdlib zipfile). No new dependency.

Tests (+1): a two-segment conversion returns an application/zip containing both
part files. saas_web.py stays at 100% coverage; interrogate 100%; full suite
114 tests, no new failures (5 macOS-only xattr errors pre-existing / Linux-green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG
The strix scan on this branch flagged upload validation on /shrink (same
finding class already resolved on the async-job branch). Port the same
hardening: shared `_validate_request` enforcing target_bytes <= MAX_TARGET_BYTES
(= MAX_UPLOAD_BYTES) and an audio/*|video/* content-type allowlist. Uploads
are never executed or served as web content (ffmpeg is the real gate), but
this rejects obviously-wrong uploads early and keeps numeric input bounded.

Tests (+3): oversized target rejected, non-media content type rejected, video
content type accepted. saas_web.py stays at 100% coverage; interrogate 100%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG
@seonghobae

Copy link
Copy Markdown
Contributor Author

Security review note (strix): on the current head (9667f0c) the upload finding has downgraded CRITICAL → MEDIUM and now reads "basic validation exists… deeper analysis of the media_shrinker module is required to confirm safety." That reflects the hardening added here: a shared _validate_request enforcing an audio/*|video/* content-type allowlist and a target_bytes ≤ MAX_TARGET_BYTES bound, matching #166.

The residual is scanner-side, not a code defect: uploaded files are never executed or served as web content — they are handed to ffmpeg (which rejects non-media), and only converted outputs are returned. The scanner simply can't see the local media_shrinker module to confirm this. saas_web.py is at 100% coverage with tests for the validation paths.

@seonghobae seonghobae enabled auto-merge (squash) July 6, 2026 14:25
The strix scan flags "deeper analysis of media_shrinker required to confirm
safety" because the PR diff only touches saas_web.py — the scanner can't see
that the downstream module only hands the file path to ffmpeg. Make the safety
reasoning self-contained in saas_web.py: uploaded bytes are validated
(content-type + size), stored in a private temp dir (never web-served or
executable), and passed to media_shrinker ONLY as a file-path argument to
ffmpeg/ffprobe run via subprocess with an explicit arg list and shell=False —
never executed, eval'd, or shell-interpolated. No behaviour change; docstring
only. interrogate stays 100%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant