Add async folder virus scanning via ClamAV#96
Conversation
Introduces a folder scan feature behind a pluggable VirusScanner
interface, with a ClamAV (clamd INSTREAM) implementation as the
default engine. POST /api/files/scan starts an asynchronous scan and
returns a job id; GET /api/files/scan/{jobId} polls status and any
detected threats. Scans run on a background executor, are confined to
the authenticated user's root folder (reusing the path-traversal
validation), exclude the trash directory, and jobs are only visible
to the user that created them. Disabled by default; configurable via
cloudpage.virus-scan.*.
Closes Vault-Web#26
There was a problem hiding this comment.
Pull request overview
Adds a new backend “folder virus scan” capability with an async job model and a pluggable scanning engine (default: ClamAV via clamd INSTREAM), exposed via /api/files/scan endpoints and controlled by cloudpage.virus-scan.* configuration.
Changes:
- Introduces
VirusScannerabstraction + ClamAV-based implementation (ClamAvVirusScanner) and scan-job domain model (ScanJob,ScanStatus,ScanVerdict,FileScanResult). - Adds
VirusScanServiceto validate paths, walk folders (excluding.trash), run scans asynchronously, and track per-user jobs in memory. - Adds
VirusScanController+ DTOs and comprehensive unit/integration tests; wires feature flags and executor config.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/src/main/java/cloudpage/scan/VirusScanner.java | Defines the pluggable scanner interface used by the service. |
| backend/src/main/java/cloudpage/scan/ClamAvVirusScanner.java | Implements ClamAV clamd INSTREAM client for per-file scanning. |
| backend/src/main/java/cloudpage/scan/VirusScanService.java | Implements async folder traversal, scanning, findings aggregation, and per-user job visibility. |
| backend/src/main/java/cloudpage/scan/VirusScanConfig.java | Wires config properties and creates the background executor for scans. |
| backend/src/main/java/cloudpage/scan/VirusScanProperties.java | Adds cloudpage.virus-scan.* configuration model (enabled/host/port/timeout/concurrency). |
| backend/src/main/java/cloudpage/scan/ScanJob.java | Adds in-memory mutable job state with counters and findings list. |
| backend/src/main/java/cloudpage/scan/ScanStatus.java | Defines job lifecycle states. |
| backend/src/main/java/cloudpage/scan/ScanVerdict.java | Defines per-file scan outcomes. |
| backend/src/main/java/cloudpage/scan/FileScanResult.java | Represents infected/errored per-file findings (relative paths). |
| backend/src/main/java/cloudpage/controller/VirusScanController.java | Adds POST to start scans and GET to poll job status/results. |
| backend/src/main/java/cloudpage/dto/ScanJobDto.java | DTO for job status/metadata/findings returned by the API. |
| backend/src/main/java/cloudpage/dto/FileScanResultDto.java | DTO for a single infected/errored file finding. |
| backend/src/main/resources/application.properties | Adds default-disabled virus scan configuration properties. |
| backend/src/test/java/cloudpage/scan/VirusScanServiceTest.java | Unit tests for service behavior (counts, findings, .trash exclusion, ownership). |
| backend/src/test/java/cloudpage/scan/ClamAvVirusScannerTest.java | Unit tests for clamd response parsing. |
| backend/src/test/java/cloudpage/scan/ClamAvVirusScannerIntegrationTest.java | End-to-end protocol test against an in-process fake clamd server. |
| backend/src/test/java/cloudpage/controller/VirusScanControllerTest.java | WebMvc tests for endpoint responses and DTO shaping. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Executor core pool = maxConcurrentScans so scans actually run in parallel instead of queueing behind one thread. - Skip symlinks during the walk (NOFOLLOW_LINKS) so a symlink inside the root cannot stream a host file outside the boundary to the scanner. - Catch all exceptions in the scan loop: a runtime failure on one file no longer leaves the job stuck in RUNNING, and a bad file is recorded as an error finding instead of aborting the scan. - Add scheduled eviction of finished jobs past a configurable retention so the in-memory job map stays bounded. - Relax the startScan Javadoc to allow an already-RUNNING job.
|
Thanks for the thorough pass — all five addressed:
Added tests for the runtime-failure resilience and the eviction. |
DenizAltunkapan
left a comment
There was a problem hiding this comment.
Really clean work. would you be up for wiring this into the Vault Web frontend as a follow-up? I'll open a tracking issue in the core repo with the API details so it's ready to pick up @GabrielBBaldez
|
@DenizAltunkapan Yes, you can assign it to me. And if there are any other issues you’d like me to work on, just let me know or tag me in the issue. |
Implements #26 as discussed. Pluggable
VirusScannerinterface with a ClamAV (clamdINSTREAM) default engine.POST /api/files/scanstarts an async scan and returns a job id;GET /api/files/scan/{jobId}polls status and detected threats. Scans run on a background executor, are confined to the user's root (reusing the existing path validation), exclude.trash, and jobs are only visible to their owner. Disabled by default viacloudpage.virus-scan.*.Job state is currently in-memory; persisting it is a natural follow-up. The ClamAV socket/INSTREAM client is exercised end-to-end against an in-process fake clamd (connect, chunk framing, response parsing); the real signature matching needs a running
clamd, which CI/deployment can validate.Closes #26