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
Backend (SecurityScanService): set clam.MaxStreamSize = MaxFileSizeMB * 1024 * 1024 so nClam streams up to the app limit.
Infra (clamav stack): mount a clamd.conf raising StreamMaxLength / MaxFileSize / MaxScanSize to >= 100 MB (defaults are 25M/25M/100M).
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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.PerformVirusScanAsyncconstructsnew ClamClient(host, port)without settingMaxStreamSize, so it uses nClam's default 25 MiB (26214400 bytes).SecurityScanner:MaxFileSizeMB = 100, so a 25–100 MB file clears app validation, then throwsnClam.MaxStreamSizeExceededExceptionwhen streaming to clamd.FailureReason = "Virus detected"and quarantines it.clamavstack (clamav/clamav:latest) mounts no custom clamd.conf, so the daemon is also at its defaultStreamMaxLength 25M— both client and daemon cap at 25 MB while uploads allow 100 MB.Evidence (live, Seq, 2026-07-16 06:12:28Z)
Fix
SecurityScanService): setclam.MaxStreamSize = MaxFileSizeMB * 1024 * 1024so nClam streams up to the app limit.clamavstack): mount aclamd.confraisingStreamMaxLength/MaxFileSize/MaxScanSizeto >= 100 MB (defaults are 25M/25M/100M).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.
PRs open (not yet merged):
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.