[Bug][Prod][Uploads] Large book/video uploads quarantined as "Virus detected" — ClamAV 25 MiB scan cap < the app's 100 MB upload limit #612

Closed
opened 2026-07-16 06:20:51 +00:00 by spikerj · 1 comment
Owner

Summary

A user uploaded a book PDF and it was quarantined as "Virus detected" — but it is not malware. The scan errored on file size and the pipeline fails closed, then mislabels the scan error as a virus detection.

Root cause

  • SecurityScanService.PerformVirusScanAsync constructs new ClamClient(host, port) without setting MaxStreamSize, so it uses nClam's default 25 MiB (26214400 bytes).
  • The app's own size guard is SecurityScanner:MaxFileSizeMB = 100, so a 25–100 MB file clears app validation, then throws nClam.MaxStreamSizeExceededException when streaming to clamd.
  • The exception is caught and returned as not-clean; the caller then relabels any not-clean result as FailureReason = "Virus detected" and quarantines it.
  • The shared clamav stack (clamav/clamav:latest) mounts no custom clamd.conf, so the daemon is also at its default StreamMaxLength 25M — both client and daemon cap at 25 MB while uploads allow 100 MB.

Evidence (live, Seq, 2026-07-16 06:12:28Z)

SecurityScanner  ERROR  Failed to connect to ClamAV for /app/uploads/ebooks/...advanced-game-narrative-toolbox-2nd.pdf
                        nClam.MaxStreamSizeExceededException: The maximum stream size of 26214400 bytes has been exceeded.
UploadCoordinator WARN  Security scan failed: ... - Virus detected
FileMovement      WARN  Moved file to quarantine: /app/quarantine/..._Virus_detected_..._advanced-game-narrative-toolbox-2nd.pdf

Fix

  1. Backend (SecurityScanService): set clam.MaxStreamSize = MaxFileSizeMB * 1024 * 1024 so nClam streams up to the app limit.
  2. Infra (clamav stack): mount a clamd.conf raising StreamMaxLength / MaxFileSize / MaxScanSize to >= 100 MB (defaults are 25M/25M/100M).
  3. Correctness: a scan error (size, ClamAV unreachable) must not be reported to users as "Virus detected" — still fail closed/quarantine, but with an accurate reason.

Impact

Any book/video between 25 MB and the 100 MB app limit is silently quarantined as a false-positive virus. No un-quarantine/re-inject path exists (cf. #499), so affected uploads must be re-uploaded after the fix.

## Summary A user uploaded a **book PDF and it was quarantined as "Virus detected"** — but it is **not malware**. The scan **errored on file size** and the pipeline fails closed, then mislabels the scan *error* as a virus detection. ## Root cause - `SecurityScanService.PerformVirusScanAsync` constructs `new ClamClient(host, port)` **without setting `MaxStreamSize`**, so it uses nClam's default **25 MiB (26214400 bytes)**. - The app's own size guard is `SecurityScanner:MaxFileSizeMB = 100`, so a **25–100 MB** file clears app validation, then throws `nClam.MaxStreamSizeExceededException` when streaming to clamd. - The exception is caught and returned as not-clean; the caller then relabels **any** not-clean result as `FailureReason = "Virus detected"` and quarantines it. - The shared `clamav` stack (`clamav/clamav:latest`) mounts **no custom clamd.conf**, so the daemon is also at its default **`StreamMaxLength 25M`** — both client and daemon cap at 25 MB while uploads allow 100 MB. ## Evidence (live, Seq, 2026-07-16 06:12:28Z) ``` SecurityScanner ERROR Failed to connect to ClamAV for /app/uploads/ebooks/...advanced-game-narrative-toolbox-2nd.pdf nClam.MaxStreamSizeExceededException: The maximum stream size of 26214400 bytes has been exceeded. UploadCoordinator WARN Security scan failed: ... - Virus detected FileMovement WARN Moved file to quarantine: /app/quarantine/..._Virus_detected_..._advanced-game-narrative-toolbox-2nd.pdf ``` ## Fix 1. **Backend** (`SecurityScanService`): set `clam.MaxStreamSize = MaxFileSizeMB * 1024 * 1024` so nClam streams up to the app limit. 2. **Infra** (`clamav` stack): mount a `clamd.conf` raising `StreamMaxLength` / `MaxFileSize` / `MaxScanSize` to >= 100 MB (defaults are 25M/25M/100M). 3. **Correctness**: a scan *error* (size, ClamAV unreachable) must not be reported to users as "Virus detected" — still fail closed/quarantine, but with an accurate reason. ## Impact Any book/video between 25 MB and the 100 MB app limit is silently quarantined as a false-positive virus. No un-quarantine/re-inject path exists (cf. #499), so affected uploads must be re-uploaded after the fix.
Author
Owner

PRs open (not yet merged):

  • spikersoft-backend #318 — nClam MaxStreamSize + scan-error-vs-virus labeling
  • spikersoft-infrastructure #117 — clamd StreamMaxLength/MaxFileSize 128M, MaxScanSize 384M via CLAMD_CONF_* env

Deploy the infra PR first (or together) — clamd must accept the larger stream before the app sends it. After merge+deploy, re-upload affected files (no un-quarantine path exists). Split off #613 for the quarantine-bucket write-policy gap found in the same log burst.

PRs open (not yet merged): - spikersoft-backend #318 — nClam MaxStreamSize + scan-error-vs-virus labeling - spikersoft-infrastructure #117 — clamd StreamMaxLength/MaxFileSize 128M, MaxScanSize 384M via CLAMD_CONF_* env Deploy the infra PR first (or together) — clamd must accept the larger stream before the app sends it. After merge+deploy, re-upload affected files (no un-quarantine path exists). Split off #613 for the quarantine-bucket write-policy gap found in the same log burst.
Sign in to join this conversation.