ClamAV Security Dashboard v0.2.3

ClamAV Security Dashboard v0.2.3 — Created Exclusively for ZimaOS

We created the ClamAV Security Dashboard specifically for ZimaOS. It provides a simple way to discover storage disks, approve folders, monitor scans and safely manage detected threats directly from a browser.

The application has been tested on both a ZimaCube Pro and a ZimaBoard.

Main features

  • Automatic discovery of ZimaOS disks and folders under /media
  • Select and save only the folders you want scanned
  • Safe exclusions for AppData, Docker, backups, snapshots, virtual machines, recycle bins and other sensitive system folders
  • Live scan progress
  • Folder size, scanned size, remaining data, speed and ETA
  • Current file display
  • Scan history with detailed folder records
  • ClamAV engine health and signature version
  • Threat detection with the exact file path and signature
  • Confirmed quarantine action
  • Restore quarantined files to their original location
  • Permanent deletion after quarantine
  • Persistent scan and quarantine records
  • No automatic deletion or quarantine

Scanning does not modify files. Quarantine, restore and permanent deletion always require a separate confirmed action.

Security validation

The complete detection and quarantine workflow was tested using the official harmless EICAR antivirus test file.

The test confirmed:

  • Eicar-Test-Signature was detected
  • The original file was moved into quarantine
  • Its original path, SHA-256 hash, size and signature were recorded
  • Restore returned the file to its exact original location with the same SHA-256 hash
  • Re-quarantine and permanent deletion removed the file successfully
  • Historical quarantine records remained available

Installation

Open the ZimaOS App Store, select Install Custom App, switch to YAML, and paste the following configuration.

Check that port 8095 is available first:

ss -lnt | grep ':8095 ' || echo "PORT 8095 AVAILABLE"

Then import:

services:
  clamav-server:
    image: clamav/clamav:1.5_base
    restart: unless-stopped
    environment:
      TZ: ${TZ:-UTC}
      FRESHCLAM_CHECKS: "12"
      CLAMD_CONF_MaxThreads: "4"
      CLAMD_CONF_MaxFileSize: 512M
      CLAMD_CONF_MaxScanSize: 2G
      CLAMD_CONF_StreamMaxLength: 512M
    volumes:
      - /DATA/AppData/clamav-dashboard/clamav:/var/lib/clamav
    networks:
      - clamav-security

  clamav-dashboard:
    image: ghcr.io/jacko88888/clamav-dashboard:0.2.3
    restart: unless-stopped
    depends_on:
      clamav-server:
        condition: service_healthy
    ports:
      - "8095:8080"
    environment:
      TZ: ${TZ:-UTC}
      CLAMAV_HOST: clamav-server
      CLAMAV_PORT: "3310"
      DATA_DIR: /data
      MEDIA_ROOT: /host-media
      MAX_STREAM_BYTES: "536870912"
    volumes:
      - /DATA/AppData/clamav-dashboard/data:/data
      - /media:/host-media:rw
    networks:
      - clamav-security
    read_only: true
    tmpfs:
      - /tmp:rw,noexec,nosuid,size=256m
    security_opt:
      - no-new-privileges:true

networks:
  clamav-security:

x-casaos:
  title:
    custom: ClamAV Security Dashboard
    en_US: ClamAV Security Dashboard
  icon: https://raw.githubusercontent.com/Cisco-Talos/clamav/main/logo.png
  main: clamav-dashboard
  scheme: http
  port_map: "8095"
  index: /

If port 8095 is already occupied, change it in both locations:

ports:
  - "NEW_PORT:8080"
port_map: "NEW_PORT"

After installation, open:

http://YOUR-ZIMAOS-IP:8095

Select the folders you want to scan, click Save approved folders, and wait for the confirmation button to turn green. The approved folders will then appear in the scan selector.

Important permission information

The dashboard mounts /media with read/write access because quarantine and restore must move files from and back to their original locations.

Normal scanning is non-destructive. The application will not quarantine, restore or permanently delete anything without explicit confirmation.

Source code

GitHub repository:

Container image:

ghcr.io/jacko88888/clamav-dashboard:0.2.3

This is the first public release created specifically around the ZimaOS storage layout and Custom App installation workflow. Feedback and testing on other ZimaOS systems are welcome.

4 Likes

Hi gelbuilding,
I ran v0.2.3 on a ZimaCube Pro (ZimaOS 1.7.x) against real storage.
Technically a clean and usefull tool, the EICAR chain does exactly what you describe:
detect → quarantine (original gone, SHA-256 recorded)
→ restore (same hash back in place), and POST /api/* without X-Dashboard-Token correctly returns 403.
Four findings, with reproductions:

1. /api/storage returns HTTP 500 on hosts with ZimaOS cloud storage connected

discover_disk_roots() globs three levels deep across all of /media, which includes the
OneDrive/Google-Drive FUSE mounts. A locked folder there raises EIO and takes the whole endpoint down:

OSError: [Errno 5] Input/output error:
'/host-media/onedrive_<id>/<folder>/.zimaos_storage.json'
  File "/app/app.py", line 122, in discover_disk_roots
    markers.extend(MEDIA_ROOT.glob(pattern))

Repro (any ZimaOS with OneDrive connected, image digest sha256:454209e9…):

docker run -d --name cav -p 127.0.0.1:18095:8080 \
  -v /media:/host-media:ro -v /tmp/cav-data:/data \
  ghcr.io/jacko88888/clamav-dashboard:0.2.3
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18095/api/storage   # -> 500
docker logs cav | tail -20

Same result with the documented :rw mount. Suggestion: wrap each markers.extend(...) in
try/except OSError and skip the offending path.

2. The ZimaOS system disk cannot be scanned

/media/ZimaOS-HD is a symlink to /DATA, so inside the container it is a dangling link, and
.zimaos_storage.json only exists on the attached data disks:

ls -la /media/ZimaOS-HD              # -> ZimaOS-HD -> /DATA
docker exec cav ls -la /host-media/ZimaOS-HD   # dangling: /DATA does not exist in the container
docker exec cav python3 -c "from pathlib import Path; \
  print(sorted(str(p) for p in Path('/host-media').glob('*/.zimaos_storage.json')))"
