Search by

phil / upload-scan

phil

Antivirus scanning of uploaded files behind a port: clean, infected with a signature, or not scanned with a reason; a clamd adapter that speaks the protocol itself over a Unix or TCP socket, in chunks, with a size limit and timeouts; null and EICAR fake scanners for tests; and a policy that rejects,

0.1.0-alpha 2026-11-11 06:11 UTC

This package is auto-updated.

Last update: 2026-11-11 13:33:14 UTC


README

Every uploaded file through an antivirus before anyone else can open it.

phil/upload-scan scans the files an application receives — manuscripts, images, attachments — behind one port, Scanner: a file in, a ScanResult out. The result is clean; infected, with the name of the signature that matched; or an error, with the reason the file could not be scanned to its end. A ScanPolicy turns it into a decision: a clean file is accepted, an infected one rejected, and a file that could not be scanned is rejected or accepted and flagged, as the application chooses.

It ships ClamdScanner, which speaks the protocol of clamd, the daemon of ClamAV, itself: the file is streamed over a Unix or TCP socket in chunks (zINSTREAM), so clamd can run in another container and never reads the application's disk. The scanner bounds what it sends with a size limit and every read and write with a timeout, and answers an error result instead of throwing when clamd is down. NullScanner and FakeScanner, which finds the EICAR test string, stand in for clamd in tests.

It is a plain PHP library with no dependency: no third-party ClamAV client, no extension beyond those PHP builds by default. A scanner keeps nothing between two files, so one instance serves every request of a long-running worker.

Status

Part State
The Scanner port, ScanResult, ScanPolicy and its two answers to an error Implemented and tested
ClamdScanner: INSTREAM in chunks over a Unix or TCP socket, size limit, timeouts, PING, VERSION Implemented and tested
The reader of clamd's answers, fuzzed Implemented and tested
NullScanner and FakeScanner (EICAR and needles of the test's choice) Implemented and tested
Sessions (IDSESSION) and several files on one connection Not started: one connection per file keeps the scanner stateless
A Symfony bundle: configuration, a #[CleanFile] constraint, quarantine, events, rescans, integrations Implemented in phil/upload-scan-bundle

Documentation

Document What it covers
Installation Requirements, installing, building a scanner.
Scanning The port, the results, the policy and its decisions.
clamd ClamdScanner, the protocol it speaks, the size limit, the timeouts, the probe.
Extension points Another antivirus behind Scanner, another way to reach clamd.
Testing The library's tests, fuzz target and benchmark; NullScanner and FakeScanner in the application's tests.
Reference Every type of the library, with its role.

Quick start

composer require phil/upload-scan
use Phil\UploadScan\Clamd\ClamdScanner;
use Phil\UploadScan\OnScanError;
use Phil\UploadScan\ScanPolicy;

$scanner = ClamdScanner::at('tcp://clamav:3310');
$policy = new ScanPolicy(OnScanError::Reject);

$result = $scanner->scanPath($uploadedFile);

if (!$policy->decide($result)->keeps()) {
    // refused: $result->signature names the threat, or $result->reason why it was not scanned
}

Requirements

PHP 8.5 or later; clamd, for ClamdScanner.

Contributing

Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.

License

MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.