Skip to content

Add async folder virus scanning via ClamAV#96

Merged
DenizAltunkapan merged 2 commits into
Vault-Web:mainfrom
GabrielBBaldez:feat/folder-virus-scan
Jul 14, 2026
Merged

Add async folder virus scanning via ClamAV#96
DenizAltunkapan merged 2 commits into
Vault-Web:mainfrom
GabrielBBaldez:feat/folder-virus-scan

Conversation

@GabrielBBaldez

Copy link
Copy Markdown
Member

Implements #26 as discussed. Pluggable VirusScanner interface with a ClamAV (clamd INSTREAM) default engine. POST /api/files/scan starts 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 via cloudpage.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

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
Copilot AI review requested due to automatic review settings July 11, 2026 16:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 VirusScanner abstraction + ClamAV-based implementation (ClamAvVirusScanner) and scan-job domain model (ScanJob, ScanStatus, ScanVerdict, FileScanResult).
  • Adds VirusScanService to 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.

Comment thread backend/src/main/java/cloudpage/scan/VirusScanConfig.java
Comment thread backend/src/main/java/cloudpage/scan/VirusScanService.java Outdated
Comment thread backend/src/main/java/cloudpage/scan/VirusScanService.java Outdated
Comment thread backend/src/main/java/cloudpage/scan/VirusScanService.java Outdated
Comment thread backend/src/main/java/cloudpage/scan/VirusScanService.java
- 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.
@GabrielBBaldez

Copy link
Copy Markdown
Member Author

Thanks for the thorough pass — all five addressed:

  1. Concurrency — the executor's core pool is now set to maxConcurrentScans (core == max), so scans run in parallel instead of queueing behind a single thread.
  2. Symlinks — the walk now filters with NOFOLLOW_LINKS, so a symlink inside the root can't stream a host file outside the boundary to the scanner.
  3. Stuck RUNNING — the scan loop now catches all exceptions: a runtime failure on one file is recorded as an error finding instead of aborting, and a walk-level failure sets the job to FAILED rather than leaving it RUNNING.
  4. PENDING race — relaxed the startScan Javadoc to note the job may already be RUNNING by the time it returns.
  5. Unbounded map — added a scheduled eviction that prunes finished jobs past cloudpage.virus-scan.retention-minutes (default 60).

Added tests for the runtime-failure resilience and the eviction.

@DenizAltunkapan DenizAltunkapan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
DenizAltunkapan merged commit 47151c1 into Vault-Web:main Jul 14, 2026
1 check passed
@GabrielBBaldez

Copy link
Copy Markdown
Member Author

@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.

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.

Add Folder Virus Scan Endpoint

3 participants