How to virus scan file users upload using ClamAV
Tags: virus, node, javascript, security
Published: 2022-09-20 14:17:33


[Clamav](https://docs.clamav.net/Introduction.html) is a popular open source virus scanning solution.  Part of the server application is several gigs worth of virus definition files.  Because of this and our micro services architecture, it made the most sense to setup a dedicated server to host ClamAV.  This also allows me to share this one instance of ClamAV across several solutions needing virus scanning capabilities.

1. Create a  [Linode with docker](https://www.linode.com/marketplace/apps/linode/docker/) 
2. In the config, leave all the advanced docker options blank.
3. Click to create the Linode
4. After it is provisioned, open the console
5. Login as the root user
6. Run the following (check the [latest ClamAV docker container versions](https://hub.docker.com/r/clamav/clamav/tags)):
```
docker pull clamav/clamav:0.105.1
docker volume create clam_db
docker run --interactive  --tty --restart=always --name "clam_container_01" --publish 3310:3310 \
    --publish 7357:7357 \
 --mount source=clam_db,target=/var/lib/clamav \
    --env 'CLAMAV_NO_FRESHCLAMD=true' \
 clamav/clamav:0.105.1
```

This pulls the ClamAV docker container, creates a persistent docker volume, runs the ClamAV docker container with ports 7357 and 3310 exposed, sets the docker container to automatically restart when the Linode is restarted, and keeps the virus definitions in the persistent docker volume called clam_db.

After completing this I used a package to help scan for viruses in nodejs.
https://www.npmjs.com/package/clamscan

For example here is what worked for me:

1. Run `npm install clamscan`
2. Create a file called virus_scan.js:
```
const NodeClam = require(‘clamscan’)
const ClamScan = new NodeClam().init({
  removeInfected: true, // If true, removes infected files
  debugMode: false, // Whether or not to log info/debug/error msgs to the console
  scanRecursively: true, // If true, deep scan folders recursively
  clamdscan: {
    host: ‘111.111.111.11’, // IP of host to connect to TCP interface
    port: 3310, // Port of host to use when connecting via TCP interface
    timeout: 60000, // Timeout for scanning files
    localFallback: false, // Use local preferred binary to scan if socket/tcp fails
    multiscan: true, // Scan using all available cores! Yay!
    active: true, // If true, this module will consider using the clamdscan binary
    bypassTest: false, // Check to see if socket is available when applicable
  },
  preference: ‘clamdscan’, // If clamdscan is found and active, it will be used by default
})

// Get instance by resolving ClamScan promise object
ClamScan.then(async (clamscan) => {
  try {
    // You can re-use the `clamscan` object as many times as you want
    const version = await clamscan.getVersion()
    console.log(`ClamAV Version: ${version}`)
    const { isInfected, file, viruses } = await clamscan.isInfected(‘./lib/virus.txt’)
    if (isInfected) console.log(`${file} is infected with ${viruses}!`)
  } catch (err) {
    // Handle any errors raised by the code in the try block
    throw err
  }
}).catch(console.error)

```

Contents of `virus.txt` includes a virus signature to help make sure everything is working:

```
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
```

When running `node virus_scan.js` you should see 
```
null is infected with Eicar-Signature!
```

From here you should be able to imagine how to scan files your users upload.  I will not go into detail since that is going to be fairly unique to your use case.