# -> only the data disks, ZimaOS-HD is not among them

So Documents / Media / Downloads on the internal NVMe are never scanned, while the result still
reads “clean”. Adding - /DATA:/host-data:rw (or resolving the symlink) would cover it.

3. Restore changes file ownership to root

Quarantine moves across a filesystem boundary (/media/data on the app disk), so shutil.move
falls back to copy and the owner is lost. Measured on ext4:

# before: uid=1000 gid=1000 mode=640
# after quarantine + restore: uid=0 gid=0 mode=640
docker exec cav stat -c 'uid=%u gid=%g mode=%a' <file>

With mode 640 the original owner can no longer read their own file. Recording st_uid/st_gid at
quarantine time and os.chown() on restore would fix it.

4. Directory exclusions are substring-based and silent

EXCLUDED_TERMS matches any folder whose name contains backup, docker, database, appdata,
snapshot, borg or restore — so e.g. “Restore Photos” or “Docker Course” is skipped. Files get a
“skipped” entry, but directories are filtered out in os.walk without any record, so the user sees no
trace of it in the report. A skipped-folder list would make the “clean” result trustworthy.

Two smaller notes:

  • Throughput measured at ~15 MB/s (462 MB / 31 s, large media files, INSTREAM, one file at a time).
    For a multi-TB NAS that is a long run; scanning a few files concurrently would use MaxThreads: 4.
  • The forum post uses port 8095, the README and docker-compose.yml in the repo use 8099.

Keep up the good work!

Hi Holger,

Thank you for the exceptionally detailed testing and reproducible findings. Your review directly shaped the new v0.2.4 release.

All four main findings have now been addressed:

1. Cloud-storage/FUSE discovery failure

Storage discovery no longer uses unrestricted Path.glob() traversal across /media.

It now uses bounded directory scanning with per-path OSError handling. An inaccessible OneDrive, Google Drive or other FUSE location is skipped without causing /api/storage to return HTTP 500.

A regression test simulating an EIO failure was added and passes.

2. ZimaOS system disk support

The Compose configuration now mounts:

- /DATA:/host-data:rw

The dashboard maps this to a logical ZimaOS-HD disk. I verified this on my ZimaCube Pro, and the internal /DATA folders now appear correctly alongside the attached storage disks.

Users must still explicitly approve folders before scanning.

3. Ownership preservation after restore

Quarantine now records:

  • Original UID
  • Original GID
  • Original file mode
  • SHA-256 digest

Restore reapplies the original ownership and permissions and verifies them before deleting the quarantined copy.

A regression test for ownership and mode preservation was added and passes.

4. Exact exclusions and visible skipped folders

Substring matching has been removed.

Names such as Restore Photos, Docker Course and database notes are no longer excluded merely because they contain a protected word.

Safety exclusions now use exact directory names and recognised virtual-disk image extensions. Skipped files and directories are also recorded with the reason and displayed in the scan details.

Additional changes

  • Forum, README and Compose port standardised to 8099
  • Five automated regression tests added to the GitHub publication workflow
  • /media and /DATA storage discovery verified on my ZimaCube Pro
  • Existing approvals, scan history and quarantine records migrate into v0.2.4
  • GitHub container publication completed successfully

The updated image is:

ghcr.io/jacko88888/clamav-dashboard:0.2.4

Repository:

The parallel-scanning suggestion is not implemented yet. The current scanner still processes one file at a time through INSTREAM. I have kept that as a documented performance limitation rather than rushing a concurrency change that could affect cancellation, progress reporting or ClamAV load control.

Thank you again, Holger. This is exactly the type of technical review that helps turn a working project into a trustworthy ZimaOS application.

1 Like

Folder selection

The dashboard works well overall, but I think the folder selection should be more flexible.

At the moment, it is not possible to select individual folders for scanning. On a large NAS, this can result in a scan of the entire storage tree. In my case, this means more than 120 TB, including storage and other mounted drives that I don’t necessarily want to scan.

It would be much better to be able to select individual disks, folders, or subfolders independently—for example, scan only my Movies folder on one specific drive without scanning the entire storage.

This would make the dashboard much more practical for large ZimaOS systems.

2 Likes

Hi gelbuilding,

I noticed one more important point while testing the ClamAV Security Dashboard. I cannot currently see or select the Apps/AppData folders for scanning.

I think these areas should be accessible as optional scan targets, because they could be an important security point on a ZimaOS system. For example, when a Docker application is installed, Docker downloads images and potentially other files from the Internet. It would be useful to be able to scan the related AppData and Docker storage for malware.

I don’t think the entire Docker storage should necessarily be scanned by default, but having the option to select specific AppData folders or Docker-related directories would be very useful. Ideally, the user should be able to decide exactly which application folder or Docker data directory should be scanned.

This would also fit nicely with the folder-selection improvement I mentioned earlier.