KicomAV is an open source antivirus engine designed for detecting malware and disinfecting it. This antivirus engine is created and maintained by Kei Choi.
KicomAV is maintained as a production-grade open-source antivirus engine. Sponsorship keeps the engine stable, continuously improved, and usable for real security work.
kicomav-x is a high-performance experimental antivirus engine implemented in Rust. It is currently private due to its experimental nature. Once KicomAV reaches a sustainable sponsorship level, kicomav-x will be opened as part of the project.
- Sponsor: https://github.com/sponsors/hanul93
- Multi-format scanning: Files, archives (ZIP, RAR, 7z, CAB, ALZ, EGG, APK, OneNote), and nested containers
- YARA integration: Custom YARA rules support for advanced threat detection
- Intelligent caching: Dual-cache system with scan mode awareness for consistent, fast rescans
- Exclusion rules: Flexible file/directory exclusion with glob patterns
- Parallel scanning: Multi-threaded scanning for improved performance
- Daemon mode: REST API and clamd-compatible socket protocol
- Cross-platform: Windows, Linux, macOS support
- Library API: Use as a Python library in your projects
- PyInstaller support: Scan PyInstaller executables (Windows PE and Linux ELF)
- Requirements
- Installation
- Configuration
- Components
- Command-Line Usage (k2)
- Advanced Features
- Library Usage
- Daemon Mode (k2d & k2c)
- License
- Author
- Python 3.10+
- rich - Terminal formatting
- requests - HTTP library
- python-dotenv - Environment variables
- yara-python - YARA rules engine
- py7zr - 7z archive support
- rarfile - RAR archive support
- pycabfile - CAB archive support
Daemon mode dependencies (k2d, k2c):
- fastapi - REST API framework
- uvicorn - ASGI server
- python-multipart - Form data parsing
Optional dependencies:
- pylzma - LZMA compression (for NSIS)
pip install kicomavgit clone https://github.com/hanul93/kicomav.git
cd kicomav
pip install -e .pip install kicomav[daemon]KicomAV uses environment variables for configuration. Create a .env file in your home directory:
Windows:
mkdir %USERPROFILE%\.kicomav
copy .env.example %USERPROFILE%\.kicomav\.env
Linux/macOS:
mkdir -p ~/.kicomav
cp .env.example ~/.kicomav/.envThen edit ~/.kicomav/.env to configure:
| Variable | Description | Example |
|---|---|---|
UNRAR_TOOL |
Path to UnRAR executable | /usr/bin/unrar or C:\Program Files\WinRAR\UnRAR.exe |
RAR_TOOL |
Path to RAR executable | /usr/bin/rar or C:\Program Files\WinRAR\Rar.exe |
SYSTEM_RULES_BASE |
System rules path | /var/lib/kicomav/rules or C:\kicomav\rules |
USER_RULES_BASE |
User rules path | /home/user/kicomav_rules or C:\kicomav\user_rules |
Note: You can also place a
.envfile in the current working directory for project-specific settings (takes priority over global settings).
KicomAV provides three command-line tools:
| Tool | Description |
|---|---|
| k2 | Main scanner - scan files and directories for malware |
| k2d | Daemon server - REST API and socket protocol service |
| k2c | Client - communicate with k2d daemon |
- Standalone scanning: Use
k2directly for local file scanning - Client-server mode: Run
k2das a service, usek2cto send scan requests
$ k2 path[s] [options]
| Option | Description |
|---|---|
-f, --files |
Scan files (default) |
-r, --arc |
Scan archives |
-R, --nor |
Do not recurse into folders |
-I, --list |
Display all files |
-V, --vlist |
Display virus list |
-?, --help |
Show help |
| Option | Description |
|---|---|
-p, --prompt |
Prompt for action |
-d, --dis |
Disinfect files |
-l, --del |
Delete infected files |
--move |
Move infected files to quarantine |
--copy |
Copy infected files to quarantine |
| Option | Description |
|---|---|
--parallel |
Enable parallel file scanning |
--workers=N |
Number of worker threads (default: CPU count) |
--cache |
Enable scan cache (default) |
--no-cache |
Disable scan cache |
| Option | Description |
|---|---|
-G, --log=FILE |
Create log file |
-e, --app |
Append to log file |
-F, --infp=PATH |
Set quarantine folder |
--password=PWD |
Password for encrypted archives |
--no-color |
Disable colored output |
--sigtool |
Extract files from archives to output folder |
--update |
Update malware signatures |
Update signatures:
$ k2 --updateScan current directory:
$ k2 . -IScan with archive support:
$ k2 /path/to/scan -r -IParallel scanning with 8 workers:
$ k2 /path/to/scan --parallel --workers=8Scan and disinfect:
$ k2 /path/to/scan -dKicomAV supports flexible file exclusion to skip unwanted files during scans.
| Option | Description | Example |
|---|---|---|
--exclude=PATTERN |
Exclude files matching glob pattern | --exclude=*.log |
--exclude-ext=EXT |
Exclude by extension (comma-separated) | --exclude-ext=log,tmp |
--max-size=SIZE |
Skip files larger than size | --max-size=100MB |
--ignore-file=FILE |
Load rules from file | --ignore-file=.kicomav-ignore |
Size units: B, KB, MB, GB
# Skip log files and files over 50MB
k2 /path/to/scan --exclude-ext=log --max-size=50MB
# Skip multiple patterns
k2 /path/to/scan --exclude=**/node_modules/** --exclude=**/cache/**
# Use an ignore file
k2 /path/to/scan --ignore-file=.kicomav-ignoreCreate a .kicomav-ignore file (similar to .gitignore):
# Comment lines start with #
*.log # Exclude all .log files
*.tmp # Exclude all .tmp files
node_modules/ # Exclude node_modules directory
build/ # Exclude build directory
**/cache/** # Exclude cache directories anywhere
>100MB # Skip files larger than 100MB
Ignore file search order:
- Current directory
- Parent directories (up to root)
~/.kicomav/.kicomav-ignore
| Pattern | Matches | Does Not Match |
|---|---|---|
*.log |
app.log, error.log |
app.txt, log.txt |
**/cache/** |
/project/cache/file.txt |
/project/cached/file.txt |
node_modules/ |
/project/node_modules/pkg.json |
/project/my_modules/pkg.json |
file?.txt |
file1.txt, fileA.txt |
file10.txt, file.txt |
from kicomav.kavcore.k2exclude import ExclusionRule, create_exclusion_rule
# Create rule with factory function
rule = create_exclusion_rule(
patterns=["**/node_modules/**", "**/cache/**"],
extensions=["log", "tmp", "bak"],
max_size="100MB"
)
# Check if a file should be excluded
if rule.should_exclude("/path/to/file.log"):
print("File excluded")KicomAV supports intelligent caching to skip unchanged files during rescans, significantly improving scan performance.
- SQLite database stored at
~/.kicomav/cache.db - Dual cache system: Separate caches for regular files and archives
- Files are skipped if unchanged since last scan
- Cache invalidates when signature version changes
- Configurable expiration period (default: 7 days)
KicomAV uses a sophisticated dual-cache system:
| Cache Type | Purpose | Key |
|---|---|---|
| scan_cache | Regular files | file_path |
| archive_cache | Archives (ZIP, RAR, 7z, etc.) | (archive_path, scan_mode) |
Scan Mode Awareness: The archive cache tracks whether -r (deep archive scan) option was used:
-Iand-r -Iscans maintain separate cache entries- Switching between scan modes produces consistent results
- Each mode's statistics (Files, Packed, Infected) are preserved independently
- Contents hash: Archives identified by hash of internal file list
- Infection tracking: Cached results include infected file paths with malware names
- Statistics preservation: File count, packed count, and scan paths are cached
- Smart revalidation: Re-compressed archives with same files remain cache-valid
| Option | Description | Default |
|---|---|---|
--cache |
Enable scan cache | Enabled |
--no-cache |
Disable scan cache | - |
--cache-clear |
Clear all cache entries | - |
--cache-stats |
Show cache statistics | - |
--cache-expire=DAYS |
Set expiration (0=never) | 7 |
# Scan with cache (default behavior)
k2 /path/to/scan -r
# Disable cache for fresh scan
k2 /path/to/scan -r --no-cache
# View cache statistics
k2 --cache-stats
# Clear cache
k2 --cache-clear
# Set 30-day cache expiration
k2 /path/to/scan --cache-expire=30Scan Cache Statistics
----------------------------------------
Cache path: ~/.kicomav/cache.db
Cache size: 1.2MB
Total entries: 5532 (files: 5432, archives: 100)
Clean files: 5410
Infected files: 22
Clean archives: 95
Infected archives: 5
Expired entries: 128
Expire days: 7
The cache automatically invalidates when:
- File modified: File size or modification time changed
- Signature updated: Signature version differs from cached version
- Cache expired: Entry older than expiration period
- File deleted: Entry removed during maintenance
- Scan mode changed: Archive scanned with different
-roption (archive cache only) - Contents changed: Archive internal file list differs (archive cache only)
Example scan times with caching enabled:
| Scan | Option | Time | Notes |
|---|---|---|---|
| First | -r -I |
~40s | Full scan, cache populated |
| Second | -r -I |
~2s | Cache hit |
| Third | -I |
~18s | Different mode, separate cache |
| Fourth | -I |
~0s | Cache hit |
| Fifth | -r -I |
~2s | Returns to cached -r results |
This ensures:
- Rescanning unchanged files/archives is nearly instant
- Different scan modes maintain independent, consistent results
- Infected entries from previous scans are reported from cache
from kicomav.kavcore.k2cache import (
ScanCache, compute_file_hash, compute_contents_hash, create_cache
)
# Create cache with custom expiration
cache = create_cache(expire_days=14)
# Check if file needs scanning
if cache.needs_scan("/path/to/file", signature_version="1.0"):
file_hash = compute_file_hash("/path/to/file")
cache.update("/path/to/file", file_hash, "clean", signature_version="1.0")
else:
result = cache.get_cached_result("/path/to/file", "1.0")
if result:
scan_result, malware_name = result
print(f"Cached result: {scan_result}")
# Archive cache usage
# Create contents hash from archive entries (filename, size, crc)
entries = [("file1.txt", 100, 12345), ("file2.txt", 200, 67890)]
contents_hash = compute_contents_hash(entries)
# opt_arc parameter controls cache separation:
# - opt_arc=False: -I option (partial archive scan)
# - opt_arc=True: -r -I option (full archive scan)
opt_arc = True # Using -r option
# Check if archive needs scanning
if cache.needs_archive_scan("/path/to/archive.zip", contents_hash, "1.0", opt_arc):
# Perform archive scan...
# After scanning, update cache with results
infected = [{"path": "malware.exe", "malware": "Trojan.Test"}]
cache.update_archive(
"/path/to/archive.zip",
archive_hash="abc123",
contents_hash=contents_hash,
scan_result="infected",
infected_entries=infected,
signature_version="1.0",
opt_arc=opt_arc # Store scan mode
)
else:
# Use cached archive result
result = cache.get_archive_cached_result(
"/path/to/archive.zip", contents_hash, "1.0", opt_arc
)
if result:
scan_result, infected_entries, total_files, total_packed, scanned_paths = result
print(f"Archive result: {scan_result}")
print(f"Files: {total_files}, Packed: {total_packed}")
for entry in infected_entries:
print(f" Infected: {entry['path']} - {entry['malware']}")
# Get statistics
stats = cache.get_stats()
print(f"Total entries: {stats['total_entries']}")
print(f"File entries: {stats['file_entries']}")
print(f"Archive entries: {stats['archive_entries']}")
# Maintenance
cache.prune_expired() # Remove expired entries
cache.prune_missing() # Remove entries for deleted files
cache.vacuum() # Compact database
cache.close()Enable multi-threaded scanning for improved performance on multi-core systems.
# Auto-detect CPU count
k2 /path/to/scan --parallel
# Specify worker count
k2 /path/to/scan --parallel --workers=8KicomAV can be used as a Python library in your projects.
import kicomav
# Scan a single file
with kicomav.Scanner() as scanner:
result = scanner.scan_file("/path/to/suspicious_file.exe")
if result.infected:
print(f"Malware detected: {result.malware_name}")
else:
print("File is clean")import kicomav
# Scan an entire directory
with kicomav.Scanner() as scanner:
results = scanner.scan_directory("/path/to/folder", recursive=True)
infected_files = [r for r in results if r.infected]
print(f"Scanned {len(results)} files, found {len(infected_files)} infected")
for result in infected_files:
print(f" {result.path}: {result.malware_name}")import kicomav
result = kicomav.update()
if result.package_update_available:
print(f"New version available: {result.latest_version}")
if result.updated_files:
print(f"Updated {len(result.updated_files)} signature files")import kicomav
config = kicomav.get_config()
print(f"System rules path: {config.system_rules_base}")
print(f"User rules path: {config.user_rules_base}")from kicomav.kavcore.config import suppress_warnings
suppress_warnings(True)
import kicomav # No configuration warningsKicomAV provides high-level APIs for exploring and extracting archives.
import kicomav
# List archive contents
with kicomav.Scanner() as scanner:
info, entries = scanner.list_archive("/path/to/archive.zip")
print(f"Format: {info.format_type}")
for entry in entries:
print(f"{' '*entry.depth}{entry.path}")
# Extract archive to a folder
with kicomav.Scanner() as scanner:
result = scanner.extract_archive(
"/path/to/archive.zip",
"/output/folder"
)
print(f"Extracted {result.extracted_count} files")
print(f"Log file: {result.log_file}")
# Read a single file from archive into memory
with kicomav.Scanner() as scanner:
data = scanner.read_archive("/path/to/archive.zip", "config.json")
if data:
import json
config = json.loads(data.decode('utf-8'))Supported Formats: ZIP, RAR, 7z, CAB, ALZ, EGG, TAR, GZ, BZ2, APK, OneNote, PyInstaller (PE/ELF)
Exception Handling:
import kicomav
try:
with kicomav.Scanner() as scanner:
result = scanner.extract_archive("/path/to/archive.zip", "/output")
except kicomav.ArchiveNotFoundError:
print("Archive file not found")
except kicomav.ArchiveFormatError:
print("Unsupported or corrupted archive format")
except kicomav.ArchivePasswordError:
print("Archive is password protected")
except kicomav.ArchiveSecurityError:
print("Security issue detected (e.g., path traversal)")
except kicomav.ArchiveExtractionError as e:
print(f"Extraction failed: {e}")import kicomav
engine = kicomav.Engine(verbose=True)
engine.set_plugins("/path/to/plugins")
instance = engine.create_instance()
instance.init()
# Get engine information
info = instance.getinfo()
for plugin_info in info:
print(f"Plugin: {plugin_info.get('title')}")
# Scan with callback
def on_detect(result, filename, malware_name, malware_id):
print(f"Detected: {malware_name} in {filename}")
instance.scan("/path/to/file.exe", on_detect)
instance.uninit()KicomAV can run as a daemon server, providing both REST API and clamd-compatible socket protocol.
# Start both REST API and Socket server
k2d
# Start REST API only (port 8311)
k2d --http-only
# Start Socket server only (port 3311)
k2d --socket-only
# Generate API key for authentication
k2d --generate-key| Variable | Default | Description |
|---|---|---|
K2D_HTTP_HOST |
127.0.0.1 |
HTTP bind address |
K2D_HTTP_PORT |
8311 |
HTTP port |
K2D_SOCKET_PORT |
3311 |
Socket port |
K2D_MAX_UPLOAD_SIZE |
52428800 |
Max upload size (50MB) |
K2D_MAX_WORKERS |
CPU count | Max concurrent workers |
K2D_API_KEY |
- | API key for authentication |
K2D_REQUIRE_AUTH |
false |
Require authentication |
k2c is a command-line client for communicating with the k2d daemon.
# Server status
k2c --ping # Check server connection
k2c --version # Get version info
k2c --stats # Get statistics
# Scan files
k2c /path/to/file # Scan a file
k2c /path/to/folder # Scan a directory
k2c --stream < suspicious.exe # Scan from stdin
# Connection options
k2c --host 192.168.1.100 # Connect to remote server
k2c --socket # Use socket protocol
k2c --api-key YOUR_KEY # Authentication
# Cache management
k2c --cache-stats # View cache statistics
k2c --cache-clear # Clear cache
# Output options
k2c --json # JSON output
k2c -q # Only show infected files$ k2c eicar.txt
============================================================
KicomAV Client (k2c) v0.41
============================================================
eicar.txt infected : EICAR-Test-File (not a virus)
----------------------------------------
Files scanned: 1
Infected: 1
Errors: 0| Method | Endpoint | Description |
|---|---|---|
| GET | /ping |
Health check |
| GET | /version |
Version and signature info |
| GET | /stats |
Scan statistics |
| POST | /scan/file |
Scan uploaded file |
| POST | /scan/path |
Scan local path |
| POST | /scan/stream |
Scan base64 encoded data |
| POST | /reload |
Reload signatures |
Examples:
# Health check
curl http://127.0.0.1:8311/ping
# Scan a file
curl -X POST -F "file=@suspicious.exe" http://127.0.0.1:8311/scan/file
# Scan a directory
curl -X POST -H "Content-Type: application/json" \
-d '{"path": "/path/to/scan", "recursive": true}' \
http://127.0.0.1:8311/scan/path| Command | Description |
|---|---|
PING |
Health check (returns PONG) |
VERSION |
Get version info |
STATS |
Get statistics |
SCAN <path> |
Scan a file |
CONTSCAN <path> |
Scan directory recursively |
INSTREAM |
Scan streamed data |
RELOAD |
Reload signatures |
Example (Python):
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 3311))
# PING test
sock.send(b"PING\n")
print(sock.recv(1024)) # b'PONG\n'
# Scan a file
sock.send(b"SCAN /path/to/file.exe\n")
print(sock.recv(1024)) # b'/path/to/file.exe: OK\n'
sock.close()This project is licensed under the MIT License - see the LICENSE file for details.
Kei Choi
