NodeJS Clamscan Virus Scanning Utility
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 0.8.2, please upgrade! There was a security vulnerability in previous versions that allows a malicious user to execute code on your server. Specific details on how the attack could be implemented will not be disclosed here. Please update to 0.8.2 or greater ASAP. No breaking changes are included, only the security patch.
All other versions in NPM have been deprecated.
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
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:
- A local UNIX Domain socket for a local instance of
clamd
- 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
Licence 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:
var clam = require('clamscan')();
And, you'll be good to go.
BUT: If you want more control, you can specify all sorts of options.
var clam = require('clamscan')({
remove_infected: false,
quarantine_infected: false,
scan_log: null,
debug_mode: false
file_list: null,
scan_recursively: true,
clamscan: {
path: '/usr/bin/clamscan',
db: null,
scan_archives: true,
active: true
},
clamdscan: {
socket: false,
host: false,
port: false,
path: '/usr/bin/clamdscan',
local_fallback: false,
config_file: '/etc/clamd.conf',
multiscan: true,
reload_db: false,
active: true
},
preference: 'clamdscan'
});
Here is a non-default values example (to help you get an idea of what the proper-looking values should be):
var clam = require('clamscan')({
remove_infected: true,
quarantine_infected: '~/infected/',
scan_recursively: false,
scan_log: '/var/log/node-clam',
debug_mode: true
file_list: '/home/webuser/scan_files.txt',
clamscan: {
path: '/usr/bin/clam',
db: '/usr/bin/better_clam_db',
scan_archives: false,
active: false
},
clamdscan: {
socket: '/var/run/clamd.scan/clamd.sock',
host: '127.0.0.1',
port: 12345,
path: '/bin/clamdscan',
local_fallback: true,
config_file: '/etc/clamd.d/daemon.conf',
multiscan: false,
reload_db: true,
active: false
},
preference: 'clamscan'
});
A note about using this module via sockets or TCP
As of version 0.9, 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 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.local_fallback: 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):
remove_infected
- remote clamd service config will dictate thisquarantine_infected
- remote clamd service config will dictate thisscan_log
- remote clamd service config will dictate thisfile_list
- this simply won't be availableclamscan.db
- only available on fallbackclamscan.scan_archives
- only available on fallbackclamscan.path
- only available on fallbackclamdscan.config_file
- only available on fallbackclamdscan.path
- only available on fallback
API
.get_version(callback)
This method allows you to determine the version of clamav you are interfacing with
Parameters:
callback
(function) (optional) Will be called when the scan is complete. It takes 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
Example:
clam.get_version(function(err, version) {
if (err) {
console.log(err);
}
console.log("ClamAV Version: " + version);
});
.is_infected(file_path, callback)
This method allows you to scan a single file.
Alias:
.scan_file
Parameters:
file_path
(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 file_path
passed into the is_infected
method.is_infected
(boolean) True: File is infected; False: File is clean. NULL: Unable to scan.
Example:
clam.is_infected('/a/picture/for_example.jpg', function(err, file, is_infected) {
if (err) {
console.log(err);
return false;
}
if (is_infected) {
res.send({msg: "File is infected!"});
} else {
res.send({msg: "File is clean!"});
}
});
.scan_dir(dir_path, end_callback, file_callback)
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 scan_files
.
TL;DR: For maximum speed, don't supply a file_callback
.
If you choose to supply a file_callback
, 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 file_callback
... it will run incredibly slow.
NOTE:
The good_files
and bad_files
parameters of the end_callback
callback in this method will only contain the directories that were scanned in all but the following scenarios:
- A
file_callback
callback is provided, and scan_recursively
is set to true. - The scanner is set to
clamdscan
and scan_recursively
is set to false.
Parameters
dir_path
(string) (required) Full path to the directory to scan.end_callback
(function) (required) 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)good_files
(array) List of the full paths to all files that are clean.bad_files
(array) List of the full paths to all files that are infected.
file_callback
(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.is_infected
(boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.
Example
clam.scan_dir('/some/path/to/scan', function(err, good_files, bad_files) {
if (!err) {
if (bad_files.length > 0) {
res.send({msg: "Your directory was infected. The offending files have been quarantined."});
} else {
res.send({msg: "Everything looks good! No problems here!."});
}
} else {
}
});
.scan_files(files, end_callback, file_callback)
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 is_infected
that simplifies the process of scanning many files or directories.
Parameters
files
(array) A list of strings representing full paths to files you want scanned.end_callback
(function) 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)good_files
(array) List of the full paths to all files that are clean.bad_files
(array) List of the full paths to all files that are infected.
file_callback
(function) 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.is_infected
(boolean) True: File is infected; False: File is clean. NULL: Unable to scan file.
Example
var scan_status = {
good: 0,
bad: 0
};
var files = [
'/path/to/file/1.jpg',
'/path/to/file/2.mov',
'/path/to/file/3.rb'
];
clam.scan_files(files, function(err, good_files, bad_files) {
if (!err) {
if (bad_files.length > 0) {
res.send({
msg: good_files.length + ' files were OK. ' + bad_files.length + ' were infected!',
bad: bad_files,
good: good_files
});
} else {
res.send({msg: "Everything looks good! No problems here!."});
}
} else {
}
}, function(err, file, is_infected) {
if (is_infected) {
scan_status.bad++;
} else {
scan_status.good++;
}
console.log("Scan Status: " + (scan_status.bad + scan_status.good) + "/" + files.length);
});
.scan_stream(stream, callback)
This method allows you to scan a binary stream. NOTE: This method will only work if the scanning method is a TCP or UNIX Domain socket. In other words, this will not work if you are using the local binary method.
Parameters
stream
(stream) A nodejs stream objectcallback
(function) 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)is_infected
(boolean) True: Stream is infected; False: Stream is clean. NULL: Unable to scan file.
Examples
Contrived Example:
var Readable = require('stream').Readable;
var rs = Readable();
rs.push('foooooo');
rs.push('barrrrr');
rs.push(null);
clam.scan_stream(stream, function(err, is_infected) {
if (err) {
console.log(err);
} else {
if (is_infected) {
console.log("Stream is infected! Booo!");
} else {
console.log("Stream is not infected! Yay!");
}
}
});
Slightly More "Realistic" Example:
This example shows how to scan every HTTP request as it comes in to your server using middleware funcionality in express (definitely not advisable!).
var express = require('express');
var app = express();
app.use(function (req, res, next) {
clam.scan_stream(req, function(err, is_infected) {
if (err) {
console.log("Unable to scan request for viruses!", err});
res.status(500).send({error: "There was an error accepting your request!"});
} else {
if (is_infected) {
res.status(500).send({error: "Your request is virus-infected!");
} else {
next();
}
}
});
});
app.get('/', function(req, res) {
});
var server = app.listen(3000);
Scanning files listed in file list
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:
/some/path/to/file.zip
/some/other/path/to/file.exe
/one/more/file/to/scan.rb
Script:
var clam = require('clamscan')({
file_list: '/path/to/file_list.txt'
});
clam.scan_files(null, function(err, good_files, bad_files) {
});
Changing Configuration After Instantiation
You can set settings directly on an instance of this module using the following syntax:
var clam = require('clamscan')({ });
clam.settings.quarantine_infected = true;
clam.is_infected('/some/file.txt');
clam.settings.quarantine_infected = false;
clam.is_infected('/some/file.txt');
Just keep in mind that some of the nice validation that happens on instantiation won't happen if it's done this way. Of course, you could also just create a new instance with different a different initial configuration.
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:
Items for version 1.0 release:
- Slight change of API to allow for a completely asynchronous module (ie, removal of all
fs.xxSync
items). - Allow the ability to scan Buffers, Streams, and Strings directly.