Repository files navigation

NodeJS Clamscan Virus Scanning Utility

NPM VersionNPM DownloadsNode.js VersionTest Suite

Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.

!!IMPORTANT

If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.

All older versions in NPM have been deprecated.

Version 1.0.0 Information

If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the release notes as there are some breaking changes (but also some awesome new features!).

Table of Contents

Dependencies

To use local binary method of scanning

You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.

Fedora-based distros:

sudo yum install clamav

Debian-based distros:

sudo apt-get install clamav clamav-daemon

For OS X, you can install clamav with brew:

sudo brew install clamav

To use ClamAV using TCP sockets

You will need access to either:

  1. A local UNIX Domain socket for a local instance of clamd
  1. A local/remote clamd daemon
  • Must know the port the daemon is running on
  • If running on remote server, you must have the IP address/domain name
  • If running on remote server, it's firewall must have the appropriate TCP port(s) open
  • Make sure clamd is running on your local/remote server

NOTE: This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).

How to Install

npm install clamscan

License Info

Licensed under the MIT License:

Getting Started

All of the values listed in the example below represent the default values for their respective configuration item.

You can simply do this:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init();

And, you'll be good to go.

BUT: If you want more control, you can specify all sorts of options.

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: false,// If true, removes infected filesquarantineInfected: false,// False: Don't quarantine, Path: Moves files to this place.scanLog: null,// Path to a writeable log file to write scan results intodebugMode: false,// Whether or not to log info/debug/error msgs to the consolefileList: null,// path to file containing list of files to scan (for scanFiles method)scanRecursively: true,// If true, deep scan folders recursivelyclamscan: {path: '/usr/bin/clamscan',// Path to clamscan binary on your serverdb: null,// Path to a custom virus definition databasescanArchives: true,// If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)active: true// If true, this module will consider using the clamscan binary},clamdscan: {socket: false,// Socket file for connecting via TCPhost: false,// IP of host to connect to TCP interfaceport: false,// Port of host to use when connecting via TCP interfacetimeout: 60000,// Timeout for scanning fileslocalFallback: true,// Use local preferred binary to scan if socket/tcp failspath: '/usr/bin/clamdscan',// Path to the clamdscan binary on your serverconfigFile: null,// Specify config file if it's in an unusual placemultiscan: true,// Scan using all available cores! Yay!reloadDb: false,// If true, will re-load the DB on every call (slow)active: true,// If true, this module will consider using the clamdscan binarybypassTest: false,// Check to see if socket is available when applicabletls: false,// Use plaintext TCP to connect to clamd},preference: 'clamdscan'// If clamdscan is found and active, it will be used by default});

Here is a non-default values example (to help you get an idea of what proper-looking values could be):

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: true,// Removes files if they are infectedquarantineInfected: '~/infected/',// Move file here. removeInfected must be FALSE, though.scanLog: '/var/log/node-clam',// You're a detail-oriented security professional.debugMode: true,// This will put some debug info in your js consolefileList: '/home/webuser/scanFiles.txt',// path to file containing list of files to scanscanRecursively: false,// Choosing false here will save some CPU cyclesclamscan: {path: '/usr/bin/clam',// I dunno, maybe your clamscan is just call "clam"scanArchives: false,// Choosing false here will save some CPU cyclesdb: '/usr/bin/better_clam_db',// Path to a custom virus definition databaseactive: false// you don't want to use this at all because it's evil},clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',// This is pretty typicalhost: '127.0.0.1',// If you want to connect locally but not through socketport: 12345,// Because, why nottimeout: 300000,// 5 minuteslocalFallback: false,// Do no fail over to binary-method of scanningpath: '/bin/clamdscan',// Special path to the clamdscan binary on your serverconfigFile: '/etc/clamd.d/daemon.conf',// A fairly typical config locationmultiscan: false,// You hate speed and multi-threaded awesome-saucereloadDb: true,// You want your scans to run slow like with clamscanactive: false,// you don't want to use this at all because it's evilbypassTest: true,// Don't check to see if socket is available. You should probably never set this to true.tls: true,// Connect to clamd over TLS},preference: 'clamscan'// If clamscan is found and active, it will be used by default});

NOTE: If a valid port is provided but no host value is provided, the clamscan will assume 'localhost' for host.

A note about using this module via sockets or TCP

As of version v1.0.0, this module supports communication with a local or remote ClamAV daemon through Unix Domain sockets or a TCP host/port combo. If you supply both in your configuration object, the UNIX Domain socket option will be used. The module will not not fallback to using the alternative Host/Port method. If you wish to connect via Host/Port and not a Socket, please either omit the socket property in the config object or use socket: null.

If you specify a valid clamscan/clamdscan binary in your config and you set clamdscan.localFallback: true in your config, this module will fallback to the traditional way this module has worked--using a binary directly/locally.

Also, there are some caveats to using the socket/tcp based approach:

  • The following configuration items are not honored (unless the module falls back to binary method):

    • removeInfected - remote clamd service config will dictate this
    • quarantineInfected - remote clamd service config will dictate this
    • scanLog - remote clamd service config will dictate this
    • fileList - this simply won't be available
    • clamscan.db - only available on fallback
    • clamscan.scanArchives - only available on fallback
    • clamscan.path - only available on fallback
    • clamdscan.configFile - only available on fallback
    • clamdscan.path - only available on fallback

Basic Usage Example

For the sake of brevity, all the examples in the API section will be shortened to just the relevant parts related specifically to that example. In those examples, we'll assume you already have an instance of the clamscan object. Since initializing the module returns a promise, you'll have to resolve that promise to get an instance of the clamscan object.

Below is the full example of how you could get that instance and run some methods:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init(options);// Get instance by resolving ClamScan promise objectClamScan.then(asyncclamscan=>{try{// You can re-use the `clamscan` object as many times as you wantconstversion=awaitclamscan.getVersion();console.log(`ClamAV Version: ${version}`);const{isInfected, file, viruses}=awaitclamscan.isInfected('/some/file.zip');if(isInfected)console.log(`${file} is infected with ${viruses}!`);}catch(err){// Handle any errors raised by the code in the try block}}).catch(err=>{// Handle errors that may have occurred during initialization});

If you're writing your code within an async function, getting an instance can be one less step:

constNodeClam=require('clamscan');asyncsome_function(){try{// Get instance by resolving ClamScan promise objectconstclamscan=awaitnewNodeClam().init(options);const{goodFiles, badFiles}=awaitclamscan.scanDir('/foo/bar');}catch(err){// Handle any errors raised by the code in the try block}}some_function();

API

Complete/functional examples for various use-cases can be found in the examples folder.

.getVersion([callback])

This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.

Parameters

  • callback (function) (optional) Will be called when the scan is complete. It receives 2 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • version (string) The version of the clamav server you're interfacing with

Returns

  • Promise

    • Promise resolution returns: version (string) The version of the clamav server you're interfacing with

Callback Example

clamscan.getVersion((err,version)=>{if(err)returnconsole.error(err);console.log(`ClamAV Version: ${version}`);});

Promise Example

clamscan.getVersion().then(version=>{console.log(`ClamAV Version: ${version}`);}).catch(err=>{console.error(err);});

.isInfected(filePath[,callback])

This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.

Alias

.scan_file

Parameters

  • filePath (string) Represents a path to the file to be scanned.

  • callback (function) (optional) Will be called when the scan is complete. It takes 3 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • file (string) The original filePath passed into the isInfected method.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
    • viruses (array) An array of any viruses found in the scanned file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) The original filePath passed into the isInfected method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Callback Example

clamscan.isInfected('/a/picture/for_example.jpg',(err,file,isInfected,viruses)=>{if(err)returnconsole.error(err);if(isInfected){console.log(`${file} is infected with ${viruses.join(', ')}.`);}});

Promise Example

clamscan.isInfected('/a/picture/for_example.jpg').then(result=>{const{file, isInfected, viruses}=result;if(isInfected)console.log(`${file} is infected with ${viruses.join(', ')}.`);}).then(err=>{console.error(err);})

Async/Await Example

const{file, isInfected, viruses}=awaitclamscan.isInfected('/a/picture/for_example.jpg');

.scanDir(dirPath[,endCallback[,fileCallback]])

Allows you to scan an entire directory for infected files. This obeys your recursive option even for clamdscan which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to scanFiles.

TL;DR: For maximum speed, don't supply a fileCallback.

If you choose to supply a fileCallback, the scan will run a little bit slower (depending on number of files to be scanned) for clamdscan. If you are using clamscan, while it will work, I'd highly advise you to NOT pass a fileCallback... it will run incredibly slow.

NOTE

The goodFiles parameter of the endCallback callback in this method will only contain the directory that was scanned in allbut the following scenarios:

  • A fileCallback callback is provided, and scanRecursively is set to true.
  • The scanner is set to clamdscan and scanRecursively is set to false.
  • The scanned directory contains 1 or more viruses. In this case, the goodFiles array will be empty.

There will, however, be a total count of the good files which is calculated by determining the total number of files scanned and subtracting the number of bad files from that count. We simply can't provide a list of all good files due to the potential large memory usage implications of scanning a directory with, for example, millions of files.

Parameters

  • dirPath (string) (required) Full path to the directory to scan.

  • endCallback (function) (optional) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:

    • err (object) A standard javascript Error object (null if no error)
    • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
    • badFiles (array) List of the full paths to all files that are infected.
    • viruses (array) List of all the viruses found (feature request: associate to the bad files).
    • numGoodFiles (number) Number of files that were found to be clean.
  • fileCallback (function) (optional) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard Javascript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • path (string) The original dir_path passed into the scanDir method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).
      • numGoodFiles (number) Number of files that were found to be clean.

Callback Example

clamscan.scanDir('/some/path/to/scan',(err,goodFiles,badFiles,viruses,numGoodFiles){if(err)returnconsole.error(err);if(badFiles.length>0){console.log(`${path} was infected. The offending files (${badFiles.join(', ')}) have been quarantined.`);console.log(`Viruses Found: ${viruses.join(', ')}`);}else{console.log(`${goodFiles[0]} looks good! ${numGoodFiles} file scanned and no problems found!.`);}});

Promise Example

clamscan.scanDir('/some/path/to/scan').then(results=>{const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=results;//...}).catch(err=>{returnconsole.error(err);});

Async/Await Example

const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=awaitclamscan.scanDir('/some/path/to/scan');

.scanFiles(files[,endCallback[,fileCallback]])

This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for isInfected that simplifies the process of scanning many files or directories.

Parameters

  • files (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a fileList config option. If neither is found, the method will throw an error.

  • endCallback (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • goodFiles (array) List of the full paths to all files that are clean.
    • badFiles (array) List of the full paths to all files that are infected.
  • fileCallback (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • goodFiles (array) List of the full paths to all files that are clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • errors (object) Per-file errors keyed by the filename in which the error happened. (ex. {'foo.txt': Error})
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).

Callback Example

constscan_status={good: 0,bad: 0};constfiles=['/path/to/file/1.jpg','/path/to/file/2.mov','/path/to/file/3.rb'];clamscan.scanFiles(files,(err,goodFiles,badFiles,viruses)=>{if(err)returnconsole.error(err);if(badFiles.length>0){console.log({msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
badFiles,
goodFiles,
viruses,});}else{res.send({msg: "Everything looks good! No problems here!."});}},(err,file,isInfected,viruses)=>{;(isInfected ? scan_status.bad++ : scan_status.good++);console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);console.log('Scan Status: ',`${(scan_status.bad+scan_status.good)}/${files.length}`);});

Promise Example

Note: There is currently no way to get per-file notifications with the Promise API.

clamscan.scanFiles(files).then(results=>{const{ goodFiles, badFiles, errors, viruses }=results;// ...}).catch(err=>{console.error(err);})

Async/Await Example

const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles(files);

Scanning files listed in fileList

If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.

Files List Document:

/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb

Script:

constClamScan=newNodeClam().init({fileList: '/path/to/fileList.txt'});ClamScan.then(asyncclamscan=>{// Supply nothing to first parameter to use `fileList`const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles();});

.scanStream(stream[,callback])

This method allows you to scan a binary stream. NOTE: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.

Parameters

  • stream (stream) A readable stream object

  • callback (function) (optional) Will be called after the stream has been scanned (or attempted to be scanned):

    • err (object or null) A standard JavaScript Error object (null if no error)
    • isInfected (boolean) True: Stream is infected; False: Stream is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) NULL as no file path can be provided with the stream
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});constReadable=require('stream').Readable;constrs=Readable();rs.push('foooooo');rs.push('barrrrr');rs.push(null);clamscan.scanStream(stream,(err,{isInfected. viruses })=>{if(err)returnconsole.error(err);if(isInfected)returnconsole.log('Stream is infected! Booo!',viruses);console.log('Stream is not infected! Yay!');});

Promise Example:

clamscan.scanStream(stream).then(({isInfected})=>{if(isInfected)returnconsole.log("Stream is infected! Booo!");console.log("Stream is not infected! Yay!");}).catch(err=>{console.error(err);};

Promise Example:

const{ isInfected, viruses }=awaitclamscan.scanStream(stream);

.passthrough()

The passthrough method returns a PassthroughStream object which allows you pipe a ReadbleStream through it and on to another output. In the case of this module's passthrough implementation, it's actually forking the data to also go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to the output if that chunk was successfully sent to and received by ClamAV. The PassthroughStream object returned from this method has a special event that is emitted when ClamAV finishes scanning the streamed data so that you can decide if there's anything you need to do with the final output destination (ex. delete a file or S3 object).

In typical, non-passthrough setups, a file is uploaded to the local filesytem and then subsequently scanned. With that setup, you have to wait for the upload to complete and then wait again for the scan to complete. Using this module's passthrough method, you could theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to any WriteableStream output (examples: filesystem, S3, gzip, etc...).

As for these theoretical gains, your mileage my vary and I'd love to hear feedback on this to see where things can still be improved.

Please note that this method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (a practical working example can be found in the examples directory of this module):

Example

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});// For example's sake, we're using the Axios moduleconstaxios=require('Axios');// Get a readable stream for a URL requestconstinput=axios.get(some_url);// Create a writable stream to a local fileconstoutput=fs.createWriteStream(some_local_file);// Get instance of this module's PassthroughStream objectconstav=clamscan.passthrough();// Send output of Axios stream to ClamAV.// Send output of Axios to `some_local_file` if ClamAV receives data successfullyinput.pipe(av).pipe(output);// What happens when scan is completedav.on('scan-complete',result=>{const{ isInfected, viruses }=result;// Do stuff if you want});// What happens when data has been fully written to `output`output.on('finish',()=>{// Do stuff if you want});// NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications

.ping()

This method checks to see if the remote/local socket is working. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method can be used for healthcheck purposes and is already implicitly used during scan.

Parameters

  • callback (function) (optional) Will be called after the ping:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • client (object) A copy of the Socket/TCP client

Returns

  • Promise

    • Promise resolution returns: client (object): A copy of the Socket/TCP client

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});clamscan.ping((err,client)=>{if(err)returnconsole.error(err);console.log('ClamAV is still working!');client.end();});

Promise Example:

clamscan.ping().then((client)=>{console.log('ClamAV is still working!');client.end();}).catch(err=>{console.error(err);};

Promise Example:

constclient=awaitclamscan.ping();client.end();

Contribute

Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.

Resources used to help develop this module

About

A robust ClamAV virus scanning library supporting scanning files, directories, and streams with local sockets, local/remote TCP, and local clamscan/clamdscan binaries (with failover).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

NodeJS Clamscan Virus Scanning Utility

NPM VersionNPM DownloadsNode.js VersionTest Suite

Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.

!!IMPORTANT

If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.

All older versions in NPM have been deprecated.

Version 1.0.0 Information

If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the release notes as there are some breaking changes (but also some awesome new features!).

Table of Contents

Dependencies

To use local binary method of scanning

You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.

Fedora-based distros:

sudo yum install clamav

Debian-based distros:

sudo apt-get install clamav clamav-daemon

For OS X, you can install clamav with brew:

sudo brew install clamav

To use ClamAV using TCP sockets

You will need access to either:

  1. A local UNIX Domain socket for a local instance of clamd
  1. A local/remote clamd daemon
  • Must know the port the daemon is running on
  • If running on remote server, you must have the IP address/domain name
  • If running on remote server, it's firewall must have the appropriate TCP port(s) open
  • Make sure clamd is running on your local/remote server

NOTE: This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).

How to Install

npm install clamscan

License Info

Licensed under the MIT License:

Getting Started

All of the values listed in the example below represent the default values for their respective configuration item.

You can simply do this:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init();

And, you'll be good to go.

BUT: If you want more control, you can specify all sorts of options.

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: false,// If true, removes infected filesquarantineInfected: false,// False: Don't quarantine, Path: Moves files to this place.scanLog: null,// Path to a writeable log file to write scan results intodebugMode: false,// Whether or not to log info/debug/error msgs to the consolefileList: null,// path to file containing list of files to scan (for scanFiles method)scanRecursively: true,// If true, deep scan folders recursivelyclamscan: {path: '/usr/bin/clamscan',// Path to clamscan binary on your serverdb: null,// Path to a custom virus definition databasescanArchives: true,// If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)active: true// If true, this module will consider using the clamscan binary},clamdscan: {socket: false,// Socket file for connecting via TCPhost: false,// IP of host to connect to TCP interfaceport: false,// Port of host to use when connecting via TCP interfacetimeout: 60000,// Timeout for scanning fileslocalFallback: true,// Use local preferred binary to scan if socket/tcp failspath: '/usr/bin/clamdscan',// Path to the clamdscan binary on your serverconfigFile: null,// Specify config file if it's in an unusual placemultiscan: true,// Scan using all available cores! Yay!reloadDb: false,// If true, will re-load the DB on every call (slow)active: true,// If true, this module will consider using the clamdscan binarybypassTest: false,// Check to see if socket is available when applicabletls: false,// Use plaintext TCP to connect to clamd},preference: 'clamdscan'// If clamdscan is found and active, it will be used by default});

Here is a non-default values example (to help you get an idea of what proper-looking values could be):

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: true,// Removes files if they are infectedquarantineInfected: '~/infected/',// Move file here. removeInfected must be FALSE, though.scanLog: '/var/log/node-clam',// You're a detail-oriented security professional.debugMode: true,// This will put some debug info in your js consolefileList: '/home/webuser/scanFiles.txt',// path to file containing list of files to scanscanRecursively: false,// Choosing false here will save some CPU cyclesclamscan: {path: '/usr/bin/clam',// I dunno, maybe your clamscan is just call "clam"scanArchives: false,// Choosing false here will save some CPU cyclesdb: '/usr/bin/better_clam_db',// Path to a custom virus definition databaseactive: false// you don't want to use this at all because it's evil},clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',// This is pretty typicalhost: '127.0.0.1',// If you want to connect locally but not through socketport: 12345,// Because, why nottimeout: 300000,// 5 minuteslocalFallback: false,// Do no fail over to binary-method of scanningpath: '/bin/clamdscan',// Special path to the clamdscan binary on your serverconfigFile: '/etc/clamd.d/daemon.conf',// A fairly typical config locationmultiscan: false,// You hate speed and multi-threaded awesome-saucereloadDb: true,// You want your scans to run slow like with clamscanactive: false,// you don't want to use this at all because it's evilbypassTest: true,// Don't check to see if socket is available. You should probably never set this to true.tls: true,// Connect to clamd over TLS},preference: 'clamscan'// If clamscan is found and active, it will be used by default});

NOTE: If a valid port is provided but no host value is provided, the clamscan will assume 'localhost' for host.

A note about using this module via sockets or TCP

As of version v1.0.0, this module supports communication with a local or remote ClamAV daemon through Unix Domain sockets or a TCP host/port combo. If you supply both in your configuration object, the UNIX Domain socket option will be used. The module will not not fallback to using the alternative Host/Port method. If you wish to connect via Host/Port and not a Socket, please either omit the socket property in the config object or use socket: null.

If you specify a valid clamscan/clamdscan binary in your config and you set clamdscan.localFallback: true in your config, this module will fallback to the traditional way this module has worked--using a binary directly/locally.

Also, there are some caveats to using the socket/tcp based approach:

  • The following configuration items are not honored (unless the module falls back to binary method):

    • removeInfected - remote clamd service config will dictate this
    • quarantineInfected - remote clamd service config will dictate this
    • scanLog - remote clamd service config will dictate this
    • fileList - this simply won't be available
    • clamscan.db - only available on fallback
    • clamscan.scanArchives - only available on fallback
    • clamscan.path - only available on fallback
    • clamdscan.configFile - only available on fallback
    • clamdscan.path - only available on fallback

Basic Usage Example

For the sake of brevity, all the examples in the API section will be shortened to just the relevant parts related specifically to that example. In those examples, we'll assume you already have an instance of the clamscan object. Since initializing the module returns a promise, you'll have to resolve that promise to get an instance of the clamscan object.

Below is the full example of how you could get that instance and run some methods:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init(options);// Get instance by resolving ClamScan promise objectClamScan.then(asyncclamscan=>{try{// You can re-use the `clamscan` object as many times as you wantconstversion=awaitclamscan.getVersion();console.log(`ClamAV Version: ${version}`);const{isInfected, file, viruses}=awaitclamscan.isInfected('/some/file.zip');if(isInfected)console.log(`${file} is infected with ${viruses}!`);}catch(err){// Handle any errors raised by the code in the try block}}).catch(err=>{// Handle errors that may have occurred during initialization});

If you're writing your code within an async function, getting an instance can be one less step:

constNodeClam=require('clamscan');asyncsome_function(){try{// Get instance by resolving ClamScan promise objectconstclamscan=awaitnewNodeClam().init(options);const{goodFiles, badFiles}=awaitclamscan.scanDir('/foo/bar');}catch(err){// Handle any errors raised by the code in the try block}}some_function();

API

Complete/functional examples for various use-cases can be found in the examples folder.

.getVersion([callback])

This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.

Parameters

  • callback (function) (optional) Will be called when the scan is complete. It receives 2 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • version (string) The version of the clamav server you're interfacing with

Returns

  • Promise

    • Promise resolution returns: version (string) The version of the clamav server you're interfacing with

Callback Example

clamscan.getVersion((err,version)=>{if(err)returnconsole.error(err);console.log(`ClamAV Version: ${version}`);});

Promise Example

clamscan.getVersion().then(version=>{console.log(`ClamAV Version: ${version}`);}).catch(err=>{console.error(err);});

.isInfected(filePath[,callback])

This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.

Alias

.scan_file

Parameters

  • filePath (string) Represents a path to the file to be scanned.

  • callback (function) (optional) Will be called when the scan is complete. It takes 3 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • file (string) The original filePath passed into the isInfected method.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
    • viruses (array) An array of any viruses found in the scanned file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) The original filePath passed into the isInfected method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Callback Example

clamscan.isInfected('/a/picture/for_example.jpg',(err,file,isInfected,viruses)=>{if(err)returnconsole.error(err);if(isInfected){console.log(`${file} is infected with ${viruses.join(', ')}.`);}});

Promise Example

clamscan.isInfected('/a/picture/for_example.jpg').then(result=>{const{file, isInfected, viruses}=result;if(isInfected)console.log(`${file} is infected with ${viruses.join(', ')}.`);}).then(err=>{console.error(err);})

Async/Await Example

const{file, isInfected, viruses}=awaitclamscan.isInfected('/a/picture/for_example.jpg');

.scanDir(dirPath[,endCallback[,fileCallback]])

Allows you to scan an entire directory for infected files. This obeys your recursive option even for clamdscan which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to scanFiles.

TL;DR: For maximum speed, don't supply a fileCallback.

If you choose to supply a fileCallback, the scan will run a little bit slower (depending on number of files to be scanned) for clamdscan. If you are using clamscan, while it will work, I'd highly advise you to NOT pass a fileCallback... it will run incredibly slow.

NOTE

The goodFiles parameter of the endCallback callback in this method will only contain the directory that was scanned in allbut the following scenarios:

  • A fileCallback callback is provided, and scanRecursively is set to true.
  • The scanner is set to clamdscan and scanRecursively is set to false.
  • The scanned directory contains 1 or more viruses. In this case, the goodFiles array will be empty.

There will, however, be a total count of the good files which is calculated by determining the total number of files scanned and subtracting the number of bad files from that count. We simply can't provide a list of all good files due to the potential large memory usage implications of scanning a directory with, for example, millions of files.

Parameters

  • dirPath (string) (required) Full path to the directory to scan.

  • endCallback (function) (optional) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:

    • err (object) A standard javascript Error object (null if no error)
    • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
    • badFiles (array) List of the full paths to all files that are infected.
    • viruses (array) List of all the viruses found (feature request: associate to the bad files).
    • numGoodFiles (number) Number of files that were found to be clean.
  • fileCallback (function) (optional) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard Javascript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • path (string) The original dir_path passed into the scanDir method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).
      • numGoodFiles (number) Number of files that were found to be clean.

Callback Example

clamscan.scanDir('/some/path/to/scan',(err,goodFiles,badFiles,viruses,numGoodFiles){if(err)returnconsole.error(err);if(badFiles.length>0){console.log(`${path} was infected. The offending files (${badFiles.join(', ')}) have been quarantined.`);console.log(`Viruses Found: ${viruses.join(', ')}`);}else{console.log(`${goodFiles[0]} looks good! ${numGoodFiles} file scanned and no problems found!.`);}});

Promise Example

clamscan.scanDir('/some/path/to/scan').then(results=>{const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=results;//...}).catch(err=>{returnconsole.error(err);});

Async/Await Example

const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=awaitclamscan.scanDir('/some/path/to/scan');

.scanFiles(files[,endCallback[,fileCallback]])

This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for isInfected that simplifies the process of scanning many files or directories.

Parameters

  • files (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a fileList config option. If neither is found, the method will throw an error.

  • endCallback (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • goodFiles (array) List of the full paths to all files that are clean.
    • badFiles (array) List of the full paths to all files that are infected.
  • fileCallback (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • goodFiles (array) List of the full paths to all files that are clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • errors (object) Per-file errors keyed by the filename in which the error happened. (ex. {'foo.txt': Error})
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).

Callback Example

constscan_status={good: 0,bad: 0};constfiles=['/path/to/file/1.jpg','/path/to/file/2.mov','/path/to/file/3.rb'];clamscan.scanFiles(files,(err,goodFiles,badFiles,viruses)=>{if(err)returnconsole.error(err);if(badFiles.length>0){console.log({msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
badFiles,
goodFiles,
viruses,});}else{res.send({msg: "Everything looks good! No problems here!."});}},(err,file,isInfected,viruses)=>{;(isInfected ? scan_status.bad++ : scan_status.good++);console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);console.log('Scan Status: ',`${(scan_status.bad+scan_status.good)}/${files.length}`);});

Promise Example

Note: There is currently no way to get per-file notifications with the Promise API.

clamscan.scanFiles(files).then(results=>{const{ goodFiles, badFiles, errors, viruses }=results;// ...}).catch(err=>{console.error(err);})

Async/Await Example

const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles(files);

Scanning files listed in fileList

If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.

Files List Document:

/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb

Script:

constClamScan=newNodeClam().init({fileList: '/path/to/fileList.txt'});ClamScan.then(asyncclamscan=>{// Supply nothing to first parameter to use `fileList`const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles();});

.scanStream(stream[,callback])

This method allows you to scan a binary stream. NOTE: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.

Parameters

  • stream (stream) A readable stream object

  • callback (function) (optional) Will be called after the stream has been scanned (or attempted to be scanned):

    • err (object or null) A standard JavaScript Error object (null if no error)
    • isInfected (boolean) True: Stream is infected; False: Stream is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) NULL as no file path can be provided with the stream
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});constReadable=require('stream').Readable;constrs=Readable();rs.push('foooooo');rs.push('barrrrr');rs.push(null);clamscan.scanStream(stream,(err,{isInfected. viruses })=>{if(err)returnconsole.error(err);if(isInfected)returnconsole.log('Stream is infected! Booo!',viruses);console.log('Stream is not infected! Yay!');});

Promise Example:

clamscan.scanStream(stream).then(({isInfected})=>{if(isInfected)returnconsole.log("Stream is infected! Booo!");console.log("Stream is not infected! Yay!");}).catch(err=>{console.error(err);};

Promise Example:

const{ isInfected, viruses }=awaitclamscan.scanStream(stream);

.passthrough()

The passthrough method returns a PassthroughStream object which allows you pipe a ReadbleStream through it and on to another output. In the case of this module's passthrough implementation, it's actually forking the data to also go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to the output if that chunk was successfully sent to and received by ClamAV. The PassthroughStream object returned from this method has a special event that is emitted when ClamAV finishes scanning the streamed data so that you can decide if there's anything you need to do with the final output destination (ex. delete a file or S3 object).

In typical, non-passthrough setups, a file is uploaded to the local filesytem and then subsequently scanned. With that setup, you have to wait for the upload to complete and then wait again for the scan to complete. Using this module's passthrough method, you could theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to any WriteableStream output (examples: filesystem, S3, gzip, etc...).

As for these theoretical gains, your mileage my vary and I'd love to hear feedback on this to see where things can still be improved.

Please note that this method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (a practical working example can be found in the examples directory of this module):

Example

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});// For example's sake, we're using the Axios moduleconstaxios=require('Axios');// Get a readable stream for a URL requestconstinput=axios.get(some_url);// Create a writable stream to a local fileconstoutput=fs.createWriteStream(some_local_file);// Get instance of this module's PassthroughStream objectconstav=clamscan.passthrough();// Send output of Axios stream to ClamAV.// Send output of Axios to `some_local_file` if ClamAV receives data successfullyinput.pipe(av).pipe(output);// What happens when scan is completedav.on('scan-complete',result=>{const{ isInfected, viruses }=result;// Do stuff if you want});// What happens when data has been fully written to `output`output.on('finish',()=>{// Do stuff if you want});// NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications

.ping()

This method checks to see if the remote/local socket is working. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method can be used for healthcheck purposes and is already implicitly used during scan.

Parameters

  • callback (function) (optional) Will be called after the ping:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • client (object) A copy of the Socket/TCP client

Returns

  • Promise

    • Promise resolution returns: client (object): A copy of the Socket/TCP client

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});clamscan.ping((err,client)=>{if(err)returnconsole.error(err);console.log('ClamAV is still working!');client.end();});

Promise Example:

clamscan.ping().then((client)=>{console.log('ClamAV is still working!');client.end();}).catch(err=>{console.error(err);};

Promise Example:

constclient=awaitclamscan.ping();client.end();

Contribute

Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.

Resources used to help develop this module

About

A robust ClamAV virus scanning library supporting scanning files, directories, and streams with local sockets, local/remote TCP, and local clamscan/clamdscan binaries (with failover).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NodeJS Clamscan Virus Scanning Utility

NPM VersionNPM DownloadsNode.js VersionTest Suite

Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.

!!IMPORTANT

If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.

All older versions in NPM have been deprecated.

Version 1.0.0 Information

If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the release notes as there are some breaking changes (but also some awesome new features!).

Table of Contents

Dependencies

To use local binary method of scanning

You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.

Fedora-based distros:

sudo yum install clamav

Debian-based distros:

sudo apt-get install clamav clamav-daemon

For OS X, you can install clamav with brew:

sudo brew install clamav

To use ClamAV using TCP sockets

You will need access to either:

  1. A local UNIX Domain socket for a local instance of clamd
  1. A local/remote clamd daemon
  • Must know the port the daemon is running on
  • If running on remote server, you must have the IP address/domain name
  • If running on remote server, it's firewall must have the appropriate TCP port(s) open
  • Make sure clamd is running on your local/remote server

NOTE: This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).

How to Install

npm install clamscan

License Info

Licensed under the MIT License:

Getting Started

All of the values listed in the example below represent the default values for their respective configuration item.

You can simply do this:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init();

And, you'll be good to go.

BUT: If you want more control, you can specify all sorts of options.

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: false,// If true, removes infected filesquarantineInfected: false,// False: Don't quarantine, Path: Moves files to this place.scanLog: null,// Path to a writeable log file to write scan results intodebugMode: false,// Whether or not to log info/debug/error msgs to the consolefileList: null,// path to file containing list of files to scan (for scanFiles method)scanRecursively: true,// If true, deep scan folders recursivelyclamscan: {path: '/usr/bin/clamscan',// Path to clamscan binary on your serverdb: null,// Path to a custom virus definition databasescanArchives: true,// If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)active: true// If true, this module will consider using the clamscan binary},clamdscan: {socket: false,// Socket file for connecting via TCPhost: false,// IP of host to connect to TCP interfaceport: false,// Port of host to use when connecting via TCP interfacetimeout: 60000,// Timeout for scanning fileslocalFallback: true,// Use local preferred binary to scan if socket/tcp failspath: '/usr/bin/clamdscan',// Path to the clamdscan binary on your serverconfigFile: null,// Specify config file if it's in an unusual placemultiscan: true,// Scan using all available cores! Yay!reloadDb: false,// If true, will re-load the DB on every call (slow)active: true,// If true, this module will consider using the clamdscan binarybypassTest: false,// Check to see if socket is available when applicabletls: false,// Use plaintext TCP to connect to clamd},preference: 'clamdscan'// If clamdscan is found and active, it will be used by default});

Here is a non-default values example (to help you get an idea of what proper-looking values could be):

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: true,// Removes files if they are infectedquarantineInfected: '~/infected/',// Move file here. removeInfected must be FALSE, though.scanLog: '/var/log/node-clam',// You're a detail-oriented security professional.debugMode: true,// This will put some debug info in your js consolefileList: '/home/webuser/scanFiles.txt',// path to file containing list of files to scanscanRecursively: false,// Choosing false here will save some CPU cyclesclamscan: {path: '/usr/bin/clam',// I dunno, maybe your clamscan is just call "clam"scanArchives: false,// Choosing false here will save some CPU cyclesdb: '/usr/bin/better_clam_db',// Path to a custom virus definition databaseactive: false// you don't want to use this at all because it's evil},clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',// This is pretty typicalhost: '127.0.0.1',// If you want to connect locally but not through socketport: 12345,// Because, why nottimeout: 300000,// 5 minuteslocalFallback: false,// Do no fail over to binary-method of scanningpath: '/bin/clamdscan',// Special path to the clamdscan binary on your serverconfigFile: '/etc/clamd.d/daemon.conf',// A fairly typical config locationmultiscan: false,// You hate speed and multi-threaded awesome-saucereloadDb: true,// You want your scans to run slow like with clamscanactive: false,// you don't want to use this at all because it's evilbypassTest: true,// Don't check to see if socket is available. You should probably never set this to true.tls: true,// Connect to clamd over TLS},preference: 'clamscan'// If clamscan is found and active, it will be used by default});

NOTE: If a valid port is provided but no host value is provided, the clamscan will assume 'localhost' for host.

A note about using this module via sockets or TCP

As of version v1.0.0, this module supports communication with a local or remote ClamAV daemon through Unix Domain sockets or a TCP host/port combo. If you supply both in your configuration object, the UNIX Domain socket option will be used. The module will not not fallback to using the alternative Host/Port method. If you wish to connect via Host/Port and not a Socket, please either omit the socket property in the config object or use socket: null.

If you specify a valid clamscan/clamdscan binary in your config and you set clamdscan.localFallback: true in your config, this module will fallback to the traditional way this module has worked--using a binary directly/locally.

Also, there are some caveats to using the socket/tcp based approach:

  • The following configuration items are not honored (unless the module falls back to binary method):

    • removeInfected - remote clamd service config will dictate this
    • quarantineInfected - remote clamd service config will dictate this
    • scanLog - remote clamd service config will dictate this
    • fileList - this simply won't be available
    • clamscan.db - only available on fallback
    • clamscan.scanArchives - only available on fallback
    • clamscan.path - only available on fallback
    • clamdscan.configFile - only available on fallback
    • clamdscan.path - only available on fallback

Basic Usage Example

For the sake of brevity, all the examples in the API section will be shortened to just the relevant parts related specifically to that example. In those examples, we'll assume you already have an instance of the clamscan object. Since initializing the module returns a promise, you'll have to resolve that promise to get an instance of the clamscan object.

Below is the full example of how you could get that instance and run some methods:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init(options);// Get instance by resolving ClamScan promise objectClamScan.then(asyncclamscan=>{try{// You can re-use the `clamscan` object as many times as you wantconstversion=awaitclamscan.getVersion();console.log(`ClamAV Version: ${version}`);const{isInfected, file, viruses}=awaitclamscan.isInfected('/some/file.zip');if(isInfected)console.log(`${file} is infected with ${viruses}!`);}catch(err){// Handle any errors raised by the code in the try block}}).catch(err=>{// Handle errors that may have occurred during initialization});

If you're writing your code within an async function, getting an instance can be one less step:

constNodeClam=require('clamscan');asyncsome_function(){try{// Get instance by resolving ClamScan promise objectconstclamscan=awaitnewNodeClam().init(options);const{goodFiles, badFiles}=awaitclamscan.scanDir('/foo/bar');}catch(err){// Handle any errors raised by the code in the try block}}some_function();

API

Complete/functional examples for various use-cases can be found in the examples folder.

.getVersion([callback])

This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.

Parameters

  • callback (function) (optional) Will be called when the scan is complete. It receives 2 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • version (string) The version of the clamav server you're interfacing with

Returns

  • Promise

    • Promise resolution returns: version (string) The version of the clamav server you're interfacing with

Callback Example

clamscan.getVersion((err,version)=>{if(err)returnconsole.error(err);console.log(`ClamAV Version: ${version}`);});

Promise Example

clamscan.getVersion().then(version=>{console.log(`ClamAV Version: ${version}`);}).catch(err=>{console.error(err);});

.isInfected(filePath[,callback])

This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.

Alias

.scan_file

Parameters

  • filePath (string) Represents a path to the file to be scanned.

  • callback (function) (optional) Will be called when the scan is complete. It takes 3 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • file (string) The original filePath passed into the isInfected method.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
    • viruses (array) An array of any viruses found in the scanned file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) The original filePath passed into the isInfected method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Callback Example

clamscan.isInfected('/a/picture/for_example.jpg',(err,file,isInfected,viruses)=>{if(err)returnconsole.error(err);if(isInfected){console.log(`${file} is infected with ${viruses.join(', ')}.`);}});

Promise Example

clamscan.isInfected('/a/picture/for_example.jpg').then(result=>{const{file, isInfected, viruses}=result;if(isInfected)console.log(`${file} is infected with ${viruses.join(', ')}.`);}).then(err=>{console.error(err);})

Async/Await Example

const{file, isInfected, viruses}=awaitclamscan.isInfected('/a/picture/for_example.jpg');

.scanDir(dirPath[,endCallback[,fileCallback]])

Allows you to scan an entire directory for infected files. This obeys your recursive option even for clamdscan which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to scanFiles.

TL;DR: For maximum speed, don't supply a fileCallback.

If you choose to supply a fileCallback, the scan will run a little bit slower (depending on number of files to be scanned) for clamdscan. If you are using clamscan, while it will work, I'd highly advise you to NOT pass a fileCallback... it will run incredibly slow.

NOTE

The goodFiles parameter of the endCallback callback in this method will only contain the directory that was scanned in allbut the following scenarios:

  • A fileCallback callback is provided, and scanRecursively is set to true.
  • The scanner is set to clamdscan and scanRecursively is set to false.
  • The scanned directory contains 1 or more viruses. In this case, the goodFiles array will be empty.

There will, however, be a total count of the good files which is calculated by determining the total number of files scanned and subtracting the number of bad files from that count. We simply can't provide a list of all good files due to the potential large memory usage implications of scanning a directory with, for example, millions of files.

Parameters

  • dirPath (string) (required) Full path to the directory to scan.

  • endCallback (function) (optional) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:

    • err (object) A standard javascript Error object (null if no error)
    • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
    • badFiles (array) List of the full paths to all files that are infected.
    • viruses (array) List of all the viruses found (feature request: associate to the bad files).
    • numGoodFiles (number) Number of files that were found to be clean.
  • fileCallback (function) (optional) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard Javascript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • path (string) The original dir_path passed into the scanDir method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).
      • numGoodFiles (number) Number of files that were found to be clean.

Callback Example

clamscan.scanDir('/some/path/to/scan',(err,goodFiles,badFiles,viruses,numGoodFiles){if(err)returnconsole.error(err);if(badFiles.length>0){console.log(`${path} was infected. The offending files (${badFiles.join(', ')}) have been quarantined.`);console.log(`Viruses Found: ${viruses.join(', ')}`);}else{console.log(`${goodFiles[0]} looks good! ${numGoodFiles} file scanned and no problems found!.`);}});

Promise Example

clamscan.scanDir('/some/path/to/scan').then(results=>{const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=results;//...}).catch(err=>{returnconsole.error(err);});

Async/Await Example

const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=awaitclamscan.scanDir('/some/path/to/scan');

.scanFiles(files[,endCallback[,fileCallback]])

This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for isInfected that simplifies the process of scanning many files or directories.

Parameters

  • files (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a fileList config option. If neither is found, the method will throw an error.

  • endCallback (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • goodFiles (array) List of the full paths to all files that are clean.
    • badFiles (array) List of the full paths to all files that are infected.
  • fileCallback (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • goodFiles (array) List of the full paths to all files that are clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • errors (object) Per-file errors keyed by the filename in which the error happened. (ex. {'foo.txt': Error})
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).

Callback Example

constscan_status={good: 0,bad: 0};constfiles=['/path/to/file/1.jpg','/path/to/file/2.mov','/path/to/file/3.rb'];clamscan.scanFiles(files,(err,goodFiles,badFiles,viruses)=>{if(err)returnconsole.error(err);if(badFiles.length>0){console.log({msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
badFiles,
goodFiles,
viruses,});}else{res.send({msg: "Everything looks good! No problems here!."});}},(err,file,isInfected,viruses)=>{;(isInfected ? scan_status.bad++ : scan_status.good++);console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);console.log('Scan Status: ',`${(scan_status.bad+scan_status.good)}/${files.length}`);});

Promise Example

Note: There is currently no way to get per-file notifications with the Promise API.

clamscan.scanFiles(files).then(results=>{const{ goodFiles, badFiles, errors, viruses }=results;// ...}).catch(err=>{console.error(err);})

Async/Await Example

const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles(files);

Scanning files listed in fileList

If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.

Files List Document:

/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb

Script:

constClamScan=newNodeClam().init({fileList: '/path/to/fileList.txt'});ClamScan.then(asyncclamscan=>{// Supply nothing to first parameter to use `fileList`const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles();});

.scanStream(stream[,callback])

This method allows you to scan a binary stream. NOTE: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.

Parameters

  • stream (stream) A readable stream object

  • callback (function) (optional) Will be called after the stream has been scanned (or attempted to be scanned):

    • err (object or null) A standard JavaScript Error object (null if no error)
    • isInfected (boolean) True: Stream is infected; False: Stream is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) NULL as no file path can be provided with the stream
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});constReadable=require('stream').Readable;constrs=Readable();rs.push('foooooo');rs.push('barrrrr');rs.push(null);clamscan.scanStream(stream,(err,{isInfected. viruses })=>{if(err)returnconsole.error(err);if(isInfected)returnconsole.log('Stream is infected! Booo!',viruses);console.log('Stream is not infected! Yay!');});

Promise Example:

clamscan.scanStream(stream).then(({isInfected})=>{if(isInfected)returnconsole.log("Stream is infected! Booo!");console.log("Stream is not infected! Yay!");}).catch(err=>{console.error(err);};

Promise Example:

const{ isInfected, viruses }=awaitclamscan.scanStream(stream);

.passthrough()

The passthrough method returns a PassthroughStream object which allows you pipe a ReadbleStream through it and on to another output. In the case of this module's passthrough implementation, it's actually forking the data to also go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to the output if that chunk was successfully sent to and received by ClamAV. The PassthroughStream object returned from this method has a special event that is emitted when ClamAV finishes scanning the streamed data so that you can decide if there's anything you need to do with the final output destination (ex. delete a file or S3 object).

In typical, non-passthrough setups, a file is uploaded to the local filesytem and then subsequently scanned. With that setup, you have to wait for the upload to complete and then wait again for the scan to complete. Using this module's passthrough method, you could theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to any WriteableStream output (examples: filesystem, S3, gzip, etc...).

As for these theoretical gains, your mileage my vary and I'd love to hear feedback on this to see where things can still be improved.

Please note that this method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (a practical working example can be found in the examples directory of this module):

Example

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});// For example's sake, we're using the Axios moduleconstaxios=require('Axios');// Get a readable stream for a URL requestconstinput=axios.get(some_url);// Create a writable stream to a local fileconstoutput=fs.createWriteStream(some_local_file);// Get instance of this module's PassthroughStream objectconstav=clamscan.passthrough();// Send output of Axios stream to ClamAV.// Send output of Axios to `some_local_file` if ClamAV receives data successfullyinput.pipe(av).pipe(output);// What happens when scan is completedav.on('scan-complete',result=>{const{ isInfected, viruses }=result;// Do stuff if you want});// What happens when data has been fully written to `output`output.on('finish',()=>{// Do stuff if you want});// NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications

.ping()

This method checks to see if the remote/local socket is working. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method can be used for healthcheck purposes and is already implicitly used during scan.

Parameters

  • callback (function) (optional) Will be called after the ping:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • client (object) A copy of the Socket/TCP client

Returns

  • Promise

    • Promise resolution returns: client (object): A copy of the Socket/TCP client

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});clamscan.ping((err,client)=>{if(err)returnconsole.error(err);console.log('ClamAV is still working!');client.end();});

Promise Example:

clamscan.ping().then((client)=>{console.log('ClamAV is still working!');client.end();}).catch(err=>{console.error(err);};

Promise Example:

constclient=awaitclamscan.ping();client.end();

Contribute

Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.

Resources used to help develop this module

About

A robust ClamAV virus scanning library supporting scanning files, directories, and streams with local sockets, local/remote TCP, and local clamscan/clamdscan binaries (with failover).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NodeJS Clamscan Virus Scanning Utility

NPM VersionNPM DownloadsNode.js VersionTest Suite

Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.

!!IMPORTANT

If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.

All older versions in NPM have been deprecated.

Version 1.0.0 Information

If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the release notes as there are some breaking changes (but also some awesome new features!).

Table of Contents

Dependencies

To use local binary method of scanning

You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.

Fedora-based distros:

sudo yum install clamav

Debian-based distros:

sudo apt-get install clamav clamav-daemon

For OS X, you can install clamav with brew:

sudo brew install clamav

To use ClamAV using TCP sockets

You will need access to either:

  1. A local UNIX Domain socket for a local instance of clamd
  1. A local/remote clamd daemon
  • Must know the port the daemon is running on
  • If running on remote server, you must have the IP address/domain name
  • If running on remote server, it's firewall must have the appropriate TCP port(s) open
  • Make sure clamd is running on your local/remote server

NOTE: This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).

How to Install

npm install clamscan

License Info

Licensed under the MIT License:

Getting Started

All of the values listed in the example below represent the default values for their respective configuration item.

You can simply do this:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init();

And, you'll be good to go.

BUT: If you want more control, you can specify all sorts of options.

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: false,// If true, removes infected filesquarantineInfected: false,// False: Don't quarantine, Path: Moves files to this place.scanLog: null,// Path to a writeable log file to write scan results intodebugMode: false,// Whether or not to log info/debug/error msgs to the consolefileList: null,// path to file containing list of files to scan (for scanFiles method)scanRecursively: true,// If true, deep scan folders recursivelyclamscan: {path: '/usr/bin/clamscan',// Path to clamscan binary on your serverdb: null,// Path to a custom virus definition databasescanArchives: true,// If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)active: true// If true, this module will consider using the clamscan binary},clamdscan: {socket: false,// Socket file for connecting via TCPhost: false,// IP of host to connect to TCP interfaceport: false,// Port of host to use when connecting via TCP interfacetimeout: 60000,// Timeout for scanning fileslocalFallback: true,// Use local preferred binary to scan if socket/tcp failspath: '/usr/bin/clamdscan',// Path to the clamdscan binary on your serverconfigFile: null,// Specify config file if it's in an unusual placemultiscan: true,// Scan using all available cores! Yay!reloadDb: false,// If true, will re-load the DB on every call (slow)active: true,// If true, this module will consider using the clamdscan binarybypassTest: false,// Check to see if socket is available when applicabletls: false,// Use plaintext TCP to connect to clamd},preference: 'clamdscan'// If clamdscan is found and active, it will be used by default});

Here is a non-default values example (to help you get an idea of what proper-looking values could be):

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: true,// Removes files if they are infectedquarantineInfected: '~/infected/',// Move file here. removeInfected must be FALSE, though.scanLog: '/var/log/node-clam',// You're a detail-oriented security professional.debugMode: true,// This will put some debug info in your js consolefileList: '/home/webuser/scanFiles.txt',// path to file containing list of files to scanscanRecursively: false,// Choosing false here will save some CPU cyclesclamscan: {path: '/usr/bin/clam',// I dunno, maybe your clamscan is just call "clam"scanArchives: false,// Choosing false here will save some CPU cyclesdb: '/usr/bin/better_clam_db',// Path to a custom virus definition databaseactive: false// you don't want to use this at all because it's evil},clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',// This is pretty typicalhost: '127.0.0.1',// If you want to connect locally but not through socketport: 12345,// Because, why nottimeout: 300000,// 5 minuteslocalFallback: false,// Do no fail over to binary-method of scanningpath: '/bin/clamdscan',// Special path to the clamdscan binary on your serverconfigFile: '/etc/clamd.d/daemon.conf',// A fairly typical config locationmultiscan: false,// You hate speed and multi-threaded awesome-saucereloadDb: true,// You want your scans to run slow like with clamscanactive: false,// you don't want to use this at all because it's evilbypassTest: true,// Don't check to see if socket is available. You should probably never set this to true.tls: true,// Connect to clamd over TLS},preference: 'clamscan'// If clamscan is found and active, it will be used by default});

NOTE: If a valid port is provided but no host value is provided, the clamscan will assume 'localhost' for host.

A note about using this module via sockets or TCP

As of version v1.0.0, this module supports communication with a local or remote ClamAV daemon through Unix Domain sockets or a TCP host/port combo. If you supply both in your configuration object, the UNIX Domain socket option will be used. The module will not not fallback to using the alternative Host/Port method. If you wish to connect via Host/Port and not a Socket, please either omit the socket property in the config object or use socket: null.

If you specify a valid clamscan/clamdscan binary in your config and you set clamdscan.localFallback: true in your config, this module will fallback to the traditional way this module has worked--using a binary directly/locally.

Also, there are some caveats to using the socket/tcp based approach:

  • The following configuration items are not honored (unless the module falls back to binary method):

    • removeInfected - remote clamd service config will dictate this
    • quarantineInfected - remote clamd service config will dictate this
    • scanLog - remote clamd service config will dictate this
    • fileList - this simply won't be available
    • clamscan.db - only available on fallback
    • clamscan.scanArchives - only available on fallback
    • clamscan.path - only available on fallback
    • clamdscan.configFile - only available on fallback
    • clamdscan.path - only available on fallback

Basic Usage Example

For the sake of brevity, all the examples in the API section will be shortened to just the relevant parts related specifically to that example. In those examples, we'll assume you already have an instance of the clamscan object. Since initializing the module returns a promise, you'll have to resolve that promise to get an instance of the clamscan object.

Below is the full example of how you could get that instance and run some methods:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init(options);// Get instance by resolving ClamScan promise objectClamScan.then(asyncclamscan=>{try{// You can re-use the `clamscan` object as many times as you wantconstversion=awaitclamscan.getVersion();console.log(`ClamAV Version: ${version}`);const{isInfected, file, viruses}=awaitclamscan.isInfected('/some/file.zip');if(isInfected)console.log(`${file} is infected with ${viruses}!`);}catch(err){// Handle any errors raised by the code in the try block}}).catch(err=>{// Handle errors that may have occurred during initialization});

If you're writing your code within an async function, getting an instance can be one less step:

constNodeClam=require('clamscan');asyncsome_function(){try{// Get instance by resolving ClamScan promise objectconstclamscan=awaitnewNodeClam().init(options);const{goodFiles, badFiles}=awaitclamscan.scanDir('/foo/bar');}catch(err){// Handle any errors raised by the code in the try block}}some_function();

API

Complete/functional examples for various use-cases can be found in the examples folder.

.getVersion([callback])

This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.

Parameters

  • callback (function) (optional) Will be called when the scan is complete. It receives 2 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • version (string) The version of the clamav server you're interfacing with

Returns

  • Promise

    • Promise resolution returns: version (string) The version of the clamav server you're interfacing with

Callback Example

clamscan.getVersion((err,version)=>{if(err)returnconsole.error(err);console.log(`ClamAV Version: ${version}`);});

Promise Example

clamscan.getVersion().then(version=>{console.log(`ClamAV Version: ${version}`);}).catch(err=>{console.error(err);});

.isInfected(filePath[,callback])

This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.

Alias

.scan_file

Parameters

  • filePath (string) Represents a path to the file to be scanned.

  • callback (function) (optional) Will be called when the scan is complete. It takes 3 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • file (string) The original filePath passed into the isInfected method.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
    • viruses (array) An array of any viruses found in the scanned file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) The original filePath passed into the isInfected method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Callback Example

clamscan.isInfected('/a/picture/for_example.jpg',(err,file,isInfected,viruses)=>{if(err)returnconsole.error(err);if(isInfected){console.log(`${file} is infected with ${viruses.join(', ')}.`);}});

Promise Example

clamscan.isInfected('/a/picture/for_example.jpg').then(result=>{const{file, isInfected, viruses}=result;if(isInfected)console.log(`${file} is infected with ${viruses.join(', ')}.`);}).then(err=>{console.error(err);})

Async/Await Example

const{file, isInfected, viruses}=awaitclamscan.isInfected('/a/picture/for_example.jpg');

.scanDir(dirPath[,endCallback[,fileCallback]])

Allows you to scan an entire directory for infected files. This obeys your recursive option even for clamdscan which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to scanFiles.

TL;DR: For maximum speed, don't supply a fileCallback.

If you choose to supply a fileCallback, the scan will run a little bit slower (depending on number of files to be scanned) for clamdscan. If you are using clamscan, while it will work, I'd highly advise you to NOT pass a fileCallback... it will run incredibly slow.

NOTE

The goodFiles parameter of the endCallback callback in this method will only contain the directory that was scanned in allbut the following scenarios:

  • A fileCallback callback is provided, and scanRecursively is set to true.
  • The scanner is set to clamdscan and scanRecursively is set to false.
  • The scanned directory contains 1 or more viruses. In this case, the goodFiles array will be empty.

There will, however, be a total count of the good files which is calculated by determining the total number of files scanned and subtracting the number of bad files from that count. We simply can't provide a list of all good files due to the potential large memory usage implications of scanning a directory with, for example, millions of files.

Parameters

  • dirPath (string) (required) Full path to the directory to scan.

  • endCallback (function) (optional) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:

    • err (object) A standard javascript Error object (null if no error)
    • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
    • badFiles (array) List of the full paths to all files that are infected.
    • viruses (array) List of all the viruses found (feature request: associate to the bad files).
    • numGoodFiles (number) Number of files that were found to be clean.
  • fileCallback (function) (optional) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard Javascript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • path (string) The original dir_path passed into the scanDir method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).
      • numGoodFiles (number) Number of files that were found to be clean.

Callback Example

clamscan.scanDir('/some/path/to/scan',(err,goodFiles,badFiles,viruses,numGoodFiles){if(err)returnconsole.error(err);if(badFiles.length>0){console.log(`${path} was infected. The offending files (${badFiles.join(', ')}) have been quarantined.`);console.log(`Viruses Found: ${viruses.join(', ')}`);}else{console.log(`${goodFiles[0]} looks good! ${numGoodFiles} file scanned and no problems found!.`);}});

Promise Example

clamscan.scanDir('/some/path/to/scan').then(results=>{const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=results;//...}).catch(err=>{returnconsole.error(err);});

Async/Await Example

const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=awaitclamscan.scanDir('/some/path/to/scan');

.scanFiles(files[,endCallback[,fileCallback]])

This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for isInfected that simplifies the process of scanning many files or directories.

Parameters

  • files (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a fileList config option. If neither is found, the method will throw an error.

  • endCallback (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • goodFiles (array) List of the full paths to all files that are clean.
    • badFiles (array) List of the full paths to all files that are infected.
  • fileCallback (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • goodFiles (array) List of the full paths to all files that are clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • errors (object) Per-file errors keyed by the filename in which the error happened. (ex. {'foo.txt': Error})
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).

Callback Example

constscan_status={good: 0,bad: 0};constfiles=['/path/to/file/1.jpg','/path/to/file/2.mov','/path/to/file/3.rb'];clamscan.scanFiles(files,(err,goodFiles,badFiles,viruses)=>{if(err)returnconsole.error(err);if(badFiles.length>0){console.log({msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
badFiles,
goodFiles,
viruses,});}else{res.send({msg: "Everything looks good! No problems here!."});}},(err,file,isInfected,viruses)=>{;(isInfected ? scan_status.bad++ : scan_status.good++);console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);console.log('Scan Status: ',`${(scan_status.bad+scan_status.good)}/${files.length}`);});

Promise Example

Note: There is currently no way to get per-file notifications with the Promise API.

clamscan.scanFiles(files).then(results=>{const{ goodFiles, badFiles, errors, viruses }=results;// ...}).catch(err=>{console.error(err);})

Async/Await Example

const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles(files);

Scanning files listed in fileList

If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.

Files List Document:

/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb

Script:

constClamScan=newNodeClam().init({fileList: '/path/to/fileList.txt'});ClamScan.then(asyncclamscan=>{// Supply nothing to first parameter to use `fileList`const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles();});

.scanStream(stream[,callback])

This method allows you to scan a binary stream. NOTE: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.

Parameters

  • stream (stream) A readable stream object

  • callback (function) (optional) Will be called after the stream has been scanned (or attempted to be scanned):

    • err (object or null) A standard JavaScript Error object (null if no error)
    • isInfected (boolean) True: Stream is infected; False: Stream is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) NULL as no file path can be provided with the stream
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});constReadable=require('stream').Readable;constrs=Readable();rs.push('foooooo');rs.push('barrrrr');rs.push(null);clamscan.scanStream(stream,(err,{isInfected. viruses })=>{if(err)returnconsole.error(err);if(isInfected)returnconsole.log('Stream is infected! Booo!',viruses);console.log('Stream is not infected! Yay!');});

Promise Example:

clamscan.scanStream(stream).then(({isInfected})=>{if(isInfected)returnconsole.log("Stream is infected! Booo!");console.log("Stream is not infected! Yay!");}).catch(err=>{console.error(err);};

Promise Example:

const{ isInfected, viruses }=awaitclamscan.scanStream(stream);

.passthrough()

The passthrough method returns a PassthroughStream object which allows you pipe a ReadbleStream through it and on to another output. In the case of this module's passthrough implementation, it's actually forking the data to also go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to the output if that chunk was successfully sent to and received by ClamAV. The PassthroughStream object returned from this method has a special event that is emitted when ClamAV finishes scanning the streamed data so that you can decide if there's anything you need to do with the final output destination (ex. delete a file or S3 object).

In typical, non-passthrough setups, a file is uploaded to the local filesytem and then subsequently scanned. With that setup, you have to wait for the upload to complete and then wait again for the scan to complete. Using this module's passthrough method, you could theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to any WriteableStream output (examples: filesystem, S3, gzip, etc...).

As for these theoretical gains, your mileage my vary and I'd love to hear feedback on this to see where things can still be improved.

Please note that this method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (a practical working example can be found in the examples directory of this module):

Example

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});// For example's sake, we're using the Axios moduleconstaxios=require('Axios');// Get a readable stream for a URL requestconstinput=axios.get(some_url);// Create a writable stream to a local fileconstoutput=fs.createWriteStream(some_local_file);// Get instance of this module's PassthroughStream objectconstav=clamscan.passthrough();// Send output of Axios stream to ClamAV.// Send output of Axios to `some_local_file` if ClamAV receives data successfullyinput.pipe(av).pipe(output);// What happens when scan is completedav.on('scan-complete',result=>{const{ isInfected, viruses }=result;// Do stuff if you want});// What happens when data has been fully written to `output`output.on('finish',()=>{// Do stuff if you want});// NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications

.ping()

This method checks to see if the remote/local socket is working. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method can be used for healthcheck purposes and is already implicitly used during scan.

Parameters

  • callback (function) (optional) Will be called after the ping:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • client (object) A copy of the Socket/TCP client

Returns

  • Promise

    • Promise resolution returns: client (object): A copy of the Socket/TCP client

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});clamscan.ping((err,client)=>{if(err)returnconsole.error(err);console.log('ClamAV is still working!');client.end();});

Promise Example:

clamscan.ping().then((client)=>{console.log('ClamAV is still working!');client.end();}).catch(err=>{console.error(err);};

Promise Example:

constclient=awaitclamscan.ping();client.end();

Contribute

Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.

Resources used to help develop this module

About

A robust ClamAV virus scanning library supporting scanning files, directories, and streams with local sockets, local/remote TCP, and local clamscan/clamdscan binaries (with failover).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

NodeJS Clamscan Virus Scanning Utility

NPM VersionNPM DownloadsNode.js VersionTest Suite

Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.

!!IMPORTANT

If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.

All older versions in NPM have been deprecated.

Version 1.0.0 Information

If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the release notes as there are some breaking changes (but also some awesome new features!).

Table of Contents

Dependencies

To use local binary method of scanning

You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.

Fedora-based distros:

sudo yum install clamav

Debian-based distros:

sudo apt-get install clamav clamav-daemon

For OS X, you can install clamav with brew:

sudo brew install clamav

To use ClamAV using TCP sockets

You will need access to either:

  1. A local UNIX Domain socket for a local instance of clamd
  1. A local/remote clamd daemon
  • Must know the port the daemon is running on
  • If running on remote server, you must have the IP address/domain name
  • If running on remote server, it's firewall must have the appropriate TCP port(s) open
  • Make sure clamd is running on your local/remote server

NOTE: This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).

How to Install

npm install clamscan

License Info

Licensed under the MIT License:

Getting Started

All of the values listed in the example below represent the default values for their respective configuration item.

You can simply do this:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init();

And, you'll be good to go.

BUT: If you want more control, you can specify all sorts of options.

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: false,// If true, removes infected filesquarantineInfected: false,// False: Don't quarantine, Path: Moves files to this place.scanLog: null,// Path to a writeable log file to write scan results intodebugMode: false,// Whether or not to log info/debug/error msgs to the consolefileList: null,// path to file containing list of files to scan (for scanFiles method)scanRecursively: true,// If true, deep scan folders recursivelyclamscan: {path: '/usr/bin/clamscan',// Path to clamscan binary on your serverdb: null,// Path to a custom virus definition databasescanArchives: true,// If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)active: true// If true, this module will consider using the clamscan binary},clamdscan: {socket: false,// Socket file for connecting via TCPhost: false,// IP of host to connect to TCP interfaceport: false,// Port of host to use when connecting via TCP interfacetimeout: 60000,// Timeout for scanning fileslocalFallback: true,// Use local preferred binary to scan if socket/tcp failspath: '/usr/bin/clamdscan',// Path to the clamdscan binary on your serverconfigFile: null,// Specify config file if it's in an unusual placemultiscan: true,// Scan using all available cores! Yay!reloadDb: false,// If true, will re-load the DB on every call (slow)active: true,// If true, this module will consider using the clamdscan binarybypassTest: false,// Check to see if socket is available when applicabletls: false,// Use plaintext TCP to connect to clamd},preference: 'clamdscan'// If clamdscan is found and active, it will be used by default});

Here is a non-default values example (to help you get an idea of what proper-looking values could be):

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: true,// Removes files if they are infectedquarantineInfected: '~/infected/',// Move file here. removeInfected must be FALSE, though.scanLog: '/var/log/node-clam',// You're a detail-oriented security professional.debugMode: true,// This will put some debug info in your js consolefileList: '/home/webuser/scanFiles.txt',// path to file containing list of files to scanscanRecursively: false,// Choosing false here will save some CPU cyclesclamscan: {path: '/usr/bin/clam',// I dunno, maybe your clamscan is just call "clam"scanArchives: false,// Choosing false here will save some CPU cyclesdb: '/usr/bin/better_clam_db',// Path to a custom virus definition databaseactive: false// you don't want to use this at all because it's evil},clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',// This is pretty typicalhost: '127.0.0.1',// If you want to connect locally but not through socketport: 12345,// Because, why nottimeout: 300000,// 5 minuteslocalFallback: false,// Do no fail over to binary-method of scanningpath: '/bin/clamdscan',// Special path to the clamdscan binary on your serverconfigFile: '/etc/clamd.d/daemon.conf',// A fairly typical config locationmultiscan: false,// You hate speed and multi-threaded awesome-saucereloadDb: true,// You want your scans to run slow like with clamscanactive: false,// you don't want to use this at all because it's evilbypassTest: true,// Don't check to see if socket is available. You should probably never set this to true.tls: true,// Connect to clamd over TLS},preference: 'clamscan'// If clamscan is found and active, it will be used by default});

NOTE: If a valid port is provided but no host value is provided, the clamscan will assume 'localhost' for host.

A note about using this module via sockets or TCP

As of version v1.0.0, this module supports communication with a local or remote ClamAV daemon through Unix Domain sockets or a TCP host/port combo. If you supply both in your configuration object, the UNIX Domain socket option will be used. The module will not not fallback to using the alternative Host/Port method. If you wish to connect via Host/Port and not a Socket, please either omit the socket property in the config object or use socket: null.

If you specify a valid clamscan/clamdscan binary in your config and you set clamdscan.localFallback: true in your config, this module will fallback to the traditional way this module has worked--using a binary directly/locally.

Also, there are some caveats to using the socket/tcp based approach:

  • The following configuration items are not honored (unless the module falls back to binary method):

    • removeInfected - remote clamd service config will dictate this
    • quarantineInfected - remote clamd service config will dictate this
    • scanLog - remote clamd service config will dictate this
    • fileList - this simply won't be available
    • clamscan.db - only available on fallback
    • clamscan.scanArchives - only available on fallback
    • clamscan.path - only available on fallback
    • clamdscan.configFile - only available on fallback
    • clamdscan.path - only available on fallback

Basic Usage Example

For the sake of brevity, all the examples in the API section will be shortened to just the relevant parts related specifically to that example. In those examples, we'll assume you already have an instance of the clamscan object. Since initializing the module returns a promise, you'll have to resolve that promise to get an instance of the clamscan object.

Below is the full example of how you could get that instance and run some methods:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init(options);// Get instance by resolving ClamScan promise objectClamScan.then(asyncclamscan=>{try{// You can re-use the `clamscan` object as many times as you wantconstversion=awaitclamscan.getVersion();console.log(`ClamAV Version: ${version}`);const{isInfected, file, viruses}=awaitclamscan.isInfected('/some/file.zip');if(isInfected)console.log(`${file} is infected with ${viruses}!`);}catch(err){// Handle any errors raised by the code in the try block}}).catch(err=>{// Handle errors that may have occurred during initialization});

If you're writing your code within an async function, getting an instance can be one less step:

constNodeClam=require('clamscan');asyncsome_function(){try{// Get instance by resolving ClamScan promise objectconstclamscan=awaitnewNodeClam().init(options);const{goodFiles, badFiles}=awaitclamscan.scanDir('/foo/bar');}catch(err){// Handle any errors raised by the code in the try block}}some_function();

API

Complete/functional examples for various use-cases can be found in the examples folder.

.getVersion([callback])

This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.

Parameters

  • callback (function) (optional) Will be called when the scan is complete. It receives 2 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • version (string) The version of the clamav server you're interfacing with

Returns

  • Promise

    • Promise resolution returns: version (string) The version of the clamav server you're interfacing with

Callback Example

clamscan.getVersion((err,version)=>{if(err)returnconsole.error(err);console.log(`ClamAV Version: ${version}`);});

Promise Example

clamscan.getVersion().then(version=>{console.log(`ClamAV Version: ${version}`);}).catch(err=>{console.error(err);});

.isInfected(filePath[,callback])

This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.

Alias

.scan_file

Parameters

  • filePath (string) Represents a path to the file to be scanned.

  • callback (function) (optional) Will be called when the scan is complete. It takes 3 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • file (string) The original filePath passed into the isInfected method.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
    • viruses (array) An array of any viruses found in the scanned file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) The original filePath passed into the isInfected method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Callback Example

clamscan.isInfected('/a/picture/for_example.jpg',(err,file,isInfected,viruses)=>{if(err)returnconsole.error(err);if(isInfected){console.log(`${file} is infected with ${viruses.join(', ')}.`);}});

Promise Example

clamscan.isInfected('/a/picture/for_example.jpg').then(result=>{const{file, isInfected, viruses}=result;if(isInfected)console.log(`${file} is infected with ${viruses.join(', ')}.`);}).then(err=>{console.error(err);})

Async/Await Example

const{file, isInfected, viruses}=awaitclamscan.isInfected('/a/picture/for_example.jpg');

.scanDir(dirPath[,endCallback[,fileCallback]])

Allows you to scan an entire directory for infected files. This obeys your recursive option even for clamdscan which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to scanFiles.

TL;DR: For maximum speed, don't supply a fileCallback.

If you choose to supply a fileCallback, the scan will run a little bit slower (depending on number of files to be scanned) for clamdscan. If you are using clamscan, while it will work, I'd highly advise you to NOT pass a fileCallback... it will run incredibly slow.

NOTE

The goodFiles parameter of the endCallback callback in this method will only contain the directory that was scanned in allbut the following scenarios:

  • A fileCallback callback is provided, and scanRecursively is set to true.
  • The scanner is set to clamdscan and scanRecursively is set to false.
  • The scanned directory contains 1 or more viruses. In this case, the goodFiles array will be empty.

There will, however, be a total count of the good files which is calculated by determining the total number of files scanned and subtracting the number of bad files from that count. We simply can't provide a list of all good files due to the potential large memory usage implications of scanning a directory with, for example, millions of files.

Parameters

  • dirPath (string) (required) Full path to the directory to scan.

  • endCallback (function) (optional) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:

    • err (object) A standard javascript Error object (null if no error)
    • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
    • badFiles (array) List of the full paths to all files that are infected.
    • viruses (array) List of all the viruses found (feature request: associate to the bad files).
    • numGoodFiles (number) Number of files that were found to be clean.
  • fileCallback (function) (optional) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard Javascript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • path (string) The original dir_path passed into the scanDir method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).
      • numGoodFiles (number) Number of files that were found to be clean.

Callback Example

clamscan.scanDir('/some/path/to/scan',(err,goodFiles,badFiles,viruses,numGoodFiles){if(err)returnconsole.error(err);if(badFiles.length>0){console.log(`${path} was infected. The offending files (${badFiles.join(', ')}) have been quarantined.`);console.log(`Viruses Found: ${viruses.join(', ')}`);}else{console.log(`${goodFiles[0]} looks good! ${numGoodFiles} file scanned and no problems found!.`);}});

Promise Example

clamscan.scanDir('/some/path/to/scan').then(results=>{const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=results;//...}).catch(err=>{returnconsole.error(err);});

Async/Await Example

const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=awaitclamscan.scanDir('/some/path/to/scan');

.scanFiles(files[,endCallback[,fileCallback]])

This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for isInfected that simplifies the process of scanning many files or directories.

Parameters

  • files (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a fileList config option. If neither is found, the method will throw an error.

  • endCallback (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • goodFiles (array) List of the full paths to all files that are clean.
    • badFiles (array) List of the full paths to all files that are infected.
  • fileCallback (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • goodFiles (array) List of the full paths to all files that are clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • errors (object) Per-file errors keyed by the filename in which the error happened. (ex. {'foo.txt': Error})
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).

Callback Example

constscan_status={good: 0,bad: 0};constfiles=['/path/to/file/1.jpg','/path/to/file/2.mov','/path/to/file/3.rb'];clamscan.scanFiles(files,(err,goodFiles,badFiles,viruses)=>{if(err)returnconsole.error(err);if(badFiles.length>0){console.log({msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
badFiles,
goodFiles,
viruses,});}else{res.send({msg: "Everything looks good! No problems here!."});}},(err,file,isInfected,viruses)=>{;(isInfected ? scan_status.bad++ : scan_status.good++);console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);console.log('Scan Status: ',`${(scan_status.bad+scan_status.good)}/${files.length}`);});

Promise Example

Note: There is currently no way to get per-file notifications with the Promise API.

clamscan.scanFiles(files).then(results=>{const{ goodFiles, badFiles, errors, viruses }=results;// ...}).catch(err=>{console.error(err);})

Async/Await Example

const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles(files);

Scanning files listed in fileList

If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.

Files List Document:

/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb

Script:

constClamScan=newNodeClam().init({fileList: '/path/to/fileList.txt'});ClamScan.then(asyncclamscan=>{// Supply nothing to first parameter to use `fileList`const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles();});

.scanStream(stream[,callback])

This method allows you to scan a binary stream. NOTE: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.

Parameters

  • stream (stream) A readable stream object

  • callback (function) (optional) Will be called after the stream has been scanned (or attempted to be scanned):

    • err (object or null) A standard JavaScript Error object (null if no error)
    • isInfected (boolean) True: Stream is infected; False: Stream is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) NULL as no file path can be provided with the stream
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});constReadable=require('stream').Readable;constrs=Readable();rs.push('foooooo');rs.push('barrrrr');rs.push(null);clamscan.scanStream(stream,(err,{isInfected. viruses })=>{if(err)returnconsole.error(err);if(isInfected)returnconsole.log('Stream is infected! Booo!',viruses);console.log('Stream is not infected! Yay!');});

Promise Example:

clamscan.scanStream(stream).then(({isInfected})=>{if(isInfected)returnconsole.log("Stream is infected! Booo!");console.log("Stream is not infected! Yay!");}).catch(err=>{console.error(err);};

Promise Example:

const{ isInfected, viruses }=awaitclamscan.scanStream(stream);

.passthrough()

The passthrough method returns a PassthroughStream object which allows you pipe a ReadbleStream through it and on to another output. In the case of this module's passthrough implementation, it's actually forking the data to also go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to the output if that chunk was successfully sent to and received by ClamAV. The PassthroughStream object returned from this method has a special event that is emitted when ClamAV finishes scanning the streamed data so that you can decide if there's anything you need to do with the final output destination (ex. delete a file or S3 object).

In typical, non-passthrough setups, a file is uploaded to the local filesytem and then subsequently scanned. With that setup, you have to wait for the upload to complete and then wait again for the scan to complete. Using this module's passthrough method, you could theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to any WriteableStream output (examples: filesystem, S3, gzip, etc...).

As for these theoretical gains, your mileage my vary and I'd love to hear feedback on this to see where things can still be improved.

Please note that this method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (a practical working example can be found in the examples directory of this module):

Example

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});// For example's sake, we're using the Axios moduleconstaxios=require('Axios');// Get a readable stream for a URL requestconstinput=axios.get(some_url);// Create a writable stream to a local fileconstoutput=fs.createWriteStream(some_local_file);// Get instance of this module's PassthroughStream objectconstav=clamscan.passthrough();// Send output of Axios stream to ClamAV.// Send output of Axios to `some_local_file` if ClamAV receives data successfullyinput.pipe(av).pipe(output);// What happens when scan is completedav.on('scan-complete',result=>{const{ isInfected, viruses }=result;// Do stuff if you want});// What happens when data has been fully written to `output`output.on('finish',()=>{// Do stuff if you want});// NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications

.ping()

This method checks to see if the remote/local socket is working. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method can be used for healthcheck purposes and is already implicitly used during scan.

Parameters

  • callback (function) (optional) Will be called after the ping:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • client (object) A copy of the Socket/TCP client

Returns

  • Promise

    • Promise resolution returns: client (object): A copy of the Socket/TCP client

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});clamscan.ping((err,client)=>{if(err)returnconsole.error(err);console.log('ClamAV is still working!');client.end();});

Promise Example:

clamscan.ping().then((client)=>{console.log('ClamAV is still working!');client.end();}).catch(err=>{console.error(err);};

Promise Example:

constclient=awaitclamscan.ping();client.end();

Contribute

Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.

Resources used to help develop this module

About

A robust ClamAV virus scanning library supporting scanning files, directories, and streams with local sockets, local/remote TCP, and local clamscan/clamdscan binaries (with failover).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NodeJS Clamscan Virus Scanning Utility

NPM VersionNPM DownloadsNode.js VersionTest Suite

Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.

!!IMPORTANT

If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.

All older versions in NPM have been deprecated.

Version 1.0.0 Information

If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the release notes as there are some breaking changes (but also some awesome new features!).

Table of Contents

Dependencies

To use local binary method of scanning

You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.

Fedora-based distros:

sudo yum install clamav

Debian-based distros:

sudo apt-get install clamav clamav-daemon

For OS X, you can install clamav with brew:

sudo brew install clamav

To use ClamAV using TCP sockets

You will need access to either:

  1. A local UNIX Domain socket for a local instance of clamd
  1. A local/remote clamd daemon
  • Must know the port the daemon is running on
  • If running on remote server, you must have the IP address/domain name
  • If running on remote server, it's firewall must have the appropriate TCP port(s) open
  • Make sure clamd is running on your local/remote server

NOTE: This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).

How to Install

npm install clamscan

License Info

Licensed under the MIT License:

Getting Started

All of the values listed in the example below represent the default values for their respective configuration item.

You can simply do this:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init();

And, you'll be good to go.

BUT: If you want more control, you can specify all sorts of options.

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: false,// If true, removes infected filesquarantineInfected: false,// False: Don't quarantine, Path: Moves files to this place.scanLog: null,// Path to a writeable log file to write scan results intodebugMode: false,// Whether or not to log info/debug/error msgs to the consolefileList: null,// path to file containing list of files to scan (for scanFiles method)scanRecursively: true,// If true, deep scan folders recursivelyclamscan: {path: '/usr/bin/clamscan',// Path to clamscan binary on your serverdb: null,// Path to a custom virus definition databasescanArchives: true,// If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)active: true// If true, this module will consider using the clamscan binary},clamdscan: {socket: false,// Socket file for connecting via TCPhost: false,// IP of host to connect to TCP interfaceport: false,// Port of host to use when connecting via TCP interfacetimeout: 60000,// Timeout for scanning fileslocalFallback: true,// Use local preferred binary to scan if socket/tcp failspath: '/usr/bin/clamdscan',// Path to the clamdscan binary on your serverconfigFile: null,// Specify config file if it's in an unusual placemultiscan: true,// Scan using all available cores! Yay!reloadDb: false,// If true, will re-load the DB on every call (slow)active: true,// If true, this module will consider using the clamdscan binarybypassTest: false,// Check to see if socket is available when applicabletls: false,// Use plaintext TCP to connect to clamd},preference: 'clamdscan'// If clamdscan is found and active, it will be used by default});

Here is a non-default values example (to help you get an idea of what proper-looking values could be):

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: true,// Removes files if they are infectedquarantineInfected: '~/infected/',// Move file here. removeInfected must be FALSE, though.scanLog: '/var/log/node-clam',// You're a detail-oriented security professional.debugMode: true,// This will put some debug info in your js consolefileList: '/home/webuser/scanFiles.txt',// path to file containing list of files to scanscanRecursively: false,// Choosing false here will save some CPU cyclesclamscan: {path: '/usr/bin/clam',// I dunno, maybe your clamscan is just call "clam"scanArchives: false,// Choosing false here will save some CPU cyclesdb: '/usr/bin/better_clam_db',// Path to a custom virus definition databaseactive: false// you don't want to use this at all because it's evil},clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',// This is pretty typicalhost: '127.0.0.1',// If you want to connect locally but not through socketport: 12345,// Because, why nottimeout: 300000,// 5 minuteslocalFallback: false,// Do no fail over to binary-method of scanningpath: '/bin/clamdscan',// Special path to the clamdscan binary on your serverconfigFile: '/etc/clamd.d/daemon.conf',// A fairly typical config locationmultiscan: false,// You hate speed and multi-threaded awesome-saucereloadDb: true,// You want your scans to run slow like with clamscanactive: false,// you don't want to use this at all because it's evilbypassTest: true,// Don't check to see if socket is available. You should probably never set this to true.tls: true,// Connect to clamd over TLS},preference: 'clamscan'// If clamscan is found and active, it will be used by default});

NOTE: If a valid port is provided but no host value is provided, the clamscan will assume 'localhost' for host.

A note about using this module via sockets or TCP

As of version v1.0.0, this module supports communication with a local or remote ClamAV daemon through Unix Domain sockets or a TCP host/port combo. If you supply both in your configuration object, the UNIX Domain socket option will be used. The module will not not fallback to using the alternative Host/Port method. If you wish to connect via Host/Port and not a Socket, please either omit the socket property in the config object or use socket: null.

If you specify a valid clamscan/clamdscan binary in your config and you set clamdscan.localFallback: true in your config, this module will fallback to the traditional way this module has worked--using a binary directly/locally.

Also, there are some caveats to using the socket/tcp based approach:

  • The following configuration items are not honored (unless the module falls back to binary method):

    • removeInfected - remote clamd service config will dictate this
    • quarantineInfected - remote clamd service config will dictate this
    • scanLog - remote clamd service config will dictate this
    • fileList - this simply won't be available
    • clamscan.db - only available on fallback
    • clamscan.scanArchives - only available on fallback
    • clamscan.path - only available on fallback
    • clamdscan.configFile - only available on fallback
    • clamdscan.path - only available on fallback

Basic Usage Example

For the sake of brevity, all the examples in the API section will be shortened to just the relevant parts related specifically to that example. In those examples, we'll assume you already have an instance of the clamscan object. Since initializing the module returns a promise, you'll have to resolve that promise to get an instance of the clamscan object.

Below is the full example of how you could get that instance and run some methods:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init(options);// Get instance by resolving ClamScan promise objectClamScan.then(asyncclamscan=>{try{// You can re-use the `clamscan` object as many times as you wantconstversion=awaitclamscan.getVersion();console.log(`ClamAV Version: ${version}`);const{isInfected, file, viruses}=awaitclamscan.isInfected('/some/file.zip');if(isInfected)console.log(`${file} is infected with ${viruses}!`);}catch(err){// Handle any errors raised by the code in the try block}}).catch(err=>{// Handle errors that may have occurred during initialization});

If you're writing your code within an async function, getting an instance can be one less step:

constNodeClam=require('clamscan');asyncsome_function(){try{// Get instance by resolving ClamScan promise objectconstclamscan=awaitnewNodeClam().init(options);const{goodFiles, badFiles}=awaitclamscan.scanDir('/foo/bar');}catch(err){// Handle any errors raised by the code in the try block}}some_function();

API

Complete/functional examples for various use-cases can be found in the examples folder.

.getVersion([callback])

This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.

Parameters

  • callback (function) (optional) Will be called when the scan is complete. It receives 2 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • version (string) The version of the clamav server you're interfacing with

Returns

  • Promise

    • Promise resolution returns: version (string) The version of the clamav server you're interfacing with

Callback Example

clamscan.getVersion((err,version)=>{if(err)returnconsole.error(err);console.log(`ClamAV Version: ${version}`);});

Promise Example

clamscan.getVersion().then(version=>{console.log(`ClamAV Version: ${version}`);}).catch(err=>{console.error(err);});

.isInfected(filePath[,callback])

This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.

Alias

.scan_file

Parameters

  • filePath (string) Represents a path to the file to be scanned.

  • callback (function) (optional) Will be called when the scan is complete. It takes 3 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • file (string) The original filePath passed into the isInfected method.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
    • viruses (array) An array of any viruses found in the scanned file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) The original filePath passed into the isInfected method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Callback Example

clamscan.isInfected('/a/picture/for_example.jpg',(err,file,isInfected,viruses)=>{if(err)returnconsole.error(err);if(isInfected){console.log(`${file} is infected with ${viruses.join(', ')}.`);}});

Promise Example

clamscan.isInfected('/a/picture/for_example.jpg').then(result=>{const{file, isInfected, viruses}=result;if(isInfected)console.log(`${file} is infected with ${viruses.join(', ')}.`);}).then(err=>{console.error(err);})

Async/Await Example

const{file, isInfected, viruses}=awaitclamscan.isInfected('/a/picture/for_example.jpg');

.scanDir(dirPath[,endCallback[,fileCallback]])

Allows you to scan an entire directory for infected files. This obeys your recursive option even for clamdscan which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to scanFiles.

TL;DR: For maximum speed, don't supply a fileCallback.

If you choose to supply a fileCallback, the scan will run a little bit slower (depending on number of files to be scanned) for clamdscan. If you are using clamscan, while it will work, I'd highly advise you to NOT pass a fileCallback... it will run incredibly slow.

NOTE

The goodFiles parameter of the endCallback callback in this method will only contain the directory that was scanned in allbut the following scenarios:

  • A fileCallback callback is provided, and scanRecursively is set to true.
  • The scanner is set to clamdscan and scanRecursively is set to false.
  • The scanned directory contains 1 or more viruses. In this case, the goodFiles array will be empty.

There will, however, be a total count of the good files which is calculated by determining the total number of files scanned and subtracting the number of bad files from that count. We simply can't provide a list of all good files due to the potential large memory usage implications of scanning a directory with, for example, millions of files.

Parameters

  • dirPath (string) (required) Full path to the directory to scan.

  • endCallback (function) (optional) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:

    • err (object) A standard javascript Error object (null if no error)
    • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
    • badFiles (array) List of the full paths to all files that are infected.
    • viruses (array) List of all the viruses found (feature request: associate to the bad files).
    • numGoodFiles (number) Number of files that were found to be clean.
  • fileCallback (function) (optional) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard Javascript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • path (string) The original dir_path passed into the scanDir method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).
      • numGoodFiles (number) Number of files that were found to be clean.

Callback Example

clamscan.scanDir('/some/path/to/scan',(err,goodFiles,badFiles,viruses,numGoodFiles){if(err)returnconsole.error(err);if(badFiles.length>0){console.log(`${path} was infected. The offending files (${badFiles.join(', ')}) have been quarantined.`);console.log(`Viruses Found: ${viruses.join(', ')}`);}else{console.log(`${goodFiles[0]} looks good! ${numGoodFiles} file scanned and no problems found!.`);}});

Promise Example

clamscan.scanDir('/some/path/to/scan').then(results=>{const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=results;//...}).catch(err=>{returnconsole.error(err);});

Async/Await Example

const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=awaitclamscan.scanDir('/some/path/to/scan');

.scanFiles(files[,endCallback[,fileCallback]])

This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for isInfected that simplifies the process of scanning many files or directories.

Parameters

  • files (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a fileList config option. If neither is found, the method will throw an error.

  • endCallback (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • goodFiles (array) List of the full paths to all files that are clean.
    • badFiles (array) List of the full paths to all files that are infected.
  • fileCallback (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • goodFiles (array) List of the full paths to all files that are clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • errors (object) Per-file errors keyed by the filename in which the error happened. (ex. {'foo.txt': Error})
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).

Callback Example

constscan_status={good: 0,bad: 0};constfiles=['/path/to/file/1.jpg','/path/to/file/2.mov','/path/to/file/3.rb'];clamscan.scanFiles(files,(err,goodFiles,badFiles,viruses)=>{if(err)returnconsole.error(err);if(badFiles.length>0){console.log({msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
badFiles,
goodFiles,
viruses,});}else{res.send({msg: "Everything looks good! No problems here!."});}},(err,file,isInfected,viruses)=>{;(isInfected ? scan_status.bad++ : scan_status.good++);console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);console.log('Scan Status: ',`${(scan_status.bad+scan_status.good)}/${files.length}`);});

Promise Example

Note: There is currently no way to get per-file notifications with the Promise API.

clamscan.scanFiles(files).then(results=>{const{ goodFiles, badFiles, errors, viruses }=results;// ...}).catch(err=>{console.error(err);})

Async/Await Example

const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles(files);

Scanning files listed in fileList

If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.

Files List Document:

/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb

Script:

constClamScan=newNodeClam().init({fileList: '/path/to/fileList.txt'});ClamScan.then(asyncclamscan=>{// Supply nothing to first parameter to use `fileList`const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles();});

.scanStream(stream[,callback])

This method allows you to scan a binary stream. NOTE: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.

Parameters

  • stream (stream) A readable stream object

  • callback (function) (optional) Will be called after the stream has been scanned (or attempted to be scanned):

    • err (object or null) A standard JavaScript Error object (null if no error)
    • isInfected (boolean) True: Stream is infected; False: Stream is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) NULL as no file path can be provided with the stream
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});constReadable=require('stream').Readable;constrs=Readable();rs.push('foooooo');rs.push('barrrrr');rs.push(null);clamscan.scanStream(stream,(err,{isInfected. viruses })=>{if(err)returnconsole.error(err);if(isInfected)returnconsole.log('Stream is infected! Booo!',viruses);console.log('Stream is not infected! Yay!');});

Promise Example:

clamscan.scanStream(stream).then(({isInfected})=>{if(isInfected)returnconsole.log("Stream is infected! Booo!");console.log("Stream is not infected! Yay!");}).catch(err=>{console.error(err);};

Promise Example:

const{ isInfected, viruses }=awaitclamscan.scanStream(stream);

.passthrough()

The passthrough method returns a PassthroughStream object which allows you pipe a ReadbleStream through it and on to another output. In the case of this module's passthrough implementation, it's actually forking the data to also go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to the output if that chunk was successfully sent to and received by ClamAV. The PassthroughStream object returned from this method has a special event that is emitted when ClamAV finishes scanning the streamed data so that you can decide if there's anything you need to do with the final output destination (ex. delete a file or S3 object).

In typical, non-passthrough setups, a file is uploaded to the local filesytem and then subsequently scanned. With that setup, you have to wait for the upload to complete and then wait again for the scan to complete. Using this module's passthrough method, you could theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to any WriteableStream output (examples: filesystem, S3, gzip, etc...).

As for these theoretical gains, your mileage my vary and I'd love to hear feedback on this to see where things can still be improved.

Please note that this method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (a practical working example can be found in the examples directory of this module):

Example

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});// For example's sake, we're using the Axios moduleconstaxios=require('Axios');// Get a readable stream for a URL requestconstinput=axios.get(some_url);// Create a writable stream to a local fileconstoutput=fs.createWriteStream(some_local_file);// Get instance of this module's PassthroughStream objectconstav=clamscan.passthrough();// Send output of Axios stream to ClamAV.// Send output of Axios to `some_local_file` if ClamAV receives data successfullyinput.pipe(av).pipe(output);// What happens when scan is completedav.on('scan-complete',result=>{const{ isInfected, viruses }=result;// Do stuff if you want});// What happens when data has been fully written to `output`output.on('finish',()=>{// Do stuff if you want});// NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications

.ping()

This method checks to see if the remote/local socket is working. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method can be used for healthcheck purposes and is already implicitly used during scan.

Parameters

  • callback (function) (optional) Will be called after the ping:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • client (object) A copy of the Socket/TCP client

Returns

  • Promise

    • Promise resolution returns: client (object): A copy of the Socket/TCP client

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});clamscan.ping((err,client)=>{if(err)returnconsole.error(err);console.log('ClamAV is still working!');client.end();});

Promise Example:

clamscan.ping().then((client)=>{console.log('ClamAV is still working!');client.end();}).catch(err=>{console.error(err);};

Promise Example:

constclient=awaitclamscan.ping();client.end();

Contribute

Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.

Resources used to help develop this module

About

A robust ClamAV virus scanning library supporting scanning files, directories, and streams with local sockets, local/remote TCP, and local clamscan/clamdscan binaries (with failover).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NodeJS Clamscan Virus Scanning Utility

NPM VersionNPM DownloadsNode.js VersionTest Suite

Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.

!!IMPORTANT

If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.

All older versions in NPM have been deprecated.

Version 1.0.0 Information

If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the release notes as there are some breaking changes (but also some awesome new features!).

Table of Contents

Dependencies

To use local binary method of scanning

You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.

Fedora-based distros:

sudo yum install clamav

Debian-based distros:

sudo apt-get install clamav clamav-daemon

For OS X, you can install clamav with brew:

sudo brew install clamav

To use ClamAV using TCP sockets

You will need access to either:

  1. A local UNIX Domain socket for a local instance of clamd
  1. A local/remote clamd daemon
  • Must know the port the daemon is running on
  • If running on remote server, you must have the IP address/domain name
  • If running on remote server, it's firewall must have the appropriate TCP port(s) open
  • Make sure clamd is running on your local/remote server

NOTE: This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).

How to Install

npm install clamscan

License Info

Licensed under the MIT License:

Getting Started

All of the values listed in the example below represent the default values for their respective configuration item.

You can simply do this:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init();

And, you'll be good to go.

BUT: If you want more control, you can specify all sorts of options.

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: false,// If true, removes infected filesquarantineInfected: false,// False: Don't quarantine, Path: Moves files to this place.scanLog: null,// Path to a writeable log file to write scan results intodebugMode: false,// Whether or not to log info/debug/error msgs to the consolefileList: null,// path to file containing list of files to scan (for scanFiles method)scanRecursively: true,// If true, deep scan folders recursivelyclamscan: {path: '/usr/bin/clamscan',// Path to clamscan binary on your serverdb: null,// Path to a custom virus definition databasescanArchives: true,// If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)active: true// If true, this module will consider using the clamscan binary},clamdscan: {socket: false,// Socket file for connecting via TCPhost: false,// IP of host to connect to TCP interfaceport: false,// Port of host to use when connecting via TCP interfacetimeout: 60000,// Timeout for scanning fileslocalFallback: true,// Use local preferred binary to scan if socket/tcp failspath: '/usr/bin/clamdscan',// Path to the clamdscan binary on your serverconfigFile: null,// Specify config file if it's in an unusual placemultiscan: true,// Scan using all available cores! Yay!reloadDb: false,// If true, will re-load the DB on every call (slow)active: true,// If true, this module will consider using the clamdscan binarybypassTest: false,// Check to see if socket is available when applicabletls: false,// Use plaintext TCP to connect to clamd},preference: 'clamdscan'// If clamdscan is found and active, it will be used by default});

Here is a non-default values example (to help you get an idea of what proper-looking values could be):

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: true,// Removes files if they are infectedquarantineInfected: '~/infected/',// Move file here. removeInfected must be FALSE, though.scanLog: '/var/log/node-clam',// You're a detail-oriented security professional.debugMode: true,// This will put some debug info in your js consolefileList: '/home/webuser/scanFiles.txt',// path to file containing list of files to scanscanRecursively: false,// Choosing false here will save some CPU cyclesclamscan: {path: '/usr/bin/clam',// I dunno, maybe your clamscan is just call "clam"scanArchives: false,// Choosing false here will save some CPU cyclesdb: '/usr/bin/better_clam_db',// Path to a custom virus definition databaseactive: false// you don't want to use this at all because it's evil},clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',// This is pretty typicalhost: '127.0.0.1',// If you want to connect locally but not through socketport: 12345,// Because, why nottimeout: 300000,// 5 minuteslocalFallback: false,// Do no fail over to binary-method of scanningpath: '/bin/clamdscan',// Special path to the clamdscan binary on your serverconfigFile: '/etc/clamd.d/daemon.conf',// A fairly typical config locationmultiscan: false,// You hate speed and multi-threaded awesome-saucereloadDb: true,// You want your scans to run slow like with clamscanactive: false,// you don't want to use this at all because it's evilbypassTest: true,// Don't check to see if socket is available. You should probably never set this to true.tls: true,// Connect to clamd over TLS},preference: 'clamscan'// If clamscan is found and active, it will be used by default});

NOTE: If a valid port is provided but no host value is provided, the clamscan will assume 'localhost' for host.

A note about using this module via sockets or TCP

As of version v1.0.0, this module supports communication with a local or remote ClamAV daemon through Unix Domain sockets or a TCP host/port combo. If you supply both in your configuration object, the UNIX Domain socket option will be used. The module will not not fallback to using the alternative Host/Port method. If you wish to connect via Host/Port and not a Socket, please either omit the socket property in the config object or use socket: null.

If you specify a valid clamscan/clamdscan binary in your config and you set clamdscan.localFallback: true in your config, this module will fallback to the traditional way this module has worked--using a binary directly/locally.

Also, there are some caveats to using the socket/tcp based approach:

  • The following configuration items are not honored (unless the module falls back to binary method):

    • removeInfected - remote clamd service config will dictate this
    • quarantineInfected - remote clamd service config will dictate this
    • scanLog - remote clamd service config will dictate this
    • fileList - this simply won't be available
    • clamscan.db - only available on fallback
    • clamscan.scanArchives - only available on fallback
    • clamscan.path - only available on fallback
    • clamdscan.configFile - only available on fallback
    • clamdscan.path - only available on fallback

Basic Usage Example

For the sake of brevity, all the examples in the API section will be shortened to just the relevant parts related specifically to that example. In those examples, we'll assume you already have an instance of the clamscan object. Since initializing the module returns a promise, you'll have to resolve that promise to get an instance of the clamscan object.

Below is the full example of how you could get that instance and run some methods:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init(options);// Get instance by resolving ClamScan promise objectClamScan.then(asyncclamscan=>{try{// You can re-use the `clamscan` object as many times as you wantconstversion=awaitclamscan.getVersion();console.log(`ClamAV Version: ${version}`);const{isInfected, file, viruses}=awaitclamscan.isInfected('/some/file.zip');if(isInfected)console.log(`${file} is infected with ${viruses}!`);}catch(err){// Handle any errors raised by the code in the try block}}).catch(err=>{// Handle errors that may have occurred during initialization});

If you're writing your code within an async function, getting an instance can be one less step:

constNodeClam=require('clamscan');asyncsome_function(){try{// Get instance by resolving ClamScan promise objectconstclamscan=awaitnewNodeClam().init(options);const{goodFiles, badFiles}=awaitclamscan.scanDir('/foo/bar');}catch(err){// Handle any errors raised by the code in the try block}}some_function();

API

Complete/functional examples for various use-cases can be found in the examples folder.

.getVersion([callback])

This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.

Parameters

  • callback (function) (optional) Will be called when the scan is complete. It receives 2 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • version (string) The version of the clamav server you're interfacing with

Returns

  • Promise

    • Promise resolution returns: version (string) The version of the clamav server you're interfacing with

Callback Example

clamscan.getVersion((err,version)=>{if(err)returnconsole.error(err);console.log(`ClamAV Version: ${version}`);});

Promise Example

clamscan.getVersion().then(version=>{console.log(`ClamAV Version: ${version}`);}).catch(err=>{console.error(err);});

.isInfected(filePath[,callback])

This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.

Alias

.scan_file

Parameters

  • filePath (string) Represents a path to the file to be scanned.

  • callback (function) (optional) Will be called when the scan is complete. It takes 3 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • file (string) The original filePath passed into the isInfected method.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
    • viruses (array) An array of any viruses found in the scanned file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) The original filePath passed into the isInfected method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Callback Example

clamscan.isInfected('/a/picture/for_example.jpg',(err,file,isInfected,viruses)=>{if(err)returnconsole.error(err);if(isInfected){console.log(`${file} is infected with ${viruses.join(', ')}.`);}});

Promise Example

clamscan.isInfected('/a/picture/for_example.jpg').then(result=>{const{file, isInfected, viruses}=result;if(isInfected)console.log(`${file} is infected with ${viruses.join(', ')}.`);}).then(err=>{console.error(err);})

Async/Await Example

const{file, isInfected, viruses}=awaitclamscan.isInfected('/a/picture/for_example.jpg');

.scanDir(dirPath[,endCallback[,fileCallback]])

Allows you to scan an entire directory for infected files. This obeys your recursive option even for clamdscan which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to scanFiles.

TL;DR: For maximum speed, don't supply a fileCallback.

If you choose to supply a fileCallback, the scan will run a little bit slower (depending on number of files to be scanned) for clamdscan. If you are using clamscan, while it will work, I'd highly advise you to NOT pass a fileCallback... it will run incredibly slow.

NOTE

The goodFiles parameter of the endCallback callback in this method will only contain the directory that was scanned in allbut the following scenarios:

  • A fileCallback callback is provided, and scanRecursively is set to true.
  • The scanner is set to clamdscan and scanRecursively is set to false.
  • The scanned directory contains 1 or more viruses. In this case, the goodFiles array will be empty.

There will, however, be a total count of the good files which is calculated by determining the total number of files scanned and subtracting the number of bad files from that count. We simply can't provide a list of all good files due to the potential large memory usage implications of scanning a directory with, for example, millions of files.

Parameters

  • dirPath (string) (required) Full path to the directory to scan.

  • endCallback (function) (optional) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:

    • err (object) A standard javascript Error object (null if no error)
    • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
    • badFiles (array) List of the full paths to all files that are infected.
    • viruses (array) List of all the viruses found (feature request: associate to the bad files).
    • numGoodFiles (number) Number of files that were found to be clean.
  • fileCallback (function) (optional) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard Javascript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • path (string) The original dir_path passed into the scanDir method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).
      • numGoodFiles (number) Number of files that were found to be clean.

Callback Example

clamscan.scanDir('/some/path/to/scan',(err,goodFiles,badFiles,viruses,numGoodFiles){if(err)returnconsole.error(err);if(badFiles.length>0){console.log(`${path} was infected. The offending files (${badFiles.join(', ')}) have been quarantined.`);console.log(`Viruses Found: ${viruses.join(', ')}`);}else{console.log(`${goodFiles[0]} looks good! ${numGoodFiles} file scanned and no problems found!.`);}});

Promise Example

clamscan.scanDir('/some/path/to/scan').then(results=>{const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=results;//...}).catch(err=>{returnconsole.error(err);});

Async/Await Example

const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=awaitclamscan.scanDir('/some/path/to/scan');

.scanFiles(files[,endCallback[,fileCallback]])

This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for isInfected that simplifies the process of scanning many files or directories.

Parameters

  • files (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a fileList config option. If neither is found, the method will throw an error.

  • endCallback (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • goodFiles (array) List of the full paths to all files that are clean.
    • badFiles (array) List of the full paths to all files that are infected.
  • fileCallback (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • goodFiles (array) List of the full paths to all files that are clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • errors (object) Per-file errors keyed by the filename in which the error happened. (ex. {'foo.txt': Error})
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).

Callback Example

constscan_status={good: 0,bad: 0};constfiles=['/path/to/file/1.jpg','/path/to/file/2.mov','/path/to/file/3.rb'];clamscan.scanFiles(files,(err,goodFiles,badFiles,viruses)=>{if(err)returnconsole.error(err);if(badFiles.length>0){console.log({msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
badFiles,
goodFiles,
viruses,});}else{res.send({msg: "Everything looks good! No problems here!."});}},(err,file,isInfected,viruses)=>{;(isInfected ? scan_status.bad++ : scan_status.good++);console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);console.log('Scan Status: ',`${(scan_status.bad+scan_status.good)}/${files.length}`);});

Promise Example

Note: There is currently no way to get per-file notifications with the Promise API.

clamscan.scanFiles(files).then(results=>{const{ goodFiles, badFiles, errors, viruses }=results;// ...}).catch(err=>{console.error(err);})

Async/Await Example

const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles(files);

Scanning files listed in fileList

If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.

Files List Document:

/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb

Script:

constClamScan=newNodeClam().init({fileList: '/path/to/fileList.txt'});ClamScan.then(asyncclamscan=>{// Supply nothing to first parameter to use `fileList`const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles();});

.scanStream(stream[,callback])

This method allows you to scan a binary stream. NOTE: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.

Parameters

  • stream (stream) A readable stream object

  • callback (function) (optional) Will be called after the stream has been scanned (or attempted to be scanned):

    • err (object or null) A standard JavaScript Error object (null if no error)
    • isInfected (boolean) True: Stream is infected; False: Stream is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) NULL as no file path can be provided with the stream
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});constReadable=require('stream').Readable;constrs=Readable();rs.push('foooooo');rs.push('barrrrr');rs.push(null);clamscan.scanStream(stream,(err,{isInfected. viruses })=>{if(err)returnconsole.error(err);if(isInfected)returnconsole.log('Stream is infected! Booo!',viruses);console.log('Stream is not infected! Yay!');});

Promise Example:

clamscan.scanStream(stream).then(({isInfected})=>{if(isInfected)returnconsole.log("Stream is infected! Booo!");console.log("Stream is not infected! Yay!");}).catch(err=>{console.error(err);};

Promise Example:

const{ isInfected, viruses }=awaitclamscan.scanStream(stream);

.passthrough()

The passthrough method returns a PassthroughStream object which allows you pipe a ReadbleStream through it and on to another output. In the case of this module's passthrough implementation, it's actually forking the data to also go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to the output if that chunk was successfully sent to and received by ClamAV. The PassthroughStream object returned from this method has a special event that is emitted when ClamAV finishes scanning the streamed data so that you can decide if there's anything you need to do with the final output destination (ex. delete a file or S3 object).

In typical, non-passthrough setups, a file is uploaded to the local filesytem and then subsequently scanned. With that setup, you have to wait for the upload to complete and then wait again for the scan to complete. Using this module's passthrough method, you could theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to any WriteableStream output (examples: filesystem, S3, gzip, etc...).

As for these theoretical gains, your mileage my vary and I'd love to hear feedback on this to see where things can still be improved.

Please note that this method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (a practical working example can be found in the examples directory of this module):

Example

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});// For example's sake, we're using the Axios moduleconstaxios=require('Axios');// Get a readable stream for a URL requestconstinput=axios.get(some_url);// Create a writable stream to a local fileconstoutput=fs.createWriteStream(some_local_file);// Get instance of this module's PassthroughStream objectconstav=clamscan.passthrough();// Send output of Axios stream to ClamAV.// Send output of Axios to `some_local_file` if ClamAV receives data successfullyinput.pipe(av).pipe(output);// What happens when scan is completedav.on('scan-complete',result=>{const{ isInfected, viruses }=result;// Do stuff if you want});// What happens when data has been fully written to `output`output.on('finish',()=>{// Do stuff if you want});// NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications

.ping()

This method checks to see if the remote/local socket is working. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method can be used for healthcheck purposes and is already implicitly used during scan.

Parameters

  • callback (function) (optional) Will be called after the ping:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • client (object) A copy of the Socket/TCP client

Returns

  • Promise

    • Promise resolution returns: client (object): A copy of the Socket/TCP client

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});clamscan.ping((err,client)=>{if(err)returnconsole.error(err);console.log('ClamAV is still working!');client.end();});

Promise Example:

clamscan.ping().then((client)=>{console.log('ClamAV is still working!');client.end();}).catch(err=>{console.error(err);};

Promise Example:

constclient=awaitclamscan.ping();client.end();

Contribute

Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.

Resources used to help develop this module

About

A robust ClamAV virus scanning library supporting scanning files, directories, and streams with local sockets, local/remote TCP, and local clamscan/clamdscan binaries (with failover).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

NodeJS Clamscan Virus Scanning Utility

NPM VersionNPM DownloadsNode.js VersionTest Suite

Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.

!!IMPORTANT

If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.

All older versions in NPM have been deprecated.

Version 1.0.0 Information

If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the release notes as there are some breaking changes (but also some awesome new features!).

Table of Contents

Dependencies

To use local binary method of scanning

You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.

Fedora-based distros:

sudo yum install clamav

Debian-based distros:

sudo apt-get install clamav clamav-daemon

For OS X, you can install clamav with brew:

sudo brew install clamav

To use ClamAV using TCP sockets

You will need access to either:

  1. A local UNIX Domain socket for a local instance of clamd
  1. A local/remote clamd daemon
  • Must know the port the daemon is running on
  • If running on remote server, you must have the IP address/domain name
  • If running on remote server, it's firewall must have the appropriate TCP port(s) open
  • Make sure clamd is running on your local/remote server

NOTE: This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).

How to Install

npm install clamscan

License Info

Licensed under the MIT License:

Getting Started

All of the values listed in the example below represent the default values for their respective configuration item.

You can simply do this:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init();

And, you'll be good to go.

BUT: If you want more control, you can specify all sorts of options.

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: false,// If true, removes infected filesquarantineInfected: false,// False: Don't quarantine, Path: Moves files to this place.scanLog: null,// Path to a writeable log file to write scan results intodebugMode: false,// Whether or not to log info/debug/error msgs to the consolefileList: null,// path to file containing list of files to scan (for scanFiles method)scanRecursively: true,// If true, deep scan folders recursivelyclamscan: {path: '/usr/bin/clamscan',// Path to clamscan binary on your serverdb: null,// Path to a custom virus definition databasescanArchives: true,// If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)active: true// If true, this module will consider using the clamscan binary},clamdscan: {socket: false,// Socket file for connecting via TCPhost: false,// IP of host to connect to TCP interfaceport: false,// Port of host to use when connecting via TCP interfacetimeout: 60000,// Timeout for scanning fileslocalFallback: true,// Use local preferred binary to scan if socket/tcp failspath: '/usr/bin/clamdscan',// Path to the clamdscan binary on your serverconfigFile: null,// Specify config file if it's in an unusual placemultiscan: true,// Scan using all available cores! Yay!reloadDb: false,// If true, will re-load the DB on every call (slow)active: true,// If true, this module will consider using the clamdscan binarybypassTest: false,// Check to see if socket is available when applicabletls: false,// Use plaintext TCP to connect to clamd},preference: 'clamdscan'// If clamdscan is found and active, it will be used by default});

Here is a non-default values example (to help you get an idea of what proper-looking values could be):

constNodeClam=require('clamscan');constClamScan=newNodeClam().init({removeInfected: true,// Removes files if they are infectedquarantineInfected: '~/infected/',// Move file here. removeInfected must be FALSE, though.scanLog: '/var/log/node-clam',// You're a detail-oriented security professional.debugMode: true,// This will put some debug info in your js consolefileList: '/home/webuser/scanFiles.txt',// path to file containing list of files to scanscanRecursively: false,// Choosing false here will save some CPU cyclesclamscan: {path: '/usr/bin/clam',// I dunno, maybe your clamscan is just call "clam"scanArchives: false,// Choosing false here will save some CPU cyclesdb: '/usr/bin/better_clam_db',// Path to a custom virus definition databaseactive: false// you don't want to use this at all because it's evil},clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',// This is pretty typicalhost: '127.0.0.1',// If you want to connect locally but not through socketport: 12345,// Because, why nottimeout: 300000,// 5 minuteslocalFallback: false,// Do no fail over to binary-method of scanningpath: '/bin/clamdscan',// Special path to the clamdscan binary on your serverconfigFile: '/etc/clamd.d/daemon.conf',// A fairly typical config locationmultiscan: false,// You hate speed and multi-threaded awesome-saucereloadDb: true,// You want your scans to run slow like with clamscanactive: false,// you don't want to use this at all because it's evilbypassTest: true,// Don't check to see if socket is available. You should probably never set this to true.tls: true,// Connect to clamd over TLS},preference: 'clamscan'// If clamscan is found and active, it will be used by default});

NOTE: If a valid port is provided but no host value is provided, the clamscan will assume 'localhost' for host.

A note about using this module via sockets or TCP

As of version v1.0.0, this module supports communication with a local or remote ClamAV daemon through Unix Domain sockets or a TCP host/port combo. If you supply both in your configuration object, the UNIX Domain socket option will be used. The module will not not fallback to using the alternative Host/Port method. If you wish to connect via Host/Port and not a Socket, please either omit the socket property in the config object or use socket: null.

If you specify a valid clamscan/clamdscan binary in your config and you set clamdscan.localFallback: true in your config, this module will fallback to the traditional way this module has worked--using a binary directly/locally.

Also, there are some caveats to using the socket/tcp based approach:

  • The following configuration items are not honored (unless the module falls back to binary method):

    • removeInfected - remote clamd service config will dictate this
    • quarantineInfected - remote clamd service config will dictate this
    • scanLog - remote clamd service config will dictate this
    • fileList - this simply won't be available
    • clamscan.db - only available on fallback
    • clamscan.scanArchives - only available on fallback
    • clamscan.path - only available on fallback
    • clamdscan.configFile - only available on fallback
    • clamdscan.path - only available on fallback

Basic Usage Example

For the sake of brevity, all the examples in the API section will be shortened to just the relevant parts related specifically to that example. In those examples, we'll assume you already have an instance of the clamscan object. Since initializing the module returns a promise, you'll have to resolve that promise to get an instance of the clamscan object.

Below is the full example of how you could get that instance and run some methods:

constNodeClam=require('clamscan');constClamScan=newNodeClam().init(options);// Get instance by resolving ClamScan promise objectClamScan.then(asyncclamscan=>{try{// You can re-use the `clamscan` object as many times as you wantconstversion=awaitclamscan.getVersion();console.log(`ClamAV Version: ${version}`);const{isInfected, file, viruses}=awaitclamscan.isInfected('/some/file.zip');if(isInfected)console.log(`${file} is infected with ${viruses}!`);}catch(err){// Handle any errors raised by the code in the try block}}).catch(err=>{// Handle errors that may have occurred during initialization});

If you're writing your code within an async function, getting an instance can be one less step:

constNodeClam=require('clamscan');asyncsome_function(){try{// Get instance by resolving ClamScan promise objectconstclamscan=awaitnewNodeClam().init(options);const{goodFiles, badFiles}=awaitclamscan.scanDir('/foo/bar');}catch(err){// Handle any errors raised by the code in the try block}}some_function();

API

Complete/functional examples for various use-cases can be found in the examples folder.

.getVersion([callback])

This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.

Parameters

  • callback (function) (optional) Will be called when the scan is complete. It receives 2 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • version (string) The version of the clamav server you're interfacing with

Returns

  • Promise

    • Promise resolution returns: version (string) The version of the clamav server you're interfacing with

Callback Example

clamscan.getVersion((err,version)=>{if(err)returnconsole.error(err);console.log(`ClamAV Version: ${version}`);});

Promise Example

clamscan.getVersion().then(version=>{console.log(`ClamAV Version: ${version}`);}).catch(err=>{console.error(err);});

.isInfected(filePath[,callback])

This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.

Alias

.scan_file

Parameters

  • filePath (string) Represents a path to the file to be scanned.

  • callback (function) (optional) Will be called when the scan is complete. It takes 3 parameters:

    • err (object or null) A standard javascript Error object (null if no error)
    • file (string) The original filePath passed into the isInfected method.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
    • viruses (array) An array of any viruses found in the scanned file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) The original filePath passed into the isInfected method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Callback Example

clamscan.isInfected('/a/picture/for_example.jpg',(err,file,isInfected,viruses)=>{if(err)returnconsole.error(err);if(isInfected){console.log(`${file} is infected with ${viruses.join(', ')}.`);}});

Promise Example

clamscan.isInfected('/a/picture/for_example.jpg').then(result=>{const{file, isInfected, viruses}=result;if(isInfected)console.log(`${file} is infected with ${viruses.join(', ')}.`);}).then(err=>{console.error(err);})

Async/Await Example

const{file, isInfected, viruses}=awaitclamscan.isInfected('/a/picture/for_example.jpg');

.scanDir(dirPath[,endCallback[,fileCallback]])

Allows you to scan an entire directory for infected files. This obeys your recursive option even for clamdscan which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to scanFiles.

TL;DR: For maximum speed, don't supply a fileCallback.

If you choose to supply a fileCallback, the scan will run a little bit slower (depending on number of files to be scanned) for clamdscan. If you are using clamscan, while it will work, I'd highly advise you to NOT pass a fileCallback... it will run incredibly slow.

NOTE

The goodFiles parameter of the endCallback callback in this method will only contain the directory that was scanned in allbut the following scenarios:

  • A fileCallback callback is provided, and scanRecursively is set to true.
  • The scanner is set to clamdscan and scanRecursively is set to false.
  • The scanned directory contains 1 or more viruses. In this case, the goodFiles array will be empty.

There will, however, be a total count of the good files which is calculated by determining the total number of files scanned and subtracting the number of bad files from that count. We simply can't provide a list of all good files due to the potential large memory usage implications of scanning a directory with, for example, millions of files.

Parameters

  • dirPath (string) (required) Full path to the directory to scan.

  • endCallback (function) (optional) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:

    • err (object) A standard javascript Error object (null if no error)
    • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
    • badFiles (array) List of the full paths to all files that are infected.
    • viruses (array) List of all the viruses found (feature request: associate to the bad files).
    • numGoodFiles (number) Number of files that were found to be clean.
  • fileCallback (function) (optional) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard Javascript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • path (string) The original dir_path passed into the scanDir method.
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • goodFiles (array) An empty array if path is infected. An array containing the directory name that was passed in if clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).
      • numGoodFiles (number) Number of files that were found to be clean.

Callback Example

clamscan.scanDir('/some/path/to/scan',(err,goodFiles,badFiles,viruses,numGoodFiles){if(err)returnconsole.error(err);if(badFiles.length>0){console.log(`${path} was infected. The offending files (${badFiles.join(', ')}) have been quarantined.`);console.log(`Viruses Found: ${viruses.join(', ')}`);}else{console.log(`${goodFiles[0]} looks good! ${numGoodFiles} file scanned and no problems found!.`);}});

Promise Example

clamscan.scanDir('/some/path/to/scan').then(results=>{const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=results;//...}).catch(err=>{returnconsole.error(err);});

Async/Await Example

const{ path, isInfected, goodFiles, badFiles, viruses, numGoodFiles }=awaitclamscan.scanDir('/some/path/to/scan');

.scanFiles(files[,endCallback[,fileCallback]])

This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for isInfected that simplifies the process of scanning many files or directories.

Parameters

  • files (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a fileList config option. If neither is found, the method will throw an error.

  • endCallback (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • goodFiles (array) List of the full paths to all files that are clean.
    • badFiles (array) List of the full paths to all files that are infected.
  • fileCallback (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • file (string) Path to the file that just got scanned.
    • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • goodFiles (array) List of the full paths to all files that are clean.
      • badFiles (array) List of the full paths to all files that are infected.
      • errors (object) Per-file errors keyed by the filename in which the error happened. (ex. {'foo.txt': Error})
      • viruses (array) List of all the viruses found (feature request: associate to the bad files).

Callback Example

constscan_status={good: 0,bad: 0};constfiles=['/path/to/file/1.jpg','/path/to/file/2.mov','/path/to/file/3.rb'];clamscan.scanFiles(files,(err,goodFiles,badFiles,viruses)=>{if(err)returnconsole.error(err);if(badFiles.length>0){console.log({msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
badFiles,
goodFiles,
viruses,});}else{res.send({msg: "Everything looks good! No problems here!."});}},(err,file,isInfected,viruses)=>{;(isInfected ? scan_status.bad++ : scan_status.good++);console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);console.log('Scan Status: ',`${(scan_status.bad+scan_status.good)}/${files.length}`);});

Promise Example

Note: There is currently no way to get per-file notifications with the Promise API.

clamscan.scanFiles(files).then(results=>{const{ goodFiles, badFiles, errors, viruses }=results;// ...}).catch(err=>{console.error(err);})

Async/Await Example

const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles(files);

Scanning files listed in fileList

If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.

Files List Document:

/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb

Script:

constClamScan=newNodeClam().init({fileList: '/path/to/fileList.txt'});ClamScan.then(asyncclamscan=>{// Supply nothing to first parameter to use `fileList`const{ goodFiles, badFiles, errors, viruses }=awaitclamscan.scanFiles();});

.scanStream(stream[,callback])

This method allows you to scan a binary stream. NOTE: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.

Parameters

  • stream (stream) A readable stream object

  • callback (function) (optional) Will be called after the stream has been scanned (or attempted to be scanned):

    • err (object or null) A standard JavaScript Error object (null if no error)
    • isInfected (boolean) True: Stream is infected; False: Stream is clean. NULL: Unable to scan file.

Returns

  • Promise

    • Promise resolution returns: result (object):

      • file (string) NULL as no file path can be provided with the stream
      • isInfected (boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
      • viruses (array) An array of any viruses found in the scanned file.

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});constReadable=require('stream').Readable;constrs=Readable();rs.push('foooooo');rs.push('barrrrr');rs.push(null);clamscan.scanStream(stream,(err,{isInfected. viruses })=>{if(err)returnconsole.error(err);if(isInfected)returnconsole.log('Stream is infected! Booo!',viruses);console.log('Stream is not infected! Yay!');});

Promise Example:

clamscan.scanStream(stream).then(({isInfected})=>{if(isInfected)returnconsole.log("Stream is infected! Booo!");console.log("Stream is not infected! Yay!");}).catch(err=>{console.error(err);};

Promise Example:

const{ isInfected, viruses }=awaitclamscan.scanStream(stream);

.passthrough()

The passthrough method returns a PassthroughStream object which allows you pipe a ReadbleStream through it and on to another output. In the case of this module's passthrough implementation, it's actually forking the data to also go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to the output if that chunk was successfully sent to and received by ClamAV. The PassthroughStream object returned from this method has a special event that is emitted when ClamAV finishes scanning the streamed data so that you can decide if there's anything you need to do with the final output destination (ex. delete a file or S3 object).

In typical, non-passthrough setups, a file is uploaded to the local filesytem and then subsequently scanned. With that setup, you have to wait for the upload to complete and then wait again for the scan to complete. Using this module's passthrough method, you could theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to any WriteableStream output (examples: filesystem, S3, gzip, etc...).

As for these theoretical gains, your mileage my vary and I'd love to hear feedback on this to see where things can still be improved.

Please note that this method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (a practical working example can be found in the examples directory of this module):

Example

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});// For example's sake, we're using the Axios moduleconstaxios=require('Axios');// Get a readable stream for a URL requestconstinput=axios.get(some_url);// Create a writable stream to a local fileconstoutput=fs.createWriteStream(some_local_file);// Get instance of this module's PassthroughStream objectconstav=clamscan.passthrough();// Send output of Axios stream to ClamAV.// Send output of Axios to `some_local_file` if ClamAV receives data successfullyinput.pipe(av).pipe(output);// What happens when scan is completedav.on('scan-complete',result=>{const{ isInfected, viruses }=result;// Do stuff if you want});// What happens when data has been fully written to `output`output.on('finish',()=>{// Do stuff if you want});// NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications

.ping()

This method checks to see if the remote/local socket is working. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method can be used for healthcheck purposes and is already implicitly used during scan.

Parameters

  • callback (function) (optional) Will be called after the ping:

    • err (object or null) A standard JavaScript Error object (null if no error)
    • client (object) A copy of the Socket/TCP client

Returns

  • Promise

    • Promise resolution returns: client (object): A copy of the Socket/TCP client

Examples

Callback Example:

constNodeClam=require('clamscan');// You'll need to specify your socket or TCP connection infoconstclamscan=newNodeClam().init({clamdscan: {socket: '/var/run/clamd.scan/clamd.sock',host: '127.0.0.1',port: 3310,}});clamscan.ping((err,client)=>{if(err)returnconsole.error(err);console.log('ClamAV is still working!');client.end();});

Promise Example:

clamscan.ping().then((client)=>{console.log('ClamAV is still working!');client.end();}).catch(err=>{console.error(err);};

Promise Example:

constclient=awaitclamscan.ping();client.end();

Contribute

Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.

Resources used to help develop this module

About

A robust ClamAV virus scanning library supporting scanning files, directories, and streams with local sockets, local/remote TCP, and local clamscan/clamdscan binaries (with failover).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